diff options
166 files changed, 6987 insertions, 1563 deletions
diff --git a/Android.mk b/Android.mk index 1a66896b2545..7fda1dde463a 100644 --- a/Android.mk +++ b/Android.mk @@ -729,6 +729,7 @@ aidl_files := \ 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/os/SharedMemory.aidl \ frameworks/base/core/java/android/os/StrictMode.aidl \ frameworks/base/core/java/android/accessibilityservice/AccessibilityServiceInfo.aidl \ frameworks/base/core/java/android/net/Network.aidl \ diff --git a/apct-tests/perftests/multiuser/Android.mk b/apct-tests/perftests/multiuser/Android.mk index f67004358ee6..e3f7775383bd 100644 --- a/apct-tests/perftests/multiuser/Android.mk +++ b/apct-tests/perftests/multiuser/Android.mk @@ -20,8 +20,7 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_STATIC_JAVA_LIBRARIES := \ - android-support-test \ - apct-perftests-utils + android-support-test LOCAL_PACKAGE_NAME := MultiUserPerfTests diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResults.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResults.java new file mode 100644 index 000000000000..0d764ce29c74 --- /dev/null +++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResults.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017 The Android Open 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.multiuser; + +import android.os.Bundle; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.TimeUnit; + +public class BenchmarkResults { + private final ArrayList<Long> mResults = new ArrayList<>(); + + public void addDuration(long duration) { + mResults.add(TimeUnit.NANOSECONDS.toMillis(duration)); + } + + public Bundle getStats() { + final Bundle stats = new Bundle(); + stats.putDouble("Mean (ms)", mean()); + stats.putDouble("Median (ms)", median()); + stats.putDouble("Sigma (ms)", standardDeviation()); + return stats; + } + + public ArrayList<Long> getAllDurations() { + return mResults; + } + + private double mean() { + final int size = mResults.size(); + long sum = 0; + for (int i = 0; i < size; ++i) { + sum += mResults.get(i); + } + return (double) sum / size; + } + + private double median() { + final int size = mResults.size(); + if (size == 0) { + return 0f; + } + Collections.sort(mResults); + final int idx = size / 2; + return size % 2 == 0 + ? (double) (mResults.get(idx) + mResults.get(idx - 1)) / 2 + : mResults.get(idx); + } + + private double standardDeviation() { + final int size = mResults.size(); + if (size == 0) { + return 0f; + } + final double mean = mean(); + double sd = 0; + for (int i = 0; i < size; ++i) { + double diff = mResults.get(i) - mean; + sd += diff * diff; + } + return Math.sqrt(sd / size); + } +} diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResultsReporter.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResultsReporter.java new file mode 100644 index 000000000000..7472865e9a5a --- /dev/null +++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkResultsReporter.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2017 The Android Open 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.multiuser; + +import android.app.Activity; +import android.app.Instrumentation; +import android.os.Bundle; +import android.support.test.InstrumentationRegistry; +import android.util.Log; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; + +public class BenchmarkResultsReporter implements TestRule { + private final BenchmarkRunner mRunner; + + public BenchmarkResultsReporter(BenchmarkRunner benchmarkRunner) { + mRunner = benchmarkRunner; + } + + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + base.evaluate(); + final Bundle stats = mRunner.getStats(); + final String summary = getSummaryString(description.getMethodName(), stats); + logSummary(description.getTestClass().getSimpleName(), summary, mRunner.getAllDurations()); + stats.putString(Instrumentation.REPORT_KEY_STREAMRESULT, summary); + InstrumentationRegistry.getInstrumentation().sendStatus( + Activity.RESULT_OK, stats); + } + }; + } + + private void logSummary(String tag, String summary, ArrayList<Long> durations) { + final StringBuilder sb = new StringBuilder(summary); + final int size = durations.size(); + for (int i = 0; i < size; ++i) { + sb.append("\n").append(i).append("->").append(durations.get(i)); + } + Log.d(tag, sb.toString()); + } + + private String getSummaryString(String testName, Bundle stats) { + final StringBuilder sb = new StringBuilder(); + sb.append("\n\n").append(getKey(testName)); + for (String key : stats.keySet()) { + sb.append("\n").append(key).append(": ").append(stats.get(key)); + } + return sb.toString(); + } + + private String getKey(String testName) { + return testName.replaceAll("Perf$", ""); + } +} diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java new file mode 100644 index 000000000000..ccadc9a8f6a9 --- /dev/null +++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 The Android Open 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.multiuser; + +import android.os.Bundle; +import android.os.SystemClock; + +import java.util.ArrayList; + +// Based on //platform/frameworks/base/apct-tests/perftests/utils/BenchmarkState.java +public class BenchmarkRunner { + + private static long COOL_OFF_PERIOD_MS = 2000; + + private static final int NUM_ITERATIONS = 4; + + private static final int NOT_STARTED = 0; // The benchmark has not started yet. + private static final int RUNNING = 1; // The benchmark is running. + private static final int PAUSED = 2; // The benchmark is paused + private static final int FINISHED = 3; // The benchmark has stopped. + + private final BenchmarkResults mResults = new BenchmarkResults(); + private int mState = NOT_STARTED; // Current benchmark state. + private int mIteration; + + public long mStartTimeNs; + public long mPausedDurationNs; + public long mPausedTimeNs; + + public boolean keepRunning() { + switch (mState) { + case NOT_STARTED: + mState = RUNNING; + prepareForNextRun(); + return true; + case RUNNING: + mIteration++; + return startNextTestRun(); + case PAUSED: + throw new IllegalStateException("Benchmarking is in paused state"); + case FINISHED: + throw new IllegalStateException("Benchmarking is finished"); + default: + throw new IllegalStateException("BenchmarkRunner is in unknown state"); + } + } + + private boolean startNextTestRun() { + mResults.addDuration(System.nanoTime() - mStartTimeNs - mPausedDurationNs); + if (mIteration == NUM_ITERATIONS) { + mState = FINISHED; + return false; + } else { + prepareForNextRun(); + return true; + } + } + + private void prepareForNextRun() { + // TODO: Once http://b/63115387 is fixed, look into using "am wait-for-broadcast-idle" + // command instead of waiting for a fixed amount of time. + SystemClock.sleep(COOL_OFF_PERIOD_MS); + mStartTimeNs = System.nanoTime(); + mPausedDurationNs = 0; + } + + public void pauseTiming() { + if (mState != RUNNING) { + throw new IllegalStateException("Unable to pause the runner: not running currently"); + } + mPausedTimeNs = System.nanoTime(); + mState = PAUSED; + } + + public void resumeTiming() { + if (mState != PAUSED) { + throw new IllegalStateException("Unable to resume the runner: already running"); + } + mPausedDurationNs += System.nanoTime() - mPausedTimeNs; + mState = RUNNING; + } + + public Bundle getStats() { + return mResults.getStats(); + } + + public ArrayList<Long> getAllDurations() { + return mResults.getAllDurations(); + } +}
\ No newline at end of file diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTest.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTest.java index e89157b52108..f114ef47007b 100644 --- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTest.java +++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTest.java @@ -27,8 +27,6 @@ import android.content.pm.UserInfo; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; -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; @@ -47,26 +45,34 @@ import java.util.concurrent.TimeUnit; * Perf tests for user life cycle events. * * Running the tests: + * * make MultiUserPerfTests && * adb install -r \ * ${ANDROID_PRODUCT_OUT}/data/app/MultiUserPerfTests/MultiUserPerfTests.apk && * adb shell am instrument -e class android.multiuser.UserLifecycleTest \ * -w com.android.perftests.multiuser/android.support.test.runner.AndroidJUnitRunner + * + * or + * + * bit MultiUserPerfTests:android.multiuser.UserLifecycleTest + * + * Note: If you use bit for running the tests, benchmark results won't be printed on the host side. + * But in either case, results can be checked on the device side 'adb logcat -s UserLifecycleTest' */ @LargeTest @RunWith(AndroidJUnit4.class) public class UserLifecycleTest { - private final int TIMEOUT_IN_SECOND = 10; + private final int TIMEOUT_IN_SECOND = 30; private final int CHECK_USER_REMOVED_INTERVAL_MS = 200; private UserManager mUm; private ActivityManager mAm; private IActivityManager mIam; - private BenchmarkState mState; private ArrayList<Integer> mUsersToRemove; + private final BenchmarkRunner mRunner = new BenchmarkRunner(); @Rule - public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); + public BenchmarkResultsReporter mReporter = new BenchmarkResultsReporter(mRunner); @Before public void setUp() { @@ -74,7 +80,6 @@ public class UserLifecycleTest { mUm = UserManager.get(context); mAm = context.getSystemService(ActivityManager.class); mIam = ActivityManager.getService(); - mState = mPerfStatusReporter.getBenchmarkState(); mUsersToRemove = new ArrayList<>(); } @@ -91,7 +96,7 @@ public class UserLifecycleTest { @Test public void createAndStartUserPerf() throws Exception { - while (mState.keepRunning()) { + while (mRunner.keepRunning()) { final UserInfo userInfo = mUm.createUser("TestUser", 0); final CountDownLatch latch = new CountDownLatch(1); @@ -99,91 +104,91 @@ public class UserLifecycleTest { mIam.startUserInBackground(userInfo.id); latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); - mState.pauseTiming(); + mRunner.pauseTiming(); removeUser(userInfo.id); - mState.resumeTiming(); + mRunner.resumeTiming(); } } @Test public void switchUserPerf() throws Exception { - while (mState.keepRunning()) { - mState.pauseTiming(); + while (mRunner.keepRunning()) { + mRunner.pauseTiming(); final int startUser = mAm.getCurrentUser(); final UserInfo userInfo = mUm.createUser("TestUser", 0); - mState.resumeTiming(); + mRunner.resumeTiming(); switchUser(userInfo.id); - mState.pauseTiming(); + mRunner.pauseTiming(); switchUser(startUser); removeUser(userInfo.id); - mState.resumeTiming(); + mRunner.resumeTiming(); } } @Test public void stopUserPerf() throws Exception { - while (mState.keepRunning()) { - mState.pauseTiming(); + while (mRunner.keepRunning()) { + mRunner.pauseTiming(); final UserInfo userInfo = mUm.createUser("TestUser", 0); final CountDownLatch latch = new CountDownLatch(1); registerBroadcastReceiver(Intent.ACTION_USER_STARTED, latch, userInfo.id); mIam.startUserInBackground(userInfo.id); latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); - mState.resumeTiming(); + mRunner.resumeTiming(); stopUser(userInfo.id, false); - mState.pauseTiming(); + mRunner.pauseTiming(); removeUser(userInfo.id); - mState.resumeTiming(); + mRunner.resumeTiming(); } } @Test public void lockedBootCompletedPerf() throws Exception { - while (mState.keepRunning()) { - mState.pauseTiming(); + while (mRunner.keepRunning()) { + mRunner.pauseTiming(); final int startUser = mAm.getCurrentUser(); final UserInfo userInfo = mUm.createUser("TestUser", 0); final CountDownLatch latch = new CountDownLatch(1); registerUserSwitchObserver(null, latch, userInfo.id); - mState.resumeTiming(); + mRunner.resumeTiming(); mAm.switchUser(userInfo.id); latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); - mState.pauseTiming(); + mRunner.pauseTiming(); switchUser(startUser); removeUser(userInfo.id); - mState.resumeTiming(); + mRunner.resumeTiming(); } } @Test public void managedProfileUnlockPerf() throws Exception { - while (mState.keepRunning()) { - mState.pauseTiming(); + while (mRunner.keepRunning()) { + mRunner.pauseTiming(); final UserInfo userInfo = mUm.createProfileForUser("TestUser", UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser()); final CountDownLatch latch = new CountDownLatch(1); registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch, userInfo.id); - mState.resumeTiming(); + mRunner.resumeTiming(); mIam.startUserInBackground(userInfo.id); latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); - mState.pauseTiming(); + mRunner.pauseTiming(); removeUser(userInfo.id); - mState.resumeTiming(); + mRunner.resumeTiming(); } } @Test public void ephemeralUserStoppedPerf() throws Exception { - while (mState.keepRunning()) { - mState.pauseTiming(); + while (mRunner.keepRunning()) { + mRunner.pauseTiming(); final int startUser = mAm.getCurrentUser(); final UserInfo userInfo = mUm.createUser("TestUser", UserInfo.FLAG_EPHEMERAL | UserInfo.FLAG_DEMO); @@ -200,35 +205,35 @@ public class UserLifecycleTest { }, new IntentFilter(Intent.ACTION_USER_STOPPED)); final CountDownLatch switchLatch = new CountDownLatch(1); registerUserSwitchObserver(switchLatch, null, startUser); - mState.resumeTiming(); + mRunner.resumeTiming(); mAm.switchUser(startUser); latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); - mState.pauseTiming(); + mRunner.pauseTiming(); switchLatch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); removeUser(userInfo.id); - mState.resumeTiming(); + mRunner.resumeTiming(); } } @Test public void managedProfileStoppedPerf() throws Exception { - while (mState.keepRunning()) { - mState.pauseTiming(); + while (mRunner.keepRunning()) { + mRunner.pauseTiming(); final UserInfo userInfo = mUm.createProfileForUser("TestUser", UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser()); final CountDownLatch latch = new CountDownLatch(1); registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch, userInfo.id); mIam.startUserInBackground(userInfo.id); latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS); - mState.resumeTiming(); + mRunner.resumeTiming(); stopUser(userInfo.id, true); - mState.pauseTiming(); + mRunner.pauseTiming(); removeUser(userInfo.id); - mState.resumeTiming(); + mRunner.resumeTiming(); } } diff --git a/api/current.txt b/api/current.txt index 17dbcbf392b9..0bda5706ff13 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1180,6 +1180,7 @@ package android { field public static final int showSilent = 16843259; // 0x10101fb field public static final int showText = 16843949; // 0x10104ad field public static final deprecated int showWeekNumber = 16843582; // 0x101033e + field public static final int showWhenLocked = 16844137; // 0x1010569 field public static final deprecated int shownWeekCount = 16843585; // 0x1010341 field public static final int shrinkColumns = 16843082; // 0x101014a field public static final deprecated int singleLine = 16843101; // 0x101015d @@ -1438,6 +1439,7 @@ package android { field public static final int trimPathOffset = 16843786; // 0x101040a field public static final int trimPathStart = 16843784; // 0x1010408 field public static final int tunerCount = 16844061; // 0x101051d + field public static final int turnScreenOn = 16844138; // 0x101056a field public static final int type = 16843169; // 0x10101a1 field public static final int typeface = 16842902; // 0x1010096 field public static final int uiOptions = 16843672; // 0x1010398 @@ -3764,10 +3766,12 @@ package android.app { method public final void setResult(int); method public final void setResult(int, android.content.Intent); method public final deprecated void setSecondaryProgress(int); + method public void setShowWhenLocked(boolean); method public void setTaskDescription(android.app.ActivityManager.TaskDescription); method public void setTitle(java.lang.CharSequence); method public void setTitle(int); method public deprecated void setTitleColor(int); + method public void setTurnScreenOn(boolean); method public void setVisible(boolean); method public final void setVolumeControlStream(int); method public void setVrModeEnabled(boolean, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; @@ -31023,12 +31027,12 @@ package android.os { public class MemoryFile { ctor public MemoryFile(java.lang.String, int) throws java.io.IOException; - method public synchronized boolean allowPurging(boolean) throws java.io.IOException; + method public deprecated synchronized boolean allowPurging(boolean) throws java.io.IOException; method public void close(); - method protected void finalize(); + method public java.io.FileDescriptor getFileDescriptor() throws java.io.IOException; method public java.io.InputStream getInputStream(); method public java.io.OutputStream getOutputStream(); - method public boolean isPurgingAllowed(); + method public deprecated boolean isPurgingAllowed(); method public int length(); method public int readBytes(byte[], int, int, int) throws java.io.IOException; method public void writeBytes(byte[], int, int, int) throws java.io.IOException; @@ -31453,6 +31457,22 @@ package android.os { field public static final android.os.Parcelable.Creator<android.os.ResultReceiver> CREATOR; } + public final class SharedMemory implements java.io.Closeable android.os.Parcelable { + method public void close(); + method public static android.os.SharedMemory create(java.lang.String, int) throws android.system.ErrnoException; + method public int describeContents(); + method public int getFd(); + method public java.io.FileDescriptor getFileDescriptor(); + method public int getSize(); + method public java.nio.ByteBuffer map(int, int, int) throws android.system.ErrnoException; + method public java.nio.ByteBuffer mapReadOnly() throws android.system.ErrnoException; + method public java.nio.ByteBuffer mapReadWrite() throws android.system.ErrnoException; + method public boolean setProtect(int); + method public static void unmap(java.nio.ByteBuffer); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.os.SharedMemory> CREATOR; + } + public class StatFs { ctor public StatFs(java.lang.String); method public deprecated int getAvailableBlocks(); @@ -46953,12 +46973,12 @@ package android.view { field public static final int FLAG_SCALED = 16384; // 0x4000 field public static final int FLAG_SECURE = 8192; // 0x2000 field public static final int FLAG_SHOW_WALLPAPER = 1048576; // 0x100000 - field public static final int FLAG_SHOW_WHEN_LOCKED = 524288; // 0x80000 + field public static final deprecated int FLAG_SHOW_WHEN_LOCKED = 524288; // 0x80000 field public static final int FLAG_SPLIT_TOUCH = 8388608; // 0x800000 field public static final deprecated int FLAG_TOUCHABLE_WHEN_WAKING = 64; // 0x40 field public static final int FLAG_TRANSLUCENT_NAVIGATION = 134217728; // 0x8000000 field public static final int FLAG_TRANSLUCENT_STATUS = 67108864; // 0x4000000 - field public static final int FLAG_TURN_SCREEN_ON = 2097152; // 0x200000 + field public static final deprecated int FLAG_TURN_SCREEN_ON = 2097152; // 0x200000 field public static final int FLAG_WATCH_OUTSIDE_TOUCH = 262144; // 0x40000 field public static final int FORMAT_CHANGED = 8; // 0x8 field public static final int LAST_APPLICATION_WINDOW = 99; // 0x63 @@ -49014,6 +49034,7 @@ package android.webkit { method public void setNetworkAvailable(boolean); method public deprecated void setPictureListener(android.webkit.WebView.PictureListener); method public void setRendererPriorityPolicy(int, boolean); + method public static void setSafeBrowsingWhiteList(java.lang.String[]); method public deprecated void setVerticalScrollbarOverlay(boolean); method public void setWebChromeClient(android.webkit.WebChromeClient); method public static void setWebContentsDebuggingEnabled(boolean); diff --git a/api/system-current.txt b/api/system-current.txt index 53e1b81d95d3..12d3d9697e70 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1307,6 +1307,7 @@ package android { field public static final int showSilent = 16843259; // 0x10101fb field public static final int showText = 16843949; // 0x10104ad field public static final deprecated int showWeekNumber = 16843582; // 0x101033e + field public static final int showWhenLocked = 16844137; // 0x1010569 field public static final deprecated int shownWeekCount = 16843585; // 0x1010341 field public static final int shrinkColumns = 16843082; // 0x101014a field public static final deprecated int singleLine = 16843101; // 0x101015d @@ -1565,6 +1566,7 @@ package android { field public static final int trimPathOffset = 16843786; // 0x101040a field public static final int trimPathStart = 16843784; // 0x1010408 field public static final int tunerCount = 16844061; // 0x101051d + field public static final int turnScreenOn = 16844138; // 0x101056a field public static final int type = 16843169; // 0x10101a1 field public static final int typeface = 16842902; // 0x1010096 field public static final int uiOptions = 16843672; // 0x1010398 @@ -3900,10 +3902,12 @@ package android.app { method public final void setResult(int); method public final void setResult(int, android.content.Intent); method public final deprecated void setSecondaryProgress(int); + method public void setShowWhenLocked(boolean); method public void setTaskDescription(android.app.ActivityManager.TaskDescription); method public void setTitle(java.lang.CharSequence); method public void setTitle(int); method public deprecated void setTitleColor(int); + method public void setTurnScreenOn(boolean); method public void setVisible(boolean); method public final void setVolumeControlStream(int); method public void setVrModeEnabled(boolean, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; @@ -17242,6 +17246,8 @@ package android.hardware.radio { method public int getSpacing(); method public int getType(); method public int getUpperLimit(); + method public boolean isAmBand(); + method public boolean isFmBand(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.hardware.radio.RadioManager.BandDescriptor> CREATOR; } @@ -33773,12 +33779,12 @@ package android.os { public class MemoryFile { ctor public MemoryFile(java.lang.String, int) throws java.io.IOException; - method public synchronized boolean allowPurging(boolean) throws java.io.IOException; + method public deprecated synchronized boolean allowPurging(boolean) throws java.io.IOException; method public void close(); - method protected void finalize(); + method public java.io.FileDescriptor getFileDescriptor() throws java.io.IOException; method public java.io.InputStream getInputStream(); method public java.io.OutputStream getOutputStream(); - method public boolean isPurgingAllowed(); + method public deprecated boolean isPurgingAllowed(); method public int length(); method public int readBytes(byte[], int, int, int) throws java.io.IOException; method public void writeBytes(byte[], int, int, int) throws java.io.IOException; @@ -34232,6 +34238,22 @@ package android.os { field public static final android.os.Parcelable.Creator<android.os.ResultReceiver> CREATOR; } + public final class SharedMemory implements java.io.Closeable android.os.Parcelable { + method public void close(); + method public static android.os.SharedMemory create(java.lang.String, int) throws android.system.ErrnoException; + method public int describeContents(); + method public int getFd(); + method public java.io.FileDescriptor getFileDescriptor(); + method public int getSize(); + method public java.nio.ByteBuffer map(int, int, int) throws android.system.ErrnoException; + method public java.nio.ByteBuffer mapReadOnly() throws android.system.ErrnoException; + method public java.nio.ByteBuffer mapReadWrite() throws android.system.ErrnoException; + method public boolean setProtect(int); + method public static void unmap(java.nio.ByteBuffer); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.os.SharedMemory> CREATOR; + } + public class StatFs { ctor public StatFs(java.lang.String); method public deprecated int getAvailableBlocks(); @@ -50436,6 +50458,7 @@ package android.view { } public abstract interface WindowManager implements android.view.ViewManager { + method public abstract android.graphics.Region getCurrentImeTouchRegion(); method public abstract android.view.Display getDefaultDisplay(); method public abstract void removeViewImmediate(android.view.View); } @@ -50504,12 +50527,12 @@ package android.view { field public static final int FLAG_SCALED = 16384; // 0x4000 field public static final int FLAG_SECURE = 8192; // 0x2000 field public static final int FLAG_SHOW_WALLPAPER = 1048576; // 0x100000 - field public static final int FLAG_SHOW_WHEN_LOCKED = 524288; // 0x80000 + field public static final deprecated int FLAG_SHOW_WHEN_LOCKED = 524288; // 0x80000 field public static final int FLAG_SPLIT_TOUCH = 8388608; // 0x800000 field public static final deprecated int FLAG_TOUCHABLE_WHEN_WAKING = 64; // 0x40 field public static final int FLAG_TRANSLUCENT_NAVIGATION = 134217728; // 0x8000000 field public static final int FLAG_TRANSLUCENT_STATUS = 67108864; // 0x4000000 - field public static final int FLAG_TURN_SCREEN_ON = 2097152; // 0x200000 + field public static final deprecated int FLAG_TURN_SCREEN_ON = 2097152; // 0x200000 field public static final int FLAG_WATCH_OUTSIDE_TOUCH = 262144; // 0x40000 field public static final int FORMAT_CHANGED = 8; // 0x8 field public static final int LAST_APPLICATION_WINDOW = 99; // 0x63 @@ -52660,6 +52683,7 @@ package android.webkit { method public void setNetworkAvailable(boolean); method public deprecated void setPictureListener(android.webkit.WebView.PictureListener); method public void setRendererPriorityPolicy(int, boolean); + method public static void setSafeBrowsingWhiteList(java.lang.String[]); method public deprecated void setVerticalScrollbarOverlay(boolean); method public void setWebChromeClient(android.webkit.WebChromeClient); method public static void setWebContentsDebuggingEnabled(boolean); @@ -52864,6 +52888,7 @@ package android.webkit { method public abstract java.lang.String getDefaultUserAgent(android.content.Context); method public abstract void initSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean>); method public abstract android.net.Uri[] parseFileChooserResult(int, android.content.Intent); + method public abstract void setSafeBrowsingWhiteList(java.lang.String[]); method public abstract void setWebContentsDebuggingEnabled(boolean); method public abstract void shutdownSafeBrowsing(); } diff --git a/api/test-current.txt b/api/test-current.txt index abfcaba3727e..6d401e1758fb 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1180,6 +1180,7 @@ package android { field public static final int showSilent = 16843259; // 0x10101fb field public static final int showText = 16843949; // 0x10104ad field public static final deprecated int showWeekNumber = 16843582; // 0x101033e + field public static final int showWhenLocked = 16844137; // 0x1010569 field public static final deprecated int shownWeekCount = 16843585; // 0x1010341 field public static final int shrinkColumns = 16843082; // 0x101014a field public static final deprecated int singleLine = 16843101; // 0x101015d @@ -1438,6 +1439,7 @@ package android { field public static final int trimPathOffset = 16843786; // 0x101040a field public static final int trimPathStart = 16843784; // 0x1010408 field public static final int tunerCount = 16844061; // 0x101051d + field public static final int turnScreenOn = 16844138; // 0x101056a field public static final int type = 16843169; // 0x10101a1 field public static final int typeface = 16842902; // 0x1010096 field public static final int uiOptions = 16843672; // 0x1010398 @@ -3766,10 +3768,12 @@ package android.app { method public final void setResult(int); method public final void setResult(int, android.content.Intent); method public final deprecated void setSecondaryProgress(int); + method public void setShowWhenLocked(boolean); method public void setTaskDescription(android.app.ActivityManager.TaskDescription); method public void setTitle(java.lang.CharSequence); method public void setTitle(int); method public deprecated void setTitleColor(int); + method public void setTurnScreenOn(boolean); method public void setVisible(boolean); method public final void setVolumeControlStream(int); method public void setVrModeEnabled(boolean, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; @@ -31154,12 +31158,12 @@ package android.os { public class MemoryFile { ctor public MemoryFile(java.lang.String, int) throws java.io.IOException; - method public synchronized boolean allowPurging(boolean) throws java.io.IOException; + method public deprecated synchronized boolean allowPurging(boolean) throws java.io.IOException; method public void close(); - method protected void finalize(); + method public java.io.FileDescriptor getFileDescriptor() throws java.io.IOException; method public java.io.InputStream getInputStream(); method public java.io.OutputStream getOutputStream(); - method public boolean isPurgingAllowed(); + method public deprecated boolean isPurgingAllowed(); method public int length(); method public int readBytes(byte[], int, int, int) throws java.io.IOException; method public void writeBytes(byte[], int, int, int) throws java.io.IOException; @@ -31585,6 +31589,22 @@ package android.os { field public static final android.os.Parcelable.Creator<android.os.ResultReceiver> CREATOR; } + public final class SharedMemory implements java.io.Closeable android.os.Parcelable { + method public void close(); + method public static android.os.SharedMemory create(java.lang.String, int) throws android.system.ErrnoException; + method public int describeContents(); + method public int getFd(); + method public java.io.FileDescriptor getFileDescriptor(); + method public int getSize(); + method public java.nio.ByteBuffer map(int, int, int) throws android.system.ErrnoException; + method public java.nio.ByteBuffer mapReadOnly() throws android.system.ErrnoException; + method public java.nio.ByteBuffer mapReadWrite() throws android.system.ErrnoException; + method public boolean setProtect(int); + method public static void unmap(java.nio.ByteBuffer); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.os.SharedMemory> CREATOR; + } + public class StatFs { ctor public StatFs(java.lang.String); method public deprecated int getAvailableBlocks(); @@ -47369,12 +47389,12 @@ package android.view { field public static final int FLAG_SCALED = 16384; // 0x4000 field public static final int FLAG_SECURE = 8192; // 0x2000 field public static final int FLAG_SHOW_WALLPAPER = 1048576; // 0x100000 - field public static final int FLAG_SHOW_WHEN_LOCKED = 524288; // 0x80000 + field public static final deprecated int FLAG_SHOW_WHEN_LOCKED = 524288; // 0x80000 field public static final int FLAG_SPLIT_TOUCH = 8388608; // 0x800000 field public static final deprecated int FLAG_TOUCHABLE_WHEN_WAKING = 64; // 0x40 field public static final int FLAG_TRANSLUCENT_NAVIGATION = 134217728; // 0x8000000 field public static final int FLAG_TRANSLUCENT_STATUS = 67108864; // 0x4000000 - field public static final int FLAG_TURN_SCREEN_ON = 2097152; // 0x200000 + field public static final deprecated int FLAG_TURN_SCREEN_ON = 2097152; // 0x200000 field public static final int FLAG_WATCH_OUTSIDE_TOUCH = 262144; // 0x40000 field public static final int FORMAT_CHANGED = 8; // 0x8 field public static final int LAST_APPLICATION_WINDOW = 99; // 0x63 @@ -49435,6 +49455,7 @@ package android.webkit { method public void setNetworkAvailable(boolean); method public deprecated void setPictureListener(android.webkit.WebView.PictureListener); method public void setRendererPriorityPolicy(int, boolean); + method public static void setSafeBrowsingWhiteList(java.lang.String[]); method public deprecated void setVerticalScrollbarOverlay(boolean); method public void setWebChromeClient(android.webkit.WebChromeClient); method public static void setWebContentsDebuggingEnabled(boolean); diff --git a/cmds/vr/src/com/android/commands/vr/Vr.java b/cmds/vr/src/com/android/commands/vr/Vr.java index b765866faef9..8fb1b7b80618 100644 --- a/cmds/vr/src/com/android/commands/vr/Vr.java +++ b/cmds/vr/src/com/android/commands/vr/Vr.java @@ -41,6 +41,7 @@ public final class Vr extends BaseCommand { "set-persistent-vr-mode-enabled"; private static final String COMMAND_SET_VR2D_DISPLAY_PROPERTIES = "set-display-props"; + private static final String COMMAND_ENABLE_VD = "enable-virtual-display"; private IVrManager mVrService; @@ -49,7 +50,8 @@ public final class Vr extends BaseCommand { out.println( "usage: vr [subcommand]\n" + "usage: vr set-persistent-vr-mode-enabled [true|false]\n" + - "usage: vr set-display-props [width] [height] [dpi]\n" + "usage: vr set-display-props [width] [height] [dpi]\n" + + "usage: vr enable-virtual-display [true|false]\n" ); } @@ -69,6 +71,9 @@ public final class Vr extends BaseCommand { case COMMAND_SET_PERSISTENT_VR_MODE_ENABLED: runSetPersistentVrModeEnabled(); break; + case COMMAND_ENABLE_VD: + runEnableVd(); + break; default: throw new IllegalArgumentException ("unknown command '" + command + "'"); } @@ -94,6 +99,23 @@ public final class Vr extends BaseCommand { } } + private void runEnableVd() throws RemoteException { + Vr2dDisplayProperties.Builder builder = new Vr2dDisplayProperties.Builder(); + + String value = nextArgRequired(); + if ("true".equals(value)) { + builder.setEnabled(true); + } else if ("false".equals(value)) { + builder.setEnabled(false); + } // Don't do anything if not exactly true/false + + try { + mVrService.setVr2dDisplayProperties(builder.build()); + } catch (RemoteException re) { + System.err.println("Error: Can't enable (" + value +") virtual display" + re); + } + } + private void runSetPersistentVrModeEnabled() throws RemoteException { String enableStr = nextArg(); boolean enabled = Boolean.parseBoolean(enableStr); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 0ff3215e1271..adb3152d4ae7 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -801,10 +801,6 @@ public class Activity extends ContextThemeWrapper final Handler mHandler = new Handler(); final FragmentController mFragments = FragmentController.createController(new HostCallbacks()); - // Most recent call to requestVisibleBehind(). - @Deprecated - boolean mVisibleBehind; - private static final class ManagedCursor { ManagedCursor(Cursor cursor) { mCursor = cursor; @@ -6459,10 +6455,6 @@ public class Activity extends ContextThemeWrapper @Deprecated @SystemApi public boolean isBackgroundVisibleBehind() { - try { - return ActivityManager.getService().isBackgroundVisibleBehind(mToken); - } catch (RemoteException e) { - } return false; } @@ -7556,6 +7548,53 @@ public class Activity extends ContextThemeWrapper } } + /** + * Specifies whether an {@link Activity} should be shown on top of the the lock screen whenever + * the lockscreen is up and the activity is resumed. Normally an activity will be transitioned + * to the stopped state if it is started while the lockscreen is up, but with this flag set the + * activity will remain in the resumed state visible on-top of the lock screen. This value can + * be set as a manifest attribute using {@link android.R.attr#showWhenLocked}. + * + * @param showWhenLocked {@code true} to show the {@link Activity} on top of the lock screen; + * {@code false} otherwise. + * @see #setTurnScreenOn(boolean) + * @see android.R.attr#turnScreenOn + * @see android.R.attr#showWhenLocked + */ + public void setShowWhenLocked(boolean showWhenLocked) { + try { + ActivityManager.getService().setShowWhenLocked(mToken, showWhenLocked); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call setShowWhenLocked", e); + } + } + + /** + * Specifies whether the screen should be turned on when the {@link Activity} is resumed. + * Normally an activity will be transitioned to the stopped state if it is started while the + * screen if off, but with this flag set the activity will cause the screen to turn on if the + * activity will be visible and resumed due to the screen coming on. The screen will not be + * turned on if the activity won't be visible after the screen is turned on. This flag is + * normally used in conjunction with the {@link android.R.attr#showWhenLocked} flag to make sure + * the activity is visible after the screen is turned on when the lockscreen is up. In addition, + * if this flag is set and the activity calls {@link + * KeyguardManager#requestDismissKeyguard(Activity, KeyguardManager.KeyguardDismissCallback)} + * the screen will turn on. + * + * @param turnScreenOn {@code true} to turn on the screen; {@code false} otherwise. + * + * @see #setShowWhenLocked(boolean) + * @see android.R.attr#turnScreenOn + * @see android.R.attr#showWhenLocked + */ + public void setTurnScreenOn(boolean turnScreenOn) { + try { + ActivityManager.getService().setTurnScreenOn(mToken, turnScreenOn); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call setTurnScreenOn", e); + } + } + class HostCallbacks extends FragmentHostCallback<Activity> { public HostCallbacks() { super(Activity.this /*activity*/); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 06dbe8218450..20a4ba159ef9 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -24,42 +24,35 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.graphics.Canvas; -import android.graphics.GraphicBuffer; -import android.graphics.Matrix; -import android.graphics.Point; -import android.os.BatteryStats; -import android.os.Build; -import android.os.Build.VERSION_CODES; -import android.os.IBinder; -import android.os.ParcelFileDescriptor; - -import com.android.internal.app.procstats.ProcessStats; -import com.android.internal.os.RoSystemProperties; -import com.android.internal.os.TransferPipe; -import com.android.internal.util.FastPrintWriter; -import com.android.server.LocalServices; - import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.UriPermission; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.GraphicBuffer; +import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.Rect; +import android.os.BatteryStats; +import android.os.Build; +import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Debug; import android.os.Handler; +import android.os.IBinder; import android.os.Parcel; +import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; @@ -72,6 +65,12 @@ import android.util.DisplayMetrics; import android.util.Singleton; import android.util.Size; +import com.android.internal.app.procstats.ProcessStats; +import com.android.internal.os.RoSystemProperties; +import com.android.internal.os.TransferPipe; +import com.android.internal.util.FastPrintWriter; +import com.android.server.LocalServices; + import org.xmlpull.v1.XmlSerializer; import java.io.FileDescriptor; @@ -904,19 +903,6 @@ public class ActivityManager { } /** - * Returns true if activities contained in this stack can request visible behind by - * calling {@link Activity#requestVisibleBehind}. - * - * @deprecated This method's functionality is no longer supported as of - * {@link android.os.Build.VERSION_CODES#O} and will be removed in a future release. - */ - @Deprecated - public static boolean activitiesCanRequestVisibleBehind(int stackId) { - return stackId == FULLSCREEN_WORKSPACE_STACK_ID || - stackId == ASSISTANT_STACK_ID; - } - - /** * Returns true if this stack may be scaled without resizing, and windows within may need * to be configured as such. */ diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 4a01fe68f0fd..074cdefc52b0 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -445,7 +445,6 @@ public final class ActivityThread { sb.append(", startedActivity=").append(activity.mStartedActivity); sb.append(", temporaryPause=").append(activity.mTemporaryPause); sb.append(", changingConfigurations=").append(activity.mChangingConfigurations); - sb.append(", visibleBehind=").append(activity.mVisibleBehind); sb.append("}"); } sb.append("}"); @@ -1384,16 +1383,6 @@ public final class ActivityThread { } @Override - public void scheduleCancelVisibleBehind(IBinder token) { - sendMessage(H.CANCEL_VISIBLE_BEHIND, token); - } - - @Override - public void scheduleBackgroundVisibleBehindChanged(IBinder token, boolean visible) { - sendMessage(H.BACKGROUND_VISIBLE_BEHIND_CHANGED, token, visible ? 1 : 0); - } - - @Override public void scheduleEnterAnimationComplete(IBinder token) { sendMessage(H.ENTER_ANIMATION_COMPLETE, token); } @@ -1509,8 +1498,6 @@ public final class ActivityThread { public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144; public static final int INSTALL_PROVIDER = 145; public static final int ON_NEW_ACTIVITY_OPTIONS = 146; - public static final int CANCEL_VISIBLE_BEHIND = 147; - public static final int BACKGROUND_VISIBLE_BEHIND_CHANGED = 148; public static final int ENTER_ANIMATION_COMPLETE = 149; public static final int START_BINDER_TRACKING = 150; public static final int STOP_BINDER_TRACKING_AND_DUMP = 151; @@ -1571,8 +1558,6 @@ public final class ActivityThread { case TRANSLUCENT_CONVERSION_COMPLETE: return "TRANSLUCENT_CONVERSION_COMPLETE"; case INSTALL_PROVIDER: return "INSTALL_PROVIDER"; case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS"; - case CANCEL_VISIBLE_BEHIND: return "CANCEL_VISIBLE_BEHIND"; - case BACKGROUND_VISIBLE_BEHIND_CHANGED: return "BACKGROUND_VISIBLE_BEHIND_CHANGED"; case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE"; case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED"; case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED"; @@ -1815,12 +1800,6 @@ public final class ActivityThread { Pair<IBinder, ActivityOptions> pair = (Pair<IBinder, ActivityOptions>) msg.obj; onNewActivityOptions(pair.first, pair.second); break; - case CANCEL_VISIBLE_BEHIND: - handleCancelVisibleBehind((IBinder) msg.obj); - break; - case BACKGROUND_VISIBLE_BEHIND_CHANGED: - handleOnBackgroundVisibleBehindChanged((IBinder) msg.obj, msg.arg1 > 0); - break; case ENTER_ANIMATION_COMPLETE: handleEnterAnimationComplete((IBinder) msg.obj); break; @@ -3070,36 +3049,6 @@ public final class ActivityThread { } } - public void handleCancelVisibleBehind(IBinder token) { - ActivityClientRecord r = mActivities.get(token); - if (r != null) { - mSomeActivitiesChanged = true; - final Activity activity = r.activity; - if (activity.mVisibleBehind) { - activity.mCalled = false; - activity.onVisibleBehindCanceled(); - // Tick, tick, tick. The activity has 500 msec to return or it will be destroyed. - if (!activity.mCalled) { - throw new SuperNotCalledException("Activity " + activity.getLocalClassName() + - " did not call through to super.onVisibleBehindCanceled()"); - } - activity.mVisibleBehind = false; - } - } - try { - ActivityManager.getService().backgroundResourcesReleased(token); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - public void handleOnBackgroundVisibleBehindChanged(IBinder token, boolean visible) { - ActivityClientRecord r = mActivities.get(token); - if (r != null) { - r.activity.onBackgroundVisibleBehindChanged(visible); - } - } - public void handleInstallProvider(ProviderInfo info) { final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites(); try { @@ -5189,11 +5138,7 @@ public final class ActivityThread { Slog.w(TAG, "Profiling failed on path " + profilerInfo.profileFile + " -- can the process access this path?"); } finally { - try { - profilerInfo.profileFd.close(); - } catch (IOException e) { - Slog.w(TAG, "Failure closing profile fd", e); - } + profilerInfo.closeFd(); } } else { switch (profileType) { diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 3ac026d70a6f..b27e224251d3 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -412,9 +412,6 @@ interface IActivityManager { void stopSystemLockTaskMode(); void finishVoiceTask(in IVoiceInteractionSession session); boolean isTopOfTask(in IBinder token); - boolean requestVisibleBehind(in IBinder token, boolean visible); - boolean isBackgroundVisibleBehind(in IBinder token); - void backgroundResourcesReleased(in IBinder token); void notifyLaunchTaskBehindComplete(in IBinder token); int startActivityFromRecents(int taskId, in Bundle options); void notifyEnterAnimationComplete(in IBinder token); @@ -636,4 +633,7 @@ interface IActivityManager { // side. If so, make sure they are using the correct transaction ids and arguments. // If a transaction which will also be used on the native side is being inserted, add it // alongside with other transactions of this kind at the top of this file. + + void setShowWhenLocked(in IBinder token, boolean showWhenLocked); + void setTurnScreenOn(in IBinder token, boolean turnScreenOn); } diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index b3521c06ae96..bd8c5c92a502 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -140,8 +140,6 @@ oneway interface IApplicationThread { void setProcessState(int state); void scheduleInstallProvider(in ProviderInfo provider); void updateTimePrefs(int timeFormatPreference); - void scheduleCancelVisibleBehind(IBinder token); - void scheduleBackgroundVisibleBehindChanged(IBinder token, boolean enabled); void scheduleEnterAnimationComplete(IBinder token); void notifyCleartextNetwork(in byte[] firstPacket); void startBinderTracking(); diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java index 16b21f15353e..c0381d69d681 100644 --- a/core/java/android/app/KeyguardManager.java +++ b/core/java/android/app/KeyguardManager.java @@ -461,6 +461,9 @@ public class KeyguardManager { * <p> * If the Keyguard is secure and the device is not in a trusted state, this will bring up the * UI so the user can enter their credentials. + * <p> + * If the value set for the {@link Activity} attr {@link android.R.attr#turnScreenOn} is true, + * the screen will turn on when the keyguard is dismissed. * * @param activity The activity requesting the dismissal. The activity must be either visible * by using {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} or must be in a state in diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java index f3fe6778375b..fad4798e3a3e 100644 --- a/core/java/android/app/ProfilerInfo.java +++ b/core/java/android/app/ProfilerInfo.java @@ -17,8 +17,11 @@ package android.app; import android.os.Parcel; -import android.os.Parcelable; import android.os.ParcelFileDescriptor; +import android.os.Parcelable; +import android.util.Slog; + +import java.io.IOException; /** * System private API for passing profiler settings. @@ -27,6 +30,8 @@ import android.os.ParcelFileDescriptor; */ public class ProfilerInfo implements Parcelable { + private static final String TAG = "ProfilerInfo"; + /* Name of profile output file. */ public final String profileFile; @@ -39,18 +44,50 @@ public class ProfilerInfo implements Parcelable { /* Automatically stop the profiler when the app goes idle. */ public final boolean autoStopProfiler; - /* Indicates whether to stream the profiling info to the out file continuously. */ + /* + * Indicates whether to stream the profiling info to the out file continuously. + */ public final boolean streamingOutput; + /** + * Denotes an agent (and its parameters) to attach for profiling. + */ + public final String agent; + public ProfilerInfo(String filename, ParcelFileDescriptor fd, int interval, boolean autoStop, - boolean streaming) { + boolean streaming, String agent) { profileFile = filename; profileFd = fd; samplingInterval = interval; autoStopProfiler = autoStop; streamingOutput = streaming; + this.agent = agent; + } + + public ProfilerInfo(ProfilerInfo in) { + profileFile = in.profileFile; + profileFd = in.profileFd; + samplingInterval = in.samplingInterval; + autoStopProfiler = in.autoStopProfiler; + streamingOutput = in.streamingOutput; + agent = in.agent; } + /** + * Close profileFd, if it is open. The field will be null after a call to this function. + */ + public void closeFd() { + if (profileFd != null) { + try { + profileFd.close(); + } catch (IOException e) { + Slog.w(TAG, "Failure closing profile fd", e); + } + profileFd = null; + } + } + + @Override public int describeContents() { if (profileFd != null) { return profileFd.describeContents(); @@ -59,6 +96,7 @@ public class ProfilerInfo implements Parcelable { } } + @Override public void writeToParcel(Parcel out, int flags) { out.writeString(profileFile); if (profileFd != null) { @@ -70,18 +108,21 @@ public class ProfilerInfo implements Parcelable { out.writeInt(samplingInterval); out.writeInt(autoStopProfiler ? 1 : 0); out.writeInt(streamingOutput ? 1 : 0); + out.writeString(agent); } public static final Parcelable.Creator<ProfilerInfo> CREATOR = new Parcelable.Creator<ProfilerInfo>() { - public ProfilerInfo createFromParcel(Parcel in) { - return new ProfilerInfo(in); - } + @Override + public ProfilerInfo createFromParcel(Parcel in) { + return new ProfilerInfo(in); + } - public ProfilerInfo[] newArray(int size) { - return new ProfilerInfo[size]; - } - }; + @Override + public ProfilerInfo[] newArray(int size) { + return new ProfilerInfo[size]; + } + }; private ProfilerInfo(Parcel in) { profileFile = in.readString(); @@ -89,5 +130,6 @@ public class ProfilerInfo implements Parcelable { samplingInterval = in.readInt(); autoStopProfiler = in.readInt() != 0; streamingOutput = in.readInt() != 0; + agent = in.readString(); } } diff --git a/core/java/android/app/Vr2dDisplayProperties.java b/core/java/android/app/Vr2dDisplayProperties.java index a608bb08836f..0eb2af361ae9 100644 --- a/core/java/android/app/Vr2dDisplayProperties.java +++ b/core/java/android/app/Vr2dDisplayProperties.java @@ -16,7 +16,6 @@ package android.app; -import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; @@ -27,19 +26,31 @@ import java.io.PrintWriter; * * @hide */ -public class Vr2dDisplayProperties implements Parcelable { +public final class Vr2dDisplayProperties implements Parcelable { - /** - * The actual width, height and dpi. - */ + public static final int FLAG_VIRTUAL_DISPLAY_ENABLED = 1; + + /** + * The actual width, height and dpi. + */ private final int mWidth; private final int mHeight; private final int mDpi; + // Flags describing the virtual display behavior. + private final int mAddedFlags; + private final int mRemovedFlags; + public Vr2dDisplayProperties(int width, int height, int dpi) { + this(width, height, dpi, 0, 0); + } + + private Vr2dDisplayProperties(int width, int height, int dpi, int flags, int removedFlags) { mWidth = width; mHeight = height; mDpi = dpi; + mAddedFlags = flags; + mRemovedFlags = removedFlags; } @Override @@ -52,11 +63,13 @@ public class Vr2dDisplayProperties implements Parcelable { @Override public String toString() { - return "Vr2dDisplayProperties{" + - "mWidth=" + mWidth + - ", mHeight=" + mHeight + - ", mDpi=" + mDpi + - "}"; + return "Vr2dDisplayProperties{" + + "mWidth=" + mWidth + + ", mHeight=" + mHeight + + ", mDpi=" + mDpi + + ", flags=" + toReadableFlags(mAddedFlags) + + ", removed_flags=" + toReadableFlags(mRemovedFlags) + + "}"; } @Override @@ -66,6 +79,8 @@ public class Vr2dDisplayProperties implements Parcelable { Vr2dDisplayProperties that = (Vr2dDisplayProperties) o; + if (getFlags() != that.getFlags()) return false; + if (getRemovedFlags() != that.getRemovedFlags()) return false; if (getWidth() != that.getWidth()) return false; if (getHeight() != that.getHeight()) return false; return getDpi() == that.getDpi(); @@ -81,6 +96,8 @@ public class Vr2dDisplayProperties implements Parcelable { dest.writeInt(mWidth); dest.writeInt(mHeight); dest.writeInt(mDpi); + dest.writeInt(mAddedFlags); + dest.writeInt(mRemovedFlags); } public static final Parcelable.Creator<Vr2dDisplayProperties> CREATOR @@ -100,13 +117,12 @@ public class Vr2dDisplayProperties implements Parcelable { mWidth = source.readInt(); mHeight = source.readInt(); mDpi = source.readInt(); + mAddedFlags = source.readInt(); + mRemovedFlags = source.readInt(); } public void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "Vr2dDisplayProperties:"); - pw.println(prefix + " width=" + mWidth); - pw.println(prefix + " height=" + mHeight); - pw.println(prefix + " dpi=" + mDpi); + pw.println(prefix + toString()); } public int getWidth() { @@ -120,4 +136,83 @@ public class Vr2dDisplayProperties implements Parcelable { public int getDpi() { return mDpi; } + + public int getFlags() { + return mAddedFlags; + } + + public int getRemovedFlags() { + return mRemovedFlags; + } + + private static String toReadableFlags(int flags) { + String retval = "{"; + if ((flags & FLAG_VIRTUAL_DISPLAY_ENABLED) == FLAG_VIRTUAL_DISPLAY_ENABLED) { + retval += "enabled"; + } + return retval + "}"; + } + + /** + * Convenience class for creating Vr2dDisplayProperties. + */ + public static class Builder { + private int mAddedFlags = 0; + private int mRemovedFlags = 0; + + // Negative values are translated as an "ignore" to VrManagerService. + private int mWidth = -1; + private int mHeight = -1; + private int mDpi = -1; + + public Builder() { + } + + /** + * Sets the dimensions to use for the virtual display. + */ + public Builder setDimensions(int width, int height, int dpi) { + mWidth = width; + mHeight = height; + mDpi = dpi; + return this; + } + + /** + * Toggles the virtual display functionality for 2D activities in VR. + */ + public Builder setEnabled(boolean enabled) { + if (enabled) { + addFlags(FLAG_VIRTUAL_DISPLAY_ENABLED); + } else { + removeFlags(FLAG_VIRTUAL_DISPLAY_ENABLED); + } + return this; + } + + /** + * Adds property flags. + */ + public Builder addFlags(int flags) { + mAddedFlags |= flags; + mRemovedFlags &= ~flags; + return this; + } + + /** + * Removes property flags. + */ + public Builder removeFlags(int flags) { + mRemovedFlags |= flags; + mAddedFlags &= ~flags; + return this; + } + + /** + * Builds the Vr2dDisplayProperty instance. + */ + public Vr2dDisplayProperties build() { + return new Vr2dDisplayProperties(mWidth, mHeight, mDpi, mAddedFlags, mRemovedFlags); + } + } } diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index dfcbfc4f8e09..0b8b689ce4e6 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -22,9 +22,9 @@ import android.annotation.Nullable; import android.annotation.RawRes; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.SystemService; -import android.annotation.SdkConstant.SdkConstantType; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -74,7 +74,6 @@ import java.io.InputStream; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -369,18 +368,17 @@ public class WallpaperManager { } WallpaperColors getWallpaperColors(int which) { - synchronized (this) { - if (which != FLAG_LOCK && which != FLAG_SYSTEM) - throw new IllegalArgumentException( - "which should be either FLAG_LOCK or FLAG_SYSTEM"); + if (which != FLAG_LOCK && which != FLAG_SYSTEM) { + throw new IllegalArgumentException( + "Must request colors for exactly one kind of wallpaper"); + } - try { - return mService.getWallpaperColors(which); - } catch (RemoteException e) { - // Can't get colors, connection lost. - } - return null; + try { + return mService.getWallpaperColors(which); + } catch (RemoteException e) { + // Can't get colors, connection lost. } + return null; } public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault, diff --git a/core/java/android/app/timezone/RulesUpdaterContract.java b/core/java/android/app/timezone/RulesUpdaterContract.java index 4e7781866aae..07b2f33ef5e0 100644 --- a/core/java/android/app/timezone/RulesUpdaterContract.java +++ b/core/java/android/app/timezone/RulesUpdaterContract.java @@ -19,6 +19,7 @@ package android.app.timezone; import android.content.Context; import android.content.Intent; import android.os.ParcelFileDescriptor; +import android.os.UserHandle; /** * Constants related to the contract between the Android system and the privileged time zone updater @@ -82,6 +83,9 @@ public final class RulesUpdaterContract { byte[] checkTokenBytes) { Intent intent = createUpdaterIntent(updaterAppPackageName); intent.putExtra(EXTRA_CHECK_TOKEN, checkTokenBytes); - context.sendBroadcast(intent, RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION); + context.sendBroadcastAsUser( + intent, + UserHandle.of(UserHandle.myUserId()), + RulesUpdaterContract.UPDATE_TIME_ZONE_RULES_PERMISSION); } } diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index 5b8d81d81efb..c84643fc46b3 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -200,6 +200,37 @@ public final class BluetoothHeadset implements BluetoothProfile { public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID"; /** + * A vendor-specific AT command + * @hide + */ + public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL"; + + /** + * A vendor-specific AT command + * @hide + */ + public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV"; + + /** + * Battery level indicator associated with + * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV} + * @hide + */ + public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1; + + /** + * A vendor-specific AT command + * @hide + */ + public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT"; + + /** + * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT} + * @hide + */ + public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY"; + + /** * Headset state when SCO audio is not connected. * This state can be one of * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of @@ -229,31 +260,31 @@ public final class BluetoothHeadset implements BluetoothProfile { * <ul> * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which * is supported by the headset ( as indicated by AT+BIND command in the SLC - * sequence).or whose value is changed (indicated by AT+BIEV command) </li> - * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE}- The updated value of headset indicator. </li> - * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> + * sequence) or whose value is changed (indicated by AT+BIEV command) </li> + * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li> + * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li> * </ul> - * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators are - * given an assigned number. Below shows the assigned number of Indicator added so far - * - Enhanced Safety - 1 - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. + * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators + * are given an assigned number. Below shows the assigned number of Indicator added so far + * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled + * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive. * @hide */ public static final String ACTION_HF_INDICATORS_VALUE_CHANGED = "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED"; /** - * A String extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} - * intents that contains the UUID of the headset indicator (as defined by Bluetooth SIG) - * that is being sent. + * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} + * intents that contains the assigned number of the headset indicator as defined by + * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7 * @hide */ public static final String EXTRA_HF_INDICATORS_IND_ID = "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID"; /** - * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} + * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED} * intents that contains the value of the Headset indicator that is being sent. * @hide */ diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 9a014766c206..b75c39c0d350 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -441,6 +441,22 @@ public class ActivityInfo extends ComponentInfo * @hide */ public static final int FLAG_SUPPORTS_PICTURE_IN_PICTURE = 0x400000; + + /** + * Bit in {@link #flags} indicating if the activity should be shown when locked. + * See {@link android.R.attr#showWhenLocked} + * @hide + */ + public static final int FLAG_SHOW_WHEN_LOCKED = 0x800000; + + /** + * Bit in {@link #flags} indicating if the screen should turn on when starting the activity. + * See {@link android.R.attr#turnScreenOn} + * @hide + */ + public static final int FLAG_TURN_SCREEN_ON = 0x1000000; + + /** * @hide Bit in {@link #flags}: If set, this component will only be seen * by the system user. Only works with broadcast receivers. Set from the @@ -462,6 +478,7 @@ public class ActivityInfo extends ComponentInfo * thrown. Set from the {@link android.R.attr#allowEmbedded} attribute. */ public static final int FLAG_ALLOW_EMBEDDED = 0x80000000; + /** * Options that have been set in the activity declaration in the * manifest. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 1b88ce7c3a04..e4f2fc1c5035 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -4292,6 +4292,15 @@ public class PackageParser { a.info.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode, ActivityInfo.COLOR_MODE_DEFAULT); + + if (sa.getBoolean(R.styleable.AndroidManifestActivity_showWhenLocked, false)) { + a.info.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED; + } + + if (sa.getBoolean(R.styleable.AndroidManifestActivity_turnScreenOn, false)) { + a.info.flags |= ActivityInfo.FLAG_TURN_SCREEN_ON; + } + } else { a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE; a.info.configChanges = 0; diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index b6eaa5c7cd10..f697b89f6fdd 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -431,6 +431,25 @@ public class RadioManager { public int getType() { return mType; } + + /** + * Checks if the band is either AM or AM_HD. + * + * @return {@code true}, if band is AM or AM_HD. + */ + public boolean isAmBand() { + return mType == BAND_AM || mType == BAND_AM_HD; + } + + /** + * Checks if the band is either FM or FM_HD. + * + * @return {@code true}, if band is FM or FM_HD. + */ + public boolean isFmBand() { + return mType == BAND_FM || mType == BAND_FM_HD; + } + /** Lower band limit expressed in units according to band type. * Currently all defined band types express channels as frequency in kHz * @return the lower band limit. diff --git a/core/java/android/os/MemoryFile.java b/core/java/android/os/MemoryFile.java index 6cec55a4a09e..9294c449e4d6 100644 --- a/core/java/android/os/MemoryFile.java +++ b/core/java/android/os/MemoryFile.java @@ -16,68 +16,50 @@ package android.os; -import android.util.Log; +import android.system.ErrnoException; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; /** - * MemoryFile is a wrapper for the Linux ashmem driver. - * MemoryFiles are backed by shared memory, which can be optionally - * set to be purgeable. + * MemoryFile is a wrapper for {@link SharedMemory} which can optionally be set to purgeable. + * + * Applications should generally prefer to use {@link SharedMemory} which offers more flexible + * access & control over the shared memory region than MemoryFile does. + * * Purgeable files may have their contents reclaimed by the kernel * in low memory conditions (only if allowPurging is set to true). * After a file is purged, attempts to read or write the file will * cause an IOException to be thrown. */ -public class MemoryFile -{ +public class MemoryFile { private static String TAG = "MemoryFile"; - // mmap(2) protection flags from <sys/mman.h> - private static final int PROT_READ = 0x1; - private static final int PROT_WRITE = 0x2; - - private static native FileDescriptor native_open(String name, int length) throws IOException; - // returns memory address for ashmem region - private static native long native_mmap(FileDescriptor fd, int length, int mode) - throws IOException; - private static native void native_munmap(long addr, int length) throws IOException; - private static native void native_close(FileDescriptor fd); - private static native int native_read(FileDescriptor fd, long address, byte[] buffer, - int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; - private static native void native_write(FileDescriptor fd, long address, byte[] buffer, - int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException; - private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException; + // Returns 'true' if purged, 'false' otherwise + private static native boolean native_pin(FileDescriptor fd, boolean pin) throws IOException; private static native int native_get_size(FileDescriptor fd) throws IOException; - private FileDescriptor mFD; // ashmem file descriptor - private long mAddress; // address of ashmem memory - private int mLength; // total length of our ashmem region + private SharedMemory mSharedMemory; + private ByteBuffer mMapping; private boolean mAllowPurging = false; // true if our ashmem region is unpinned /** * Allocates a new ashmem region. The region is initially not purgable. * * @param name optional name for the file (can be null). - * @param length of the memory file in bytes, must be non-negative. + * @param length of the memory file in bytes, must be positive. * @throws IOException if the memory file could not be created. */ public MemoryFile(String name, int length) throws IOException { - mLength = length; - if (length >= 0) { - mFD = native_open(name, length); - } else { - throw new IOException("Invalid length: " + length); - } - - if (length > 0) { - mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE); - } else { - mAddress = 0; + try { + mSharedMemory = SharedMemory.create(name, length); + mMapping = mSharedMemory.mapReadWrite(); + } catch (ErrnoException ex) { + ex.rethrowAsIOException(); } } @@ -87,9 +69,7 @@ public class MemoryFile */ public void close() { deactivate(); - if (!isClosed()) { - native_close(mFD); - } + mSharedMemory.close(); } /** @@ -100,35 +80,30 @@ public class MemoryFile * @hide */ void deactivate() { - if (!isDeactivated()) { - try { - native_munmap(mAddress, mLength); - mAddress = 0; - } catch (IOException ex) { - Log.e(TAG, ex.toString()); - } + if (mMapping != null) { + SharedMemory.unmap(mMapping); + mMapping = null; } } - /** - * Checks whether the memory file has been deactivated. - */ - private boolean isDeactivated() { - return mAddress == 0; + private void checkActive() throws IOException { + if (mMapping == null) { + throw new IOException("MemoryFile has been deactivated"); + } } - /** - * Checks whether the memory file has been closed. - */ - private boolean isClosed() { - return !mFD.valid(); + private void beginAccess() throws IOException { + checkActive(); + if (mAllowPurging) { + if (native_pin(mSharedMemory.getFileDescriptor(), true)) { + throw new IOException("MemoryFile has been purged"); + } + } } - @Override - protected void finalize() { - if (!isClosed()) { - Log.e(TAG, "MemoryFile.finalize() called while ashmem still open"); - close(); + private void endAccess() throws IOException { + if (mAllowPurging) { + native_pin(mSharedMemory.getFileDescriptor(), false); } } @@ -138,14 +113,19 @@ public class MemoryFile * @return file length. */ public int length() { - return mLength; + return mSharedMemory.getSize(); } /** * Is memory file purging enabled? * * @return true if the file may be purged. + * + * @deprecated Purgable is considered generally fragile and hard to use safely. Applications + * are recommend to instead use {@link android.content.ComponentCallbacks2#onTrimMemory(int)} + * to react to memory events and release shared memory regions as appropriate. */ + @Deprecated public boolean isPurgingAllowed() { return mAllowPurging; } @@ -156,11 +136,16 @@ public class MemoryFile * @param allowPurging true if the operating system can purge the contents * of the file in low memory situations * @return previous value of allowPurging + * + * @deprecated Purgable is considered generally fragile and hard to use safely. Applications + * are recommend to instead use {@link android.content.ComponentCallbacks2#onTrimMemory(int)} + * to react to memory events and release shared memory regions as appropriate. */ + @Deprecated synchronized public boolean allowPurging(boolean allowPurging) throws IOException { boolean oldValue = mAllowPurging; if (oldValue != allowPurging) { - native_pin(mFD, !allowPurging); + native_pin(mSharedMemory.getFileDescriptor(), !allowPurging); mAllowPurging = allowPurging; } return oldValue; @@ -197,16 +182,14 @@ public class MemoryFile */ public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count) throws IOException { - if (isDeactivated()) { - throw new IOException("Can't read from deactivated memory file."); + beginAccess(); + try { + mMapping.position(srcOffset); + mMapping.get(buffer, destOffset, count); + } finally { + endAccess(); } - if (destOffset < 0 || destOffset > buffer.length || count < 0 - || count > buffer.length - destOffset - || srcOffset < 0 || srcOffset > mLength - || count > mLength - srcOffset) { - throw new IndexOutOfBoundsException(); - } - return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); + return count; } /** @@ -221,16 +204,13 @@ public class MemoryFile */ public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count) throws IOException { - if (isDeactivated()) { - throw new IOException("Can't write to deactivated memory file."); - } - if (srcOffset < 0 || srcOffset > buffer.length || count < 0 - || count > buffer.length - srcOffset - || destOffset < 0 || destOffset > mLength - || count > mLength - destOffset) { - throw new IndexOutOfBoundsException(); + beginAccess(); + try { + mMapping.position(destOffset); + mMapping.put(buffer, srcOffset, count); + } finally { + endAccess(); } - native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging); } /** @@ -239,11 +219,9 @@ public class MemoryFile * The returned file descriptor is not duplicated. * * @throws IOException If the memory file has been closed. - * - * @hide */ public FileDescriptor getFileDescriptor() throws IOException { - return mFD; + return mSharedMemory.getFileDescriptor(); } /** @@ -266,10 +244,10 @@ public class MemoryFile @Override public int available() throws IOException { - if (mOffset >= mLength) { + if (mOffset >= mSharedMemory.getSize()) { return 0; } - return mLength - mOffset; + return mSharedMemory.getSize() - mOffset; } @Override @@ -319,8 +297,8 @@ public class MemoryFile @Override public long skip(long n) throws IOException { - if (mOffset + n > mLength) { - n = mLength - mOffset; + if (mOffset + n > mSharedMemory.getSize()) { + n = mSharedMemory.getSize() - mOffset; } mOffset += n; return n; diff --git a/core/java/android/os/SharedMemory.aidl b/core/java/android/os/SharedMemory.aidl new file mode 100644 index 000000000000..b7c695e5f15c --- /dev/null +++ b/core/java/android/os/SharedMemory.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android/os/SharedMemory.aidl +** +** Copyright 2017, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.os; + +parcelable SharedMemory;
\ No newline at end of file diff --git a/core/java/android/os/SharedMemory.java b/core/java/android/os/SharedMemory.java new file mode 100644 index 000000000000..712bbaa101aa --- /dev/null +++ b/core/java/android/os/SharedMemory.java @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2017 The Android Open 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.annotation.NonNull; +import android.annotation.Nullable; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; + +import dalvik.system.VMRuntime; + +import java.io.Closeable; +import java.io.FileDescriptor; +import java.nio.ByteBuffer; +import java.nio.DirectByteBuffer; + +import sun.misc.Cleaner; + +/** + * SharedMemory enables the creation, mapping, and protection control over anonymous shared memory. + */ +public final class SharedMemory implements Parcelable, Closeable { + + private final FileDescriptor mFileDescriptor; + private final int mSize; + private final MemoryRegistration mMemoryRegistration; + private Cleaner mCleaner; + + private SharedMemory(FileDescriptor fd) { + // This constructor is only used internally so it should be impossible to hit any of the + // exceptions unless something goes horribly wrong. + if (fd == null) { + throw new IllegalArgumentException( + "Unable to create SharedMemory from a null FileDescriptor"); + } + if (!fd.valid()) { + throw new IllegalArgumentException( + "Unable to create SharedMemory from closed FileDescriptor"); + } + mFileDescriptor = fd; + mSize = nGetSize(mFileDescriptor); + if (mSize <= 0) { + throw new IllegalArgumentException("FileDescriptor is not a valid ashmem fd"); + } + + mMemoryRegistration = new MemoryRegistration(mSize); + mCleaner = Cleaner.create(this, new Closer(mFileDescriptor, mMemoryRegistration)); + } + + /** + * Creates an anonymous SharedMemory instance with the provided debug name and size. The name + * is only used for debugging purposes and can help identify what the shared memory is used + * for when inspecting memory maps for the processes that have mapped this SharedMemory + * instance. + * + * @param name The debug name to use for this SharedMemory instance. This can be null, however + * a debug name is recommended to help identify memory usage when using tools + * such as lsof or examining /proc/[pid]/maps + * @param size The size of the shared memory to create. Must be greater than 0. + * @return A SharedMemory instance of the requested size + * @throws ErrnoException if the requested allocation fails. + */ + public static @NonNull SharedMemory create(@Nullable String name, int size) + throws ErrnoException { + if (size <= 0) { + throw new IllegalArgumentException("Size must be greater than zero"); + } + return new SharedMemory(nCreate(name, size)); + } + + private void checkOpen() { + if (!mFileDescriptor.valid()) { + throw new IllegalStateException("SharedMemory is closed"); + } + } + + private static final int PROT_MASK = OsConstants.PROT_READ | OsConstants.PROT_WRITE + | OsConstants.PROT_EXEC | OsConstants.PROT_NONE; + + private static void validateProt(int prot) { + if ((prot & ~PROT_MASK) != 0) { + throw new IllegalArgumentException("Invalid prot value"); + } + } + + /** + * Sets the protection on the shared memory to the combination specified in prot, which + * is either a bitwise-or'd combination of {@link android.system.OsConstants#PROT_READ}, + * {@link android.system.OsConstants#PROT_WRITE}, {@link android.system.OsConstants#PROT_EXEC} + * from {@link android.system.OsConstants}, or {@link android.system.OsConstants#PROT_NONE}, + * to remove all further access. + * + * Note that protection can only ever be removed, not added. By default shared memory + * is created with protection set to PROT_READ | PROT_WRITE | PROT_EXEC. The protection + * passed here also only applies to any mappings created after calling this method. Existing + * mmaps of the shared memory retain whatever protection they had when they were created. + * + * A common usage of this is to share a read-only copy of the data with something else. To do + * that first create the read/write mapping with PROT_READ | PROT_WRITE, + * then call setProtect(PROT_READ) to remove write capability, then send the SharedMemory + * to another process. That process will only be able to mmap with PROT_READ. + * + * @param prot Any bitwise-or'ed combination of + * {@link android.system.OsConstants#PROT_READ}, + * {@link android.system.OsConstants#PROT_WRITE}, and + * {@link android.system.OsConstants#PROT_EXEC}; or + * {@link android.system.OsConstants#PROT_NONE} + * @return Whether or not the requested protection was applied. Returns true on success, + * false if the requested protection was broader than the existing protection. + */ + public boolean setProtect(int prot) { + checkOpen(); + validateProt(prot); + int errno = nSetProt(mFileDescriptor, prot); + return errno == 0; + } + + /** + * Returns the backing {@link FileDescriptor} for this SharedMemory object. The SharedMemory + * instance retains ownership of the FileDescriptor. + * + * This FileDescriptor is interoperable with the ASharedMemory NDK APIs. + * + * @return Returns the FileDescriptor associated with this object. + */ + public @NonNull FileDescriptor getFileDescriptor() { + return mFileDescriptor; + } + + /** + * Returns the backing native fd int for this SharedMemory object. The SharedMemory + * instance retains ownership of the fd. + * + * This fd is interoperable with the ASharedMemory NDK APIs. + * + * @return Returns the native fd associated with this object, or -1 if it is already closed. + */ + public int getFd() { + return mFileDescriptor.getInt$(); + } + + /** + * @return The size of the SharedMemory region. + */ + public int getSize() { + checkOpen(); + return mSize; + } + + /** + * Creates a read/write mapping of the entire shared memory region. This requires the the + * protection level of the shared memory is at least PROT_READ|PROT_WRITE or the map will fail. + * + * Use {@link #map(int, int, int)} to have more control over the mapping if desired. + * This is equivalent to map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, getSize()) + * + * @return A ByteBuffer mapping + * @throws ErrnoException if the mmap call failed. + */ + public @NonNull ByteBuffer mapReadWrite() throws ErrnoException { + return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize); + } + + /** + * Creates a read-only mapping of the entire shared memory region. This requires the the + * protection level of the shared memory is at least PROT_READ or the map will fail. + * + * Use {@link #map(int, int, int)} to have more control over the mapping if desired. + * This is equivalent to map(OsConstants.PROT_READ, 0, getSize()) + * + * @return A ByteBuffer mapping + * @throws ErrnoException if the mmap call failed. + */ + public @NonNull ByteBuffer mapReadOnly() throws ErrnoException { + return map(OsConstants.PROT_READ, 0, mSize); + } + + /** + * Creates an mmap of the SharedMemory with the specified prot, offset, and length. + * + * @param prot A bitwise-or'd combination of PROT_READ, PROT_WRITE, PROT_EXEC, or PROT_NONE. + * @param offset The offset into the shared memory to begin mapping + * @param length The length of the region to map + * @return A ByteBuffer mapping. + * @throws ErrnoException if the mmap call failed. + */ + public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException { + checkOpen(); + validateProt(prot); + if (offset < 0) { + throw new IllegalArgumentException("Offset must be > 0"); + } + if (length <= 0) { + throw new IllegalArgumentException("Length must be > 0"); + } + if (offset + length > mSize) { + throw new IllegalArgumentException("offset + length must not exceed getSize()"); + } + long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset); + boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0; + Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire()); + return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly); + } + + /** + * Unmaps a buffer previously returned by {@link #map(int, int, int)} + * @param buffer The buffer to unmap + */ + public static void unmap(@NonNull ByteBuffer buffer) { + if (buffer instanceof DirectByteBuffer) { + Cleaner cleaner = ((DirectByteBuffer) buffer).cleaner(); + if (cleaner != null) { + cleaner.clean(); + } + } else { + throw new IllegalArgumentException( + "ByteBuffer wasn't created by #map(int, int, int); can't unmap"); + } + } + + /** + * Close the backing {@link FileDescriptor} of this SharedMemory instance. Note that all + * open mappings of the shared memory will remain valid and may continue to be used. The + * shared memory will not be freed until all file descriptor handles are closed and all + * memory mappings are unmapped. + */ + @Override + public void close() { + if (mCleaner != null) { + mCleaner.clean(); + mCleaner = null; + } + } + + @Override + public int describeContents() { + return CONTENTS_FILE_DESCRIPTOR; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + checkOpen(); + dest.writeFileDescriptor(mFileDescriptor); + } + + public static final Parcelable.Creator<SharedMemory> CREATOR = + new Parcelable.Creator<SharedMemory>() { + @Override + public SharedMemory createFromParcel(Parcel source) { + FileDescriptor descriptor = source.readRawFileDescriptor(); + return new SharedMemory(descriptor); + } + + @Override + public SharedMemory[] newArray(int size) { + return new SharedMemory[size]; + } + }; + + /** + * Cleaner that closes the FD + */ + private static final class Closer implements Runnable { + private FileDescriptor mFd; + private MemoryRegistration mMemoryReference; + + private Closer(FileDescriptor fd, MemoryRegistration memoryReference) { + mFd = fd; + mMemoryReference = memoryReference; + } + + @Override + public void run() { + try { + Os.close(mFd); + } catch (ErrnoException e) { /* swallow error */ } + mMemoryReference.release(); + mMemoryReference = null; + } + } + + /** + * Cleaner that munmap regions + */ + private static final class Unmapper implements Runnable { + private long mAddress; + private int mSize; + private MemoryRegistration mMemoryReference; + + private Unmapper(long address, int size, MemoryRegistration memoryReference) { + mAddress = address; + mSize = size; + mMemoryReference = memoryReference; + } + + @Override + public void run() { + try { + Os.munmap(mAddress, mSize); + } catch (ErrnoException e) { /* swallow exception */ } + mMemoryReference.release(); + mMemoryReference = null; + } + } + + /** + * Helper class that ensures that the native allocation pressure against the VM heap stays + * active until the FD is closed as well as all mappings from that FD are closed. + */ + private static final class MemoryRegistration { + private int mSize; + private int mReferenceCount; + + private MemoryRegistration(int size) { + mSize = size; + mReferenceCount = 1; + VMRuntime.getRuntime().registerNativeAllocation(mSize); + } + + public synchronized MemoryRegistration acquire() { + mReferenceCount++; + return this; + } + + public synchronized void release() { + mReferenceCount--; + if (mReferenceCount == 0) { + VMRuntime.getRuntime().registerNativeFree(mSize); + } + } + } + + private static native FileDescriptor nCreate(String name, int size) throws ErrnoException; + private static native int nGetSize(FileDescriptor fd); + private static native int nSetProt(FileDescriptor fd, int prot); +} diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index 2179bd45e303..4306bc47f2b5 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -934,6 +934,7 @@ public class Preference implements Comparable<Preference> { * @param singleLineTitle set {@code true} if the title should be constrained to one line */ public void setSingleLineTitle(boolean singleLineTitle) { + mHasSingleLineTitleAttr = true; mSingleLineTitle = singleLineTitle; notifyChanged(); } diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index 951aa8df9ea3..8691136dfedc 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -30,8 +30,9 @@ import java.net.UnknownHostException; /** * API for sending log output. * - * <p>Generally, use the Log.v() Log.d() Log.i() Log.w() and Log.e() - * methods. + * <p>Generally, you should use the {@link #v Log.v()}, {@link #d Log.d()}, + * {@link #i Log.i()}, {@link #w Log.w()}, and {@link #e Log.e()} methods to write logs. + * You can then <a href="{@docRoot}studio/debug/am-logcat.html">view the logs in logcat</a>. * * <p>The order in terms of verbosity, from least to most is * ERROR, WARN, INFO, DEBUG, VERBOSE. Verbose should never be compiled diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 371277622740..63fe300a7927 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -724,6 +724,8 @@ import java.util.function.Predicate; * @attr ref android.R.styleable#View_nextFocusUp * @attr ref android.R.styleable#View_onClick * @attr ref android.R.styleable#View_padding + * @attr ref android.R.styleable#View_paddingHorizontal + * @attr ref android.R.styleable#View_paddingVertical * @attr ref android.R.styleable#View_paddingBottom * @attr ref android.R.styleable#View_paddingLeft * @attr ref android.R.styleable#View_paddingRight diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index cb92a4ca3ad4..ecdfa3fc3336 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -7609,6 +7609,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * See * {@link android.R.styleable#ViewGroup_MarginLayout ViewGroup Margin Layout Attributes} * for a list of all child view attributes that this class supports. + * + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_margin + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginHorizontal + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginVertical + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginLeft + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginRight + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart + * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd */ public static class MarginLayoutParams extends ViewGroup.LayoutParams { /** diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 4c0a1902d6a4..3fd459958ed4 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; @@ -27,6 +28,7 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.Region; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -169,6 +171,18 @@ public interface WindowManager extends ViewManager { */ public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId); + /** + * Return the touch region for the current IME window, or an empty region if there is none. + * + * @return The region of the IME that is accepting touch inputs, or null if there is no IME, no + * region or there was an error. + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) + public Region getCurrentImeTouchRegion(); + public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable { /** * X position for this window. With the default gravity it is ignored. @@ -941,7 +955,11 @@ public interface WindowManager extends ViewManager { * {@link #FLAG_DISMISS_KEYGUARD} to automatically fully dismisss * non-secure keyguards. This flag only applies to the top-most * full-screen window. + * @deprecated Use {@link android.R.attr#showWhenLocked} or + * {@link android.app.Activity#setShowWhenLocked(boolean)} instead to prevent an + * unintentional double life-cycle event. */ + @Deprecated public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000; /** Window flag: ask that the system wallpaper be shown behind @@ -966,7 +984,12 @@ public interface WindowManager extends ViewManager { /** Window flag: when set as a window is being added or made * visible, once the window has been shown then the system will * poke the power manager's user activity (as if the user had woken - * up the device) to turn the screen on. */ + * up the device) to turn the screen on. + * @deprecated Use {@link android.R.attr#turnScreenOn} or + * {@link android.app.Activity#setTurnScreenOn(boolean)} instead to prevent an + * unintentional double life-cycle event. + */ + @Deprecated public static final int FLAG_TURN_SCREEN_ON = 0x00200000; /** Window flag: when set the window will cause the keyguard to diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index c1b8f04a6865..a8722f101ef4 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -18,6 +18,7 @@ package android.view; import android.annotation.NonNull; import android.content.Context; +import android.graphics.Region; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -145,4 +146,13 @@ public final class WindowManagerImpl implements WindowManager { public Display getDefaultDisplay() { return mContext.getDisplay(); } + + @Override + public Region getCurrentImeTouchRegion() { + try { + return WindowManagerGlobal.getWindowManagerService().getCurrentImeTouchRegion(); + } catch (RemoteException e) { + } + return null; + } } diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 5b04f41c7ee2..e1e8317d8ccb 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -741,7 +741,8 @@ public final class AutofillManager { } /** - * Returns {@code true} if Autofill is supported for this user. + * Returns {@code true} if autofill is supported by the current device and + * is supported for this user. * * <p>Autofill is typically supported, but it could be unsupported in cases like: * <ol> diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index ec2b302d35ca..5fed925dd956 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -1638,6 +1638,17 @@ public class WebView extends AbsoluteLayout } /** + * Sets the list of domains that are exempt from SafeBrowsing checks. The list is + * global for all the WebViews. + * TODO: Add documentation for the format of the urls. + * + * @param urls the list of URLs + */ + public static void setSafeBrowsingWhiteList(@Nullable String[] urls) { + getFactory().getStatics().setSafeBrowsingWhiteList(urls); + } + + /** * Gets the WebBackForwardList for this WebView. This contains the * back/forward list for use in querying each item in the history stack. * This is a copy of the private WebBackForwardList so it contains only a diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java index 5cf48b585b16..7c938ae5bb70 100644 --- a/core/java/android/webkit/WebViewFactoryProvider.java +++ b/core/java/android/webkit/WebViewFactoryProvider.java @@ -86,6 +86,12 @@ public interface WebViewFactoryProvider { * {@link android.webkit.WebView#shutdownSafeBrowsing()} */ void shutdownSafeBrowsing(); + + /** + * Implement the API method + * {@link android.webkit.WebView#setSafeBrowsingWhiteList(String[])} + */ + void setSafeBrowsingWhiteList(String[] urls); } Statics getStatics(); diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 8fe9100d2011..544afd993b37 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -3103,12 +3103,29 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } /** + * Check if Setup or Post-Setup update is completed on TV + * @return true if completed + */ + private boolean isTvUserSetupComplete() { + boolean isTvSetupComplete = Settings.Secure.getInt(getContext().getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0) != 0; + isTvSetupComplete &= Settings.Secure.getInt(getContext().getContentResolver(), + Settings.Secure.TV_USER_SETUP_COMPLETE, 0) != 0; + return isTvSetupComplete; + } + + /** * Helper method for adding launch-search to most applications. Opens the * search window using default settings. * * @return true if search window opened */ private boolean launchDefaultSearch(KeyEvent event) { + if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK) + && !isTvUserSetupComplete()) { + // If we are in Setup or Post-Setup update mode on TV, consume the search key + return false; + } boolean result; final Callback cb = getCallback(); if (cb == null || isDestroyed()) { diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 51851690c08d..c4533c36d0f2 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -90,6 +90,7 @@ cc_library_shared { "android_os_Parcel.cpp", "android_os_SELinux.cpp", "android_os_seccomp.cpp", + "android_os_SharedMemory.cpp", "android_os_SystemClock.cpp", "android_os_SystemProperties.cpp", "android_os_Trace.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 92bc16055931..9d9828ead705 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -167,6 +167,7 @@ extern int register_android_os_Trace(JNIEnv* env); extern int register_android_os_FileObserver(JNIEnv *env); extern int register_android_os_UEventObserver(JNIEnv* env); extern int register_android_os_MemoryFile(JNIEnv* env); +extern int register_android_os_SharedMemory(JNIEnv* env); extern int register_android_net_LocalSocketImpl(JNIEnv* env); extern int register_android_net_NetworkUtils(JNIEnv* env); extern int register_android_net_TrafficStats(JNIEnv* env); @@ -1392,6 +1393,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_net_NetworkUtils), REG_JNI(register_android_net_TrafficStats), REG_JNI(register_android_os_MemoryFile), + REG_JNI(register_android_os_SharedMemory), REG_JNI(register_com_android_internal_os_PathClassLoaderFactory), REG_JNI(register_com_android_internal_os_Zygote), REG_JNI(register_com_android_internal_util_VirtualRefBasePtr), diff --git a/core/jni/android_os_MemoryFile.cpp b/core/jni/android_os_MemoryFile.cpp index c198a733103d..fdc9f9dc7464 100644 --- a/core/jni/android_os_MemoryFile.cpp +++ b/core/jni/android_os_MemoryFile.cpp @@ -26,95 +26,14 @@ namespace android { -static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring name, jint length) -{ - const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL); - - int result = ashmem_create_region(namestr, length); - - if (name) - env->ReleaseStringUTFChars(name, namestr); - - if (result < 0) { - jniThrowException(env, "java/io/IOException", "ashmem_create_region failed"); - return NULL; - } - - return jniCreateFileDescriptor(env, result); -} - -static jlong android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor, - jint length, jint prot) -{ - int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - void* result = mmap(NULL, length, prot, MAP_SHARED, fd, 0); - if (result == MAP_FAILED) { - jniThrowException(env, "java/io/IOException", "mmap failed"); - } - return reinterpret_cast<jlong>(result); -} - -static void android_os_MemoryFile_munmap(JNIEnv* env, jobject clazz, jlong addr, jint length) -{ - int result = munmap(reinterpret_cast<void *>(addr), length); - if (result < 0) - jniThrowException(env, "java/io/IOException", "munmap failed"); -} - -static void android_os_MemoryFile_close(JNIEnv* env, jobject clazz, jobject fileDescriptor) -{ - int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - if (fd >= 0) { - jniSetFileDescriptorOfFD(env, fileDescriptor, -1); - close(fd); - } -} - -static jint android_os_MemoryFile_read(JNIEnv* env, jobject clazz, - jobject fileDescriptor, jlong address, jbyteArray buffer, jint srcOffset, jint destOffset, - jint count, jboolean unpinned) -{ - int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) { - ashmem_unpin_region(fd, 0, 0); - jniThrowException(env, "java/io/IOException", "ashmem region was purged"); - return -1; - } - - env->SetByteArrayRegion(buffer, destOffset, count, (const jbyte *)address + srcOffset); - - if (unpinned) { - ashmem_unpin_region(fd, 0, 0); - } - return count; -} - -static jint android_os_MemoryFile_write(JNIEnv* env, jobject clazz, - jobject fileDescriptor, jlong address, jbyteArray buffer, jint srcOffset, jint destOffset, - jint count, jboolean unpinned) -{ - int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); - if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) { - ashmem_unpin_region(fd, 0, 0); - jniThrowException(env, "java/io/IOException", "ashmem region was purged"); - return -1; - } - - env->GetByteArrayRegion(buffer, srcOffset, count, (jbyte *)address + destOffset); - - if (unpinned) { - ashmem_unpin_region(fd, 0, 0); - } - return count; -} - -static void android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDescriptor, jboolean pin) -{ +static jboolean android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDescriptor, + jboolean pin) { int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); int result = (pin ? ashmem_pin_region(fd, 0, 0) : ashmem_unpin_region(fd, 0, 0)); if (result < 0) { jniThrowException(env, "java/io/IOException", NULL); } + return result == ASHMEM_WAS_PURGED; } static jint android_os_MemoryFile_get_size(JNIEnv* env, jobject clazz, @@ -138,19 +57,12 @@ static jint android_os_MemoryFile_get_size(JNIEnv* env, jobject clazz, } static const JNINativeMethod methods[] = { - {"native_open", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_MemoryFile_open}, - {"native_mmap", "(Ljava/io/FileDescriptor;II)J", (void*)android_os_MemoryFile_mmap}, - {"native_munmap", "(JI)V", (void*)android_os_MemoryFile_munmap}, - {"native_close", "(Ljava/io/FileDescriptor;)V", (void*)android_os_MemoryFile_close}, - {"native_read", "(Ljava/io/FileDescriptor;J[BIIIZ)I", (void*)android_os_MemoryFile_read}, - {"native_write", "(Ljava/io/FileDescriptor;J[BIIIZ)V", (void*)android_os_MemoryFile_write}, - {"native_pin", "(Ljava/io/FileDescriptor;Z)V", (void*)android_os_MemoryFile_pin}, + {"native_pin", "(Ljava/io/FileDescriptor;Z)Z", (void*)android_os_MemoryFile_pin}, {"native_get_size", "(Ljava/io/FileDescriptor;)I", (void*)android_os_MemoryFile_get_size} }; -int register_android_os_MemoryFile(JNIEnv* env) -{ +int register_android_os_MemoryFile(JNIEnv* env) { return RegisterMethodsOrDie(env, "android/os/MemoryFile", methods, NELEM(methods)); } diff --git a/core/jni/android_os_SharedMemory.cpp b/core/jni/android_os_SharedMemory.cpp new file mode 100644 index 000000000000..24d08112275e --- /dev/null +++ b/core/jni/android_os_SharedMemory.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 The Android Open 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 "SharedMemory" + +#include "core_jni_helpers.h" + +#include <cutils/ashmem.h> +#include <utils/Log.h> +#include "JNIHelp.h" +#include "JniConstants.h" +#include "ScopedLocalRef.h" + +#include <algorithm> +#include <errno.h> +#include <limits> +#include <unistd.h> + +namespace { + +static void throwErrnoException(JNIEnv* env, const char* functionName, int error) { + static jmethodID ctor = env->GetMethodID(JniConstants::errnoExceptionClass, + "<init>", "(Ljava/lang/String;I)V"); + + ScopedLocalRef<jstring> detailMessage(env, env->NewStringUTF(functionName)); + if (detailMessage.get() == NULL) { + // Not really much we can do here. We're probably dead in the water, + // but let's try to stumble on... + env->ExceptionClear(); + } + + jobject exception = env->NewObject(JniConstants::errnoExceptionClass, ctor, + detailMessage.get(), error); + env->Throw(reinterpret_cast<jthrowable>(exception)); +} + +static jobject SharedMemory_create(JNIEnv* env, jobject, jstring jname, jint size) { + + // Name is optional so we can't use ScopedUtfChars for this as it throws NPE on null + const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr; + + int fd = ashmem_create_region(name, size); + + // Capture the error, if there is one, before calling ReleaseStringUTFChars + int err = fd < 0 ? errno : 0; + + if (name) { + env->ReleaseStringUTFChars(jname, name); + } + + if (fd < 0) { + throwErrnoException(env, "SharedMemory_create", err); + return nullptr; + } + + return jniCreateFileDescriptor(env, fd); +} + +static jint SharedMemory_getSize(JNIEnv* env, jobject, jobject fileDescriptor) { + int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); + if (!ashmem_valid(fd)) { + return -1; + } + size_t size = ashmem_get_size_region(fd); + return static_cast<jint>(std::min(size, static_cast<size_t>(std::numeric_limits<jint>::max()))); +} + +static jint SharedMemory_setProt(JNIEnv* env, jobject, jobject fileDescriptor, jint prot) { + int fd = jniGetFDFromFileDescriptor(env, fileDescriptor); + int err = 0; + if (ashmem_set_prot_region(fd, prot)) { + err = errno; + } + return err; +} + +static const JNINativeMethod methods[] = { + {"nCreate", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)SharedMemory_create}, + {"nGetSize", "(Ljava/io/FileDescriptor;)I", (void*)SharedMemory_getSize}, + {"nSetProt", "(Ljava/io/FileDescriptor;I)I", (void*)SharedMemory_setProt}, +}; + +} // anonymous namespace + +namespace android { + +int register_android_os_SharedMemory(JNIEnv* env) +{ + return RegisterMethodsOrDie(env, "android/os/SharedMemory", methods, NELEM(methods)); +} + +} // namespace android diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index ced22cc37f5a..01b8e1558fb3 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -561,6 +561,35 @@ to be set for all windows of this activity --> <attr name="showForAllUsers" format="boolean" /> + <!-- Specifies whether an {@link Activity} should be shown on top of the the lock screen + whenever the lockscreen is up and the activity is resumed. Normally an activity will be + transitioned to the stopped state if it is started while the lockscreen is up, but with + this flag set the activity will remain in the resumed state visible on-top of the lock + screen. + + <p>This should be used instead of {@link android.view.LayoutParams#FLAG_SHOW_WHEN_LOCKED} + flag set for Windows. When using the Window flag during activity startup, there may not be + time to add it before the system stops your activity for being behind the lock-screen. + This leads to a double life-cycle as it is then restarted.</p> --> + <attr name="showWhenLocked" format="boolean" /> + + <!-- Specifies whether the screen should be turned on when the {@link Activity} is resumed. + Normally an activity will be transitioned to the stopped state if it is started while the + screen if off, but with this flag set the activity will cause the screen to turn on if the + activity will be visible and resumed due to the screen coming on. The screen will not be + turned on if the activity won't be visible after the screen is turned on. This flag is + normally used in conjunction with the {@link android.R.attr#showWhenLocked} flag to make + sure the activity is visible after the screen is turned on when the lockscreen is up. In + addition, if this flag is set and the activity calls + {@link KeyguardManager#requestDismissKeyguard(Activity, KeyguardManager.KeyguardDismissCallback)} + the screen will turn on. + + <p>This should be used instead of {@link android.view.LayoutParams.FLAG_TURN_SCREEN_ON} + flag set for Windows. When using the Window flag during activity startup, there may not be + time to add it before the system stops your activity because the screen has not yet turned + on. This leads to a double life-cycle as it is then restarted.</p> --> + <attr name="turnScreenOn" format="boolean" /> + <!-- Specify the authorities under which this content provider can be found. Multiple authorities may be supplied by separating them with a semicolon. Authority names should use a Java-style naming @@ -2093,6 +2122,10 @@ <attr name="maxAspectRatio" /> <attr name="lockTaskMode" /> <attr name="showForAllUsers" /> + + <attr name="showWhenLocked" /> + <attr name="turnScreenOn" /> + <attr name="directBootAware" /> <!-- @hide This activity is always focusable regardless of if it is in a task/stack whose activities are normally not focusable. diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 3033f8c6543a..31c6039c8f78 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -407,12 +407,21 @@ <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering --> <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH --> + <!-- This list is also modified by code within the framework, including: + + - TYPE_ETHERNET (9) is prepended to this list, and + - the output of TelephonyManager.getTetherApnRequired() + determines whether both TYPE_MOBILE (0) and TYPE_HIPRI (5) + or TYPE_MOBILE_DUN (4) are appended (if not already present). + + For other changes applied to this list, now and in the future, see + com.android.server.connectivity.tethering.TetheringConfiguration. + --> <integer-array translatable="false" name="config_tether_upstream_types"> <item>0</item> <item>1</item> <item>5</item> <item>7</item> - <item>9</item> </integer-array> <!-- If the DUN connection for this CDMA device supports more than just DUN --> @@ -2975,4 +2984,7 @@ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app --> <bool name="config_showAreaUpdateInfoSettings">false</bool> + + <!-- Enable the RingtonePickerActivity in 'com.android.providers.media'. --> + <bool name="config_defaultRingtonePickerEnabled">true</bool> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 48e667a06aa0..2a4881d6b167 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2843,6 +2843,8 @@ <eat-comment /> <public-group type="attr" first-id="0x01010569"> + <public name="showWhenLocked"/> + <public name="turnScreenOn"/> </public-group> <public-group type="style" first-id="0x010302e0"> diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 1ebd42925b98..b96bda1596b6 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -26,6 +26,7 @@ import static java.lang.reflect.Modifier.isFinal; import static java.lang.reflect.Modifier.isPublic; import static java.lang.reflect.Modifier.isStatic; +import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -37,6 +38,7 @@ import java.util.HashSet; import java.util.Set; /** Tests that ensure appropriate settings are backed up. */ +@Presubmit @RunWith(AndroidJUnit4.class) @SmallTest public class SettingsBackupTest { @@ -98,6 +100,7 @@ public class SettingsBackupTest { Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, Settings.Global.ALWAYS_FINISH_ACTIVITIES, Settings.Global.ANIMATOR_DURATION_SCALE, + Settings.Global.ANOMALY_DETECTION_CONSTANTS, Settings.Global.APN_DB_UPDATE_CONTENT_URL, Settings.Global.APN_DB_UPDATE_METADATA_URL, Settings.Global.APP_IDLE_CONSTANTS, @@ -117,7 +120,6 @@ public class SettingsBackupTest { Settings.Global.BLUETOOTH_INTEROPERABILITY_LIST, Settings.Global.BLUETOOTH_MAP_CLIENT_PRIORITY_PREFIX, Settings.Global.BLUETOOTH_MAP_PRIORITY_PREFIX, - Settings.Global.BLUETOOTH_ON, // Candidate for backup? Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX, Settings.Global.BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX, Settings.Global.BLUETOOTH_SAP_PRIORITY_PREFIX, @@ -166,6 +168,7 @@ public class SettingsBackupTest { Settings.Global.DEVICE_IDLE_CONSTANTS, Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH, Settings.Global.BATTERY_SAVER_CONSTANTS, + Settings.Global.DEFAULT_SM_DP_PLUS, Settings.Global.DEVICE_NAME, Settings.Global.DEVICE_POLICY_CONSTANTS, Settings.Global.DEVICE_PROVISIONED, @@ -190,11 +193,14 @@ public class SettingsBackupTest { Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, Settings.Global.ENABLE_CACHE_QUOTA_CALCULATION, Settings.Global.ENABLE_CELLULAR_ON_BOOT, + Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE, Settings.Global.ENABLE_DISKSTATS_LOGGING, Settings.Global.ENABLE_EPHEMERAL_FEATURE, Settings.Global.ENHANCED_4G_MODE_ENABLED, Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES, Settings.Global.ERROR_LOGCAT_PREFIX, + Settings.Global.EUICC_PROVISIONED, + Settings.Global.EUICC_WIPING_TIMEOUT_MILLIS, Settings.Global.FANCY_IME_ANIMATIONS, Settings.Global.FORCE_ALLOW_ON_EXTERNAL, Settings.Global.FSTRIM_MANDATORY_INTERVAL, @@ -211,6 +217,7 @@ public class SettingsBackupTest { Settings.Global.HTTP_PROXY, Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY, Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY, + Settings.Global.INSTANT_APP_DEXOPT_ENABLED, Settings.Global.INTENT_FIREWALL_UPDATE_CONTENT_URL, Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL, Settings.Global.JOB_SCHEDULER_CONSTANTS, @@ -330,6 +337,7 @@ public class SettingsBackupTest { Settings.Global.TCP_DEFAULT_INIT_RWND, Settings.Global.TETHER_DUN_APN, Settings.Global.TETHER_DUN_REQUIRED, + Settings.Global.TETHER_OFFLOAD_DISABLED, Settings.Global.TETHER_SUPPORTED, Settings.Global.THEATER_MODE_ON, Settings.Global.TRANSITION_ANIMATION_SCALE, @@ -488,6 +496,7 @@ public class SettingsBackupTest { Settings.Secure.TRUST_AGENTS_INITIALIZED, Settings.Secure.TV_INPUT_CUSTOM_LABELS, Settings.Secure.TV_INPUT_HIDDEN_INPUTS, + Settings.Secure.TV_USER_SETUP_COMPLETE, Settings.Secure.UI_NIGHT_MODE, // candidate? Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java index 8616d58a0319..1d0cfa5ff082 100644 --- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java +++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java @@ -289,6 +289,9 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback } private void updateLayerBounds(Rect bounds) { + if (bounds.isEmpty()) { + return; + } try { suspendChildInvalidation(); updateLayerBoundsInternal(bounds); @@ -1109,4 +1112,4 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback mCheckedStateful = false; } } -}
\ No newline at end of file +} diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 0782269d7de1..99ccc98a1b31 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -1901,8 +1901,6 @@ int ResTable_config::compare(const ResTable_config& o) const { if (diff != 0) return diff; diff = (int32_t)(screenSize - o.screenSize); if (diff != 0) return diff; - diff = (int32_t)(version - o.version); - if (diff != 0) return diff; diff = (int32_t)(screenLayout - o.screenLayout); if (diff != 0) return diff; diff = (int32_t)(screenLayout2 - o.screenLayout2); @@ -1914,6 +1912,11 @@ int ResTable_config::compare(const ResTable_config& o) const { diff = (int32_t)(smallestScreenWidthDp - o.smallestScreenWidthDp); if (diff != 0) return diff; diff = (int32_t)(screenSizeDp - o.screenSizeDp); + if (diff != 0) return diff; + + // Version MUST be last to ensure that a sorted list of configurations will always have the + // versions beside each other. + diff = (int32_t)(version - o.version); return (int)diff; } diff --git a/location/java/android/location/GnssStatus.java b/location/java/android/location/GnssStatus.java index 912551f5e4a9..b2903c4689e0 100644 --- a/location/java/android/location/GnssStatus.java +++ b/location/java/android/location/GnssStatus.java @@ -97,13 +97,12 @@ public final class GnssStatus { CONSTELLATION_QZSS, CONSTELLATION_BEIDOU, CONSTELLATION_GALILEO}) public @interface ConstellationType {} - /* These package private values are modified by the LocationManager class */ - /* package */ int[] mSvidWithFlags; - /* package */ float[] mCn0DbHz; - /* package */ float[] mElevations; - /* package */ float[] mAzimuths; - /* package */ int mSvCount; - /* package */ float[] mCarrierFrequencies; + final int[] mSvidWithFlags; + final float[] mCn0DbHz; + final float[] mElevations; + final float[] mAzimuths; + final int mSvCount; + final float[] mCarrierFrequencies; GnssStatus(int svCount, int[] svidWithFlags, float[] cn0s, float[] elevations, float[] azimuths, float[] carrierFrequencies) { diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 26ac2a23d8c5..968f596e0cc5 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -74,7 +74,8 @@ public class LocationManager { new HashMap<>(); private final HashMap<OnNmeaMessageListener, GnssStatusListenerTransport> mGnssNmeaListeners = new HashMap<>(); - private GnssStatus mGnssStatus; + // volatile + GnssStatus final-fields pattern to avoid a partially published object + private volatile GnssStatus mGnssStatus; private int mTimeToFirstFix; /** @@ -1563,7 +1564,7 @@ public class LocationManager { float[] cn0s, float[] elevations, float[] azimuths, float[] carrierFreqs) { if (mGnssCallback != null) { mGnssStatus = new GnssStatus(svCount, prnWithFlags, cn0s, elevations, azimuths, - carrierFreqs); + carrierFreqs); Message msg = Message.obtain(); msg.what = GpsStatus.GPS_EVENT_SATELLITE_STATUS; @@ -1949,7 +1950,7 @@ public class LocationManager { status = new GpsStatus(); } // When mGnssStatus is null, that means that this method is called outside - // onGpsStatusChanged(). Return an empty status to maintain backwards compatibility. + // onGpsStatusChanged(). Return an empty status to maintain backwards compatibility. if (mGnssStatus != null) { status.setStatus(mGnssStatus, mTimeToFirstFix); } diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index fd994b572981..a4302880dfaf 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -130,6 +130,15 @@ <!-- Bluetooth settings. Message when connected to a device, except for phone/media audio. [CHAR LIMIT=40] --> <string name="bluetooth_connected_no_headset_no_a2dp">Connected (no phone or media)</string> + <!-- Bluetooth settings. Message when connected to a device, showing remote device battery level. [CHAR LIMIT=NONE] --> + <string name="bluetooth_connected_battery_level">Connected, battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g></string> + <!-- Bluetooth settings. Message when connected to a device, except for phone audio, showing remote device battery level. [CHAR LIMIT=NONE] --> + <string name="bluetooth_connected_no_headset_battery_level">Connected (no phone), battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g></string> + <!-- Bluetooth settings. Message when connected to a device, except for media audio, showing remote device battery level. [CHAR LIMIT=NONE] --> + <string name="bluetooth_connected_no_a2dp_battery_level">Connected (no media), battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g></string> + <!-- Bluetooth settings. Message when connected to a device, except for phone/media audio, showing remote device battery level. [CHAR LIMIT=NONE] --> + <string name="bluetooth_connected_no_headset_no_a2dp_battery_level">Connected (no phone or media), battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g></string> + <!-- Bluetooth settings. The user-visible string that is used whenever referring to the A2DP profile. --> <string name="bluetooth_profile_a2dp">Media audio</string> <!-- Bluetooth settings. The user-visible string that is used whenever referring to the headset or handsfree profile. --> diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index fc6a1611e697..4a5445163650 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -1058,6 +1058,12 @@ public class AccessPoint implements Comparable<AccessPoint> { final int oldLevel = getLevel(); if (info != null && isInfoForThisAccessPoint(config, info)) { updated = (mInfo == null); + if (mConfig != config) { + // We do not set updated = true as we do not want to increase the amount of sorting + // and copying performed in WifiTracker at this time. If issues involving refresh + // are still seen, we will investigate further. + update(config); // Notifies the AccessPointListener of the change + } if (mRssi != info.getRssi()) { mRssi = info.getRssi(); updated = true; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java index e5977f313091..92995a8e850d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -70,6 +70,19 @@ public class AccessPointPreference extends Preference { R.string.accessibility_wifi_signal_full }; + public static String generatePreferenceKey(AccessPoint accessPoint) { + StringBuilder builder = new StringBuilder(); + + if (TextUtils.isEmpty(accessPoint.getBssid())) { + builder.append(accessPoint.getSsidStr()); + } else { + builder.append(accessPoint.getBssid()); + } + + builder.append(',').append(accessPoint.getSecurity()); + return builder.toString(); + } + // Used for dummy pref. public AccessPointPreference(Context context, AttributeSet attrs) { super(context, attrs); diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java index 2213ae647e98..4307cb0f1b7b 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/TestAccessPointBuilder.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java @@ -26,9 +26,9 @@ import android.os.Bundle; /** * Build and return a valid AccessPoint. * -* Only intended for testing the AccessPoint class; -* AccessPoints were designed to only be populated -* by the mechanisms of scan results and wifi configurations. +* Only intended for testing the AccessPoint class or creating Access points to be used in testing +* applications. AccessPoints were designed to only be populated by the mechanisms of scan results +* and wifi configurations. */ public class TestAccessPointBuilder { // match the private values in WifiManager @@ -36,12 +36,14 @@ public class TestAccessPointBuilder { private static final int MAX_RSSI = -55; // set some sensible defaults + private String mBssid = null; private int mRssi = AccessPoint.UNREACHABLE_RSSI; private int mNetworkId = WifiConfiguration.INVALID_NETWORK_ID; private String ssid = "TestSsid"; private NetworkInfo mNetworkInfo = null; private String mFqdn = null; private String mProviderFriendlyName = null; + private int mSecurity = AccessPoint.SECURITY_NONE; private WifiConfiguration mWifiConfig; private WifiInfo mWifiInfo; @@ -56,6 +58,7 @@ public class TestAccessPointBuilder { WifiConfiguration wifiConfig = new WifiConfiguration(); wifiConfig.networkId = mNetworkId; + wifiConfig.BSSID = mBssid; bundle.putString(AccessPoint.KEY_SSID, ssid); bundle.putParcelable(AccessPoint.KEY_CONFIG, wifiConfig); @@ -67,6 +70,8 @@ public class TestAccessPointBuilder { if (mProviderFriendlyName != null) { bundle.putString(AccessPoint.KEY_PROVIDER_FRIENDLY_NAME, mProviderFriendlyName); } + bundle.putInt(AccessPoint.KEY_SECURITY, mSecurity); + AccessPoint ap = new AccessPoint(mContext, bundle); ap.setRssi(mRssi); return ap; @@ -141,6 +146,11 @@ public class TestAccessPointBuilder { return this; } + public TestAccessPointBuilder setSecurity(int security) { + mSecurity = security; + return this; + } + public TestAccessPointBuilder setSsid(String newSsid) { ssid = newSsid; return this; @@ -171,4 +181,9 @@ public class TestAccessPointBuilder { mNetworkId = networkId; return this; } + + public TestAccessPointBuilder setBssid(String bssid) { + mBssid = bssid; + return this; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 85a453d24e6f..596eaefcd054 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -16,6 +16,7 @@ package com.android.settingslib.wifi; import android.annotation.MainThread; +import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -36,7 +37,6 @@ import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WifiNetworkScoreCache; import android.net.wifi.WifiNetworkScoreCache.CacheListener; -import android.os.ConditionVariable; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -247,7 +247,10 @@ public class WifiTracker { mWorkHandler.removeMessages(WorkHandler.MSG_UPDATE_ACCESS_POINTS); mLastInfo = mWifiManager.getConnectionInfo(); mLastNetworkInfo = mConnectivityManager.getNetworkInfo(mWifiManager.getCurrentNetwork()); - updateAccessPointsLocked(); + + final List<ScanResult> newScanResults = mWifiManager.getScanResults(); + List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); + updateAccessPointsLocked(newScanResults, configs); if (DBG) { Log.d(TAG, "force update - internal access point list:\n" + mInternalAccessPoints); @@ -377,7 +380,7 @@ public class WifiTracker { mNetworkScoreManager.unregisterNetworkScoreCache(NetworkKey.TYPE_WIFI, mScoreCache); mScoreCache.clearScores(); - // Synchronize on mLock to avoid concurrent modification during updateAccessPointsLocked + // Synchronize on mLock to avoid concurrent modification during updateAccessPoints synchronized (mLock) { mRequestedScores.clear(); } @@ -427,9 +430,8 @@ public class WifiTracker { mScanId = 0; } - private Collection<ScanResult> fetchScanResults() { + private Collection<ScanResult> updateScanResultCache(final List<ScanResult> newResults) { mScanId++; - final List<ScanResult> newResults = mWifiManager.getScanResults(); for (ScanResult newResult : newResults) { if (newResult.SSID == null || newResult.SSID.isEmpty()) { continue; @@ -457,8 +459,8 @@ public class WifiTracker { return mScanResultCache.values(); } - private WifiConfiguration getWifiConfigurationForNetworkId(int networkId) { - final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); + private WifiConfiguration getWifiConfigurationForNetworkId( + int networkId, final List<WifiConfiguration> configs) { if (configs != null) { for (WifiConfiguration config : configs) { if (mLastInfo != null && networkId == config.networkId && @@ -470,20 +472,37 @@ public class WifiTracker { return null; } - /** Safely modify {@link #mInternalAccessPoints} by acquiring {@link #mLock} first. */ - private void updateAccessPointsLocked() { + /** + * Safely modify {@link #mInternalAccessPoints} by acquiring {@link #mLock} first. + * + * <p>Will not perform the update if {@link #mStaleScanResults} is true + */ + private void updateAccessPoints() { + List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); + final List<ScanResult> newScanResults = mWifiManager.getScanResults(); + synchronized (mLock) { - updateAccessPoints(); + if(!mStaleScanResults) { + updateAccessPointsLocked(newScanResults, configs); + } } } /** * Update the internal list of access points. * - * <p>Should never be called directly, use {@link #updateAccessPointsLocked()} instead. + * <p>Do not called directly (except for forceUpdate), use {@link #updateAccessPoints()} which + * respects {@link #mStaleScanResults}. */ @GuardedBy("mLock") - private void updateAccessPoints() { + private void updateAccessPointsLocked(final List<ScanResult> newScanResults, + List<WifiConfiguration> configs) { + WifiConfiguration connectionConfig = null; + if (mLastInfo != null) { + connectionConfig = getWifiConfigurationForNetworkId( + mLastInfo.getNetworkId(), mWifiManager.getConfiguredNetworks()); + } + // Swap the current access points into a cached list. List<AccessPoint> cachedAccessPoints = new ArrayList<>(mInternalAccessPoints); ArrayList<AccessPoint> accessPoints = new ArrayList<>(); @@ -496,14 +515,9 @@ public class WifiTracker { /* Lookup table to more quickly update AccessPoints by only considering objects with the * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */ Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>(); - WifiConfiguration connectionConfig = null; - if (mLastInfo != null) { - connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId()); - } - final Collection<ScanResult> results = fetchScanResults(); + final Collection<ScanResult> results = updateScanResultCache(newScanResults); - final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); if (configs != null) { for (WifiConfiguration config : configs) { if (config.selfAdded && config.numAssociation == 0) { @@ -671,7 +685,8 @@ public class WifiTracker { WifiConfiguration connectionConfig = null; mLastInfo = mWifiManager.getConnectionInfo(); if (mLastInfo != null) { - connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId()); + connectionConfig = getWifiConfigurationForNetworkId(mLastInfo.getNetworkId(), + mWifiManager.getConfiguredNetworks()); } boolean updated = false; @@ -868,15 +883,12 @@ public class WifiTracker { } } - @GuardedBy("mLock") private void processMessage(Message msg) { if (!mRegistered) return; switch (msg.what) { case MSG_UPDATE_ACCESS_POINTS: - if (!mStaleScanResults) { - updateAccessPointsLocked(); - } + updateAccessPoints(); break; case MSG_UPDATE_NETWORK_INFO: updateNetworkInfo((NetworkInfo) msg.obj); diff --git a/packages/SettingsLib/tests/integ/AndroidManifest.xml b/packages/SettingsLib/tests/integ/AndroidManifest.xml index 0d5ff2ca05a5..e8e0b41ffd41 100644 --- a/packages/SettingsLib/tests/integ/AndroidManifest.xml +++ b/packages/SettingsLib/tests/integ/AndroidManifest.xml @@ -21,6 +21,7 @@ <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_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index 57ad093422a4..5c64cff78a88 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -20,6 +20,8 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; @@ -622,4 +624,36 @@ public class AccessPointTest { NetworkInfo newInfo = new NetworkInfo(networkInfo); // same values assertThat(ap.update(config, wifiInfo, newInfo)).isFalse(); } + + @Test + public void testUpdateWithConfigChangeOnly_returnsFalseButInvokesListener() { + int networkId = 123; + int rssi = -55; + WifiConfiguration config = new WifiConfiguration(); + config.networkId = networkId; + config.numNoInternetAccessReports = 1; + + WifiInfo wifiInfo = new WifiInfo(); + wifiInfo.setNetworkId(networkId); + wifiInfo.setRssi(rssi); + + NetworkInfo networkInfo = + new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", ""); + networkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, "", ""); + + AccessPoint ap = new TestAccessPointBuilder(mContext) + .setNetworkInfo(networkInfo) + .setNetworkId(networkId) + .setRssi(rssi) + .setWifiInfo(wifiInfo) + .build(); + + AccessPoint.AccessPointListener mockListener = mock(AccessPoint.AccessPointListener.class); + ap.setListener(mockListener); + WifiConfiguration newConfig = new WifiConfiguration(config); + config.validatedInternetAccess = true; + + assertThat(ap.update(newConfig, wifiInfo, networkInfo)).isFalse(); + verify(mockListener).onAccessPointChanged(ap); + } } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java index 073da7eea25f..071f9216ea56 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java @@ -59,6 +59,7 @@ import android.provider.Settings; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.support.test.filters.FlakyTest; import org.junit.After; import org.junit.Before; @@ -492,6 +493,7 @@ public class WifiTrackerTest { waitForHandlersToProcessCurrentlyEnqueuedMessages(tracker); } + @FlakyTest @Test public void scoreCacheUpdateScoresShouldChangeSortOrder() throws InterruptedException { WifiTracker tracker = createTrackerWithImmediateBroadcastsAndInjectInitialScanResults(); @@ -530,6 +532,7 @@ public class WifiTrackerTest { assertEquals(aps.get(1).getSsidStr(), SSID_2); } + @FlakyTest @Test public void scoreCacheUpdateScoresShouldInsertSpeedIntoAccessPoint() throws InterruptedException { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java new file mode 100644 index 000000000000..335653bc2f18 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017 The Android Open 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 com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import com.android.settingslib.SettingLibRobolectricTestRunner; +import com.android.settingslib.TestConfig; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(SettingLibRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class AccessPointPreferenceTest { + + private Context mContext = RuntimeEnvironment.application; + + @Test + public void generatePreferenceKey_shouldReturnSsidPlusSecurity() { + String ssid = "ssid"; + int security = AccessPoint.SECURITY_WEP; + String expectedKey = ssid + ',' + security; + + TestAccessPointBuilder builder = new TestAccessPointBuilder(mContext); + builder.setSsid(ssid).setSecurity(security); + + assertThat(AccessPointPreference.generatePreferenceKey(builder.build())) + .isEqualTo(expectedKey); + } + + @Test + public void generatePreferenceKey_shouldReturnBssidPlusSecurity() { + String bssid = "bssid"; + int security = AccessPoint.SECURITY_WEP; + String expectedKey = bssid + ',' + security; + + TestAccessPointBuilder builder = new TestAccessPointBuilder(mContext); + builder.setBssid(bssid).setSecurity(security); + + assertThat(AccessPointPreference.generatePreferenceKey(builder.build())) + .isEqualTo(expectedKey); + } +} diff --git a/packages/SystemUI/res/anim/ic_portrait_from_auto_rotate_animation_arrow_top.xml b/packages/SystemUI/res/anim/ic_portrait_from_auto_rotate_animation_arrow_top.xml deleted file mode 100644 index 4e20d8190bbc..000000000000 --- a/packages/SystemUI/res/anim/ic_portrait_from_auto_rotate_animation_arrow_top.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="400" - android:propertyName="fillAlpha" - android:valueFrom="1" - android:valueTo="1" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="83" - android:propertyName="fillAlpha" - android:valueFrom="1" - android:valueTo="0" - android:interpolator="@android:interpolator/linear" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/ic_portrait_to_auto_rotate_animation_arrow_top.xml b/packages/SystemUI/res/anim/ic_portrait_to_auto_rotate_animation_arrow_top.xml deleted file mode 100644 index 682dcf35e076..000000000000 --- a/packages/SystemUI/res/anim/ic_portrait_to_auto_rotate_animation_arrow_top.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="50" - android:propertyName="fillAlpha" - android:valueFrom="0" - android:valueTo="0" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="83" - android:propertyName="fillAlpha" - android:valueFrom="0" - android:valueTo="1" - android:interpolator="@android:interpolator/linear" /> - </set> -</set> diff --git a/packages/SystemUI/res/anim/ic_portrait_to_auto_rotate_animation_arrows.xml b/packages/SystemUI/res/anim/ic_portrait_to_auto_rotate_animation_arrows.xml deleted file mode 100644 index 9fa8ec0d65d4..000000000000 --- a/packages/SystemUI/res/anim/ic_portrait_to_auto_rotate_animation_arrows.xml +++ /dev/null @@ -1,54 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2014 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> -<set xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="133" - android:propertyName="scaleX" - android:valueFrom="0.9" - android:valueTo="0.9" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="333" - android:propertyName="scaleX" - android:valueFrom="0.9" - android:valueTo="1" - android:interpolator="@android:interpolator/linear" /> - </set> - <set - android:ordering="sequentially" > - <objectAnimator - android:duration="133" - android:propertyName="scaleY" - android:valueFrom="0.9" - android:valueTo="0.9" - android:interpolator="@android:interpolator/linear" /> - <objectAnimator - android:duration="333" - android:propertyName="scaleY" - android:valueFrom="0.9" - android:valueTo="1" - android:interpolator="@android:interpolator/linear" /> - </set> - <objectAnimator - android:duration="617" - android:propertyName="rotation" - android:valueFrom="-221" - android:valueTo="0" - android:interpolator="@android:interpolator/fast_out_slow_in" /> -</set> diff --git a/packages/SystemUI/res/anim/ic_portrait_from_auto_rotate_animation_arrows.xml b/packages/SystemUI/res/anim/ic_portrait_to_rotate_arrows_0_animation.xml index 61bdfeaa27a0..ad2a5fad5268 100644 --- a/packages/SystemUI/res/anim/ic_portrait_from_auto_rotate_animation_arrows.xml +++ b/packages/SystemUI/res/anim/ic_portrait_to_rotate_arrows_0_animation.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2017 The Android Open 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,41 +14,40 @@ See the License for the specific language governing permissions and limitations under the License. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" > +<set + xmlns:android="http://schemas.android.com/apk/res/android" > <set android:ordering="sequentially" > <objectAnimator - android:duration="117" + android:duration="116" android:propertyName="scaleX" - android:valueFrom="1" - android:valueTo="1" + android:valueFrom="1.0" + android:valueTo="1.0" + android:valueType="floatType" android:interpolator="@android:interpolator/linear" /> <objectAnimator android:duration="333" android:propertyName="scaleX" - android:valueFrom="1" + android:valueFrom="1.0" android:valueTo="0.9" + android:valueType="floatType" android:interpolator="@android:interpolator/linear" /> </set> <set android:ordering="sequentially" > <objectAnimator - android:duration="117" + android:duration="116" android:propertyName="scaleY" - android:valueFrom="1" - android:valueTo="1" + android:valueFrom="1.0" + android:valueTo="1.0" + android:valueType="floatType" android:interpolator="@android:interpolator/linear" /> <objectAnimator android:duration="333" android:propertyName="scaleY" - android:valueFrom="1" + android:valueFrom="1.0" android:valueTo="0.9" + android:valueType="floatType" android:interpolator="@android:interpolator/linear" /> </set> - <objectAnimator - android:duration="617" - android:propertyName="rotation" - android:valueFrom="0" - android:valueTo="-221" - android:interpolator="@android:interpolator/fast_out_slow_in" /> </set> diff --git a/packages/SystemUI/res/anim/ic_portrait_to_rotate_arrows_animation.xml b/packages/SystemUI/res/anim/ic_portrait_to_rotate_arrows_animation.xml new file mode 100644 index 000000000000..cdb7890dc170 --- /dev/null +++ b/packages/SystemUI/res/anim/ic_portrait_to_rotate_arrows_animation.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2017 The Android Open 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. +--> +<set + xmlns:android="http://schemas.android.com/apk/res/android" > + <objectAnimator + android:duration="616" + android:propertyName="rotation" + android:valueFrom="0.0" + android:valueTo="-221.0" + android:valueType="floatType" + android:interpolator="@android:interpolator/fast_out_slow_in" /> +</set> diff --git a/packages/SystemUI/res/anim/ic_portrait_from_auto_rotate_animation_arrow_bottom.xml b/packages/SystemUI/res/anim/ic_portrait_to_rotate_bottom_merged_animation.xml index 4e20d8190bbc..46100b407831 100644 --- a/packages/SystemUI/res/anim/ic_portrait_from_auto_rotate_animation_arrow_bottom.xml +++ b/packages/SystemUI/res/anim/ic_portrait_to_rotate_bottom_merged_animation.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2017 The Android Open 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,20 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" > +<set + xmlns:android="http://schemas.android.com/apk/res/android" > <set android:ordering="sequentially" > <objectAnimator android:duration="400" android:propertyName="fillAlpha" - android:valueFrom="1" - android:valueTo="1" + android:valueFrom="1.0" + android:valueTo="1.0" + android:valueType="floatType" android:interpolator="@android:interpolator/linear" /> <objectAnimator android:duration="83" android:propertyName="fillAlpha" - android:valueFrom="1" - android:valueTo="0" + android:valueFrom="1.0" + android:valueTo="0.0" + android:valueType="floatType" android:interpolator="@android:interpolator/linear" /> </set> </set> diff --git a/packages/SystemUI/res/anim/ic_portrait_from_auto_rotate_animation_device.xml b/packages/SystemUI/res/anim/ic_portrait_to_rotate_device_0_animation.xml index 6a0a20bd1242..8f6d24d0d61d 100644 --- a/packages/SystemUI/res/anim/ic_portrait_from_auto_rotate_animation_device.xml +++ b/packages/SystemUI/res/anim/ic_portrait_to_rotate_device_0_animation.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2017 The Android Open 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,11 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" > +<set + xmlns:android="http://schemas.android.com/apk/res/android" > <objectAnimator android:duration="400" android:propertyName="rotation" - android:valueFrom="0" - android:valueTo="-135" + android:valueFrom="0.0" + android:valueTo="-135.0" + android:valueType="floatType" android:interpolator="@android:interpolator/fast_out_slow_in" /> </set> diff --git a/packages/SystemUI/res/anim/ic_portrait_from_auto_rotate_animation_device_1.xml b/packages/SystemUI/res/anim/ic_portrait_to_rotate_device_merged_animation.xml index 92b19add4bcb..300ed53052a8 100644 --- a/packages/SystemUI/res/anim/ic_portrait_from_auto_rotate_animation_device_1.xml +++ b/packages/SystemUI/res/anim/ic_portrait_to_rotate_device_merged_animation.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2017 The Android Open 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,18 +14,19 @@ See the License for the specific language governing permissions and limitations under the License. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" > +<set + xmlns:android="http://schemas.android.com/apk/res/android" > <set android:ordering="sequentially" > <objectAnimator - android:duration="167" + android:duration="66" android:propertyName="pathData" android:valueFrom="M -3.5,-20.5 c -1.19999694824,-1.19999694824 -3.10000610352,-1.19999694824 -4.19999694824,0.0 c 0.0,0.0 -12.8000030518,12.6999969482 -12.8000030518,12.6999969482 c -1.19999694824,1.19999694824 -1.19999694824,3.10000610352 0.0,4.19999694824 c 0.0,0.0 24.0,24.0000152588 24.0,24.0000152588 c 1.19999694824,1.19999694824 3.10000610352,1.19999694824 4.19999694824,0.0 c 0.0,0.0 12.6999969482,-12.700012207 12.6999969482,-12.700012207 c 1.20001220703,-1.19999694824 1.20001220703,-3.09999084473 0.0,-4.19999694824 c 0.0,0.0 -23.8999938965,-24.0 -23.8999938965,-24.0 Z M 2.84999084473,15.5500183105 c 0.0,0.0 -18.6000061035,-18.5000457764 -18.6000061035,-18.5000457764 c 0.0,0.0 12.5999908447,-12.8000030518 12.5999908447,-12.8000030518 c 0.0,0.0 18.6000213623,18.5000457764 18.6000213623,18.5000457764 c 0.0,0.0 -12.6000061035,12.8000030518 -12.6000061035,12.8000030518 Z" android:valueTo="M -3.5,-20.5 c -1.19999694824,-1.19999694824 -3.10000610352,-1.19999694824 -4.19999694824,0.0 c 0.0,0.0 -12.8000030518,12.6999969482 -12.8000030518,12.6999969482 c -1.19999694824,1.19999694824 -1.19999694824,3.10000610352 0.0,4.19999694824 c 0.0,0.0 24.0,24.0000152588 24.0,24.0000152588 c 1.19999694824,1.19999694824 3.10000610352,1.19999694824 4.19999694824,0.0 c 0.0,0.0 12.6999969482,-12.700012207 12.6999969482,-12.700012207 c 1.20001220703,-1.19999694824 1.20001220703,-3.09999084473 0.0,-4.19999694824 c 0.0,0.0 -23.8999938965,-24.0 -23.8999938965,-24.0 Z M 2.84999084473,15.5500183105 c 0.0,0.0 -18.6000061035,-18.5000457764 -18.6000061035,-18.5000457764 c 0.0,0.0 12.5999908447,-12.8000030518 12.5999908447,-12.8000030518 c 0.0,0.0 18.6000213623,18.5000457764 18.6000213623,18.5000457764 c 0.0,0.0 -12.6000061035,12.8000030518 -12.6000061035,12.8000030518 Z" android:valueType="pathType" - android:interpolator="@android:interpolator/fast_out_slow_in" /> + android:interpolator="@android:interpolator/linear" /> <objectAnimator - android:duration="217" + android:duration="216" android:propertyName="pathData" android:valueFrom="M -3.5,-20.5 c -1.19999694824,-1.19999694824 -3.10000610352,-1.19999694824 -4.19999694824,0.0 c 0.0,0.0 -12.8000030518,12.6999969482 -12.8000030518,12.6999969482 c -1.19999694824,1.19999694824 -1.19999694824,3.10000610352 0.0,4.19999694824 c 0.0,0.0 24.0,24.0000152588 24.0,24.0000152588 c 1.19999694824,1.19999694824 3.10000610352,1.19999694824 4.19999694824,0.0 c 0.0,0.0 12.6999969482,-12.700012207 12.6999969482,-12.700012207 c 1.20001220703,-1.19999694824 1.20001220703,-3.09999084473 0.0,-4.19999694824 c 0.0,0.0 -23.8999938965,-24.0 -23.8999938965,-24.0 Z M 2.84999084473,15.5500183105 c 0.0,0.0 -18.6000061035,-18.5000457764 -18.6000061035,-18.5000457764 c 0.0,0.0 12.5999908447,-12.8000030518 12.5999908447,-12.8000030518 c 0.0,0.0 18.6000213623,18.5000457764 18.6000213623,18.5000457764 c 0.0,0.0 -12.6000061035,12.8000030518 -12.6000061035,12.8000030518 Z" android:valueTo="M -3.34053039551,-22.9980926514 c -1.3207244873,-1.3207244873 -3.46876525879,-1.26383972168 -4.74829101563,0.125762939453 c 0.0,0.0 -14.8512420654,14.7411804199 -14.8512420654,14.7411804199 c -1.39259338379,1.392578125 -1.44947814941,3.54061889648 -0.125762939453,4.74827575684 c 0.0,0.0 26.4143981934,26.4144134521 26.4143981934,26.4144134521 c 1.3207244873,1.3207244873 3.46876525879,1.26382446289 4.74829101562,-0.125762939453 c 0.0,0.0 14.7381896973,-14.7381896973 14.7381896973,-14.7381896973 c 1.392578125,-1.39259338379 1.44947814941,-3.54061889648 0.125762939453,-4.74829101562 c 0.0,0.0 -26.3013458252,-26.417388916 -26.3013458252,-26.417388916 Z M 2.87156677246,16.9857940674 c 0.0,0.0 -19.7573547363,-19.7573699951 -19.7573547363,-19.7573699951 c 0.0,0.0 14.0142059326,-14.2142181396 14.0142059326,-14.2142181396 c 0.0,0.0 19.7573699951,19.7573699951 19.7573699951,19.7573699951 c 0.0,0.0 -14.0142211914,14.2142181396 -14.0142211914,14.2142181396 Z" diff --git a/packages/SystemUI/res/anim/ic_rotate_to_portrait_arrows_0_animation.xml b/packages/SystemUI/res/anim/ic_rotate_to_portrait_arrows_0_animation.xml new file mode 100644 index 000000000000..ce267704dac5 --- /dev/null +++ b/packages/SystemUI/res/anim/ic_rotate_to_portrait_arrows_0_animation.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2017 The Android Open 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. +--> +<set + xmlns:android="http://schemas.android.com/apk/res/android" > + <objectAnimator + android:duration="466" + android:propertyName="scaleX" + android:valueFrom="0.9" + android:valueTo="1.0" + android:valueType="floatType" + android:interpolator="@interpolator/ic_rotate_to_portrait_animation_interpolator_0" /> + <objectAnimator + android:duration="466" + android:propertyName="scaleY" + android:valueFrom="0.9" + android:valueTo="1.0" + android:valueType="floatType" + android:interpolator="@interpolator/ic_rotate_to_portrait_animation_interpolator_0" /> +</set> diff --git a/packages/SystemUI/res/anim/ic_rotate_to_portrait_arrows_animation.xml b/packages/SystemUI/res/anim/ic_rotate_to_portrait_arrows_animation.xml new file mode 100644 index 000000000000..6e8941d608cd --- /dev/null +++ b/packages/SystemUI/res/anim/ic_rotate_to_portrait_arrows_animation.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2017 The Android Open 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. +--> +<set + xmlns:android="http://schemas.android.com/apk/res/android" > + <objectAnimator + android:duration="616" + android:propertyName="rotation" + android:valueFrom="-221.0" + android:valueTo="0.0" + android:valueType="floatType" + android:interpolator="@android:interpolator/fast_out_slow_in" /> +</set> diff --git a/packages/SystemUI/res/anim/ic_portrait_to_auto_rotate_animation_arrow_bottom.xml b/packages/SystemUI/res/anim/ic_rotate_to_portrait_bottom_merged_animation.xml index 682dcf35e076..3c3c131ef16b 100644 --- a/packages/SystemUI/res/anim/ic_portrait_to_auto_rotate_animation_arrow_bottom.xml +++ b/packages/SystemUI/res/anim/ic_rotate_to_portrait_bottom_merged_animation.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2017 The Android Open 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,20 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" > +<set + xmlns:android="http://schemas.android.com/apk/res/android" > <set android:ordering="sequentially" > <objectAnimator android:duration="50" android:propertyName="fillAlpha" - android:valueFrom="0" - android:valueTo="0" + android:valueFrom="0.0" + android:valueTo="0.0" + android:valueType="floatType" android:interpolator="@android:interpolator/linear" /> <objectAnimator android:duration="83" android:propertyName="fillAlpha" - android:valueFrom="0" - android:valueTo="1" + android:valueFrom="0.0" + android:valueTo="1.0" + android:valueType="floatType" android:interpolator="@android:interpolator/linear" /> </set> </set> diff --git a/packages/SystemUI/res/anim/ic_portrait_to_auto_rotate_animation_device.xml b/packages/SystemUI/res/anim/ic_rotate_to_portrait_device_0_animation.xml index 3208eee2c2c6..fd8e4f881160 100644 --- a/packages/SystemUI/res/anim/ic_portrait_to_auto_rotate_animation_device.xml +++ b/packages/SystemUI/res/anim/ic_rotate_to_portrait_device_0_animation.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2017 The Android Open 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,20 +14,23 @@ See the License for the specific language governing permissions and limitations under the License. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" > +<set + xmlns:android="http://schemas.android.com/apk/res/android" > <set android:ordering="sequentially" > <objectAnimator android:duration="50" android:propertyName="rotation" - android:valueFrom="-135" - android:valueTo="-135" - android:interpolator="@android:interpolator/fast_out_slow_in" /> + android:valueFrom="-135.0" + android:valueTo="-135.0" + android:valueType="floatType" + android:interpolator="@android:interpolator/linear" /> <objectAnimator android:duration="400" android:propertyName="rotation" - android:valueFrom="-135" - android:valueTo="0" + android:valueFrom="-135.0" + android:valueTo="0.0" + android:valueType="floatType" android:interpolator="@android:interpolator/fast_out_slow_in" /> </set> </set> diff --git a/packages/SystemUI/res/anim/ic_portrait_to_auto_rotate_animation_device_1.xml b/packages/SystemUI/res/anim/ic_rotate_to_portrait_device_merged_animation.xml index c1124af4e234..a77a536e46c0 100644 --- a/packages/SystemUI/res/anim/ic_portrait_to_auto_rotate_animation_device_1.xml +++ b/packages/SystemUI/res/anim/ic_rotate_to_portrait_device_merged_animation.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2017 The Android Open 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,7 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. --> -<set xmlns:android="http://schemas.android.com/apk/res/android" > +<set + xmlns:android="http://schemas.android.com/apk/res/android" > <set android:ordering="sequentially" > <objectAnimator @@ -23,7 +24,7 @@ android:valueFrom="M -3.34053039551,-22.9980926514 c -1.3207244873,-1.3207244873 -3.46876525879,-1.26383972168 -4.74829101563,0.125762939453 c 0.0,0.0 -14.8512420654,14.7411804199 -14.8512420654,14.7411804199 c -1.39259338379,1.392578125 -1.44947814941,3.54061889648 -0.125762939453,4.74827575684 c 0.0,0.0 26.4143981934,26.4144134521 26.4143981934,26.4144134521 c 1.3207244873,1.3207244873 3.46876525879,1.26382446289 4.74829101562,-0.125762939453 c 0.0,0.0 14.7381896973,-14.7381896973 14.7381896973,-14.7381896973 c 1.392578125,-1.39259338379 1.44947814941,-3.54061889648 0.125762939453,-4.74829101562 c 0.0,0.0 -26.3013458252,-26.417388916 -26.3013458252,-26.417388916 Z M 2.87156677246,16.9857940674 c 0.0,0.0 -19.7573547363,-19.7573699951 -19.7573547363,-19.7573699951 c 0.0,0.0 14.0142059326,-14.2142181396 14.0142059326,-14.2142181396 c 0.0,0.0 19.7573699951,19.7573699951 19.7573699951,19.7573699951 c 0.0,0.0 -14.0142211914,14.2142181396 -14.0142211914,14.2142181396 Z" android:valueTo="M -3.34053039551,-22.9980926514 c -1.3207244873,-1.3207244873 -3.46876525879,-1.26383972168 -4.74829101563,0.125762939453 c 0.0,0.0 -14.8512420654,14.7411804199 -14.8512420654,14.7411804199 c -1.39259338379,1.392578125 -1.44947814941,3.54061889648 -0.125762939453,4.74827575684 c 0.0,0.0 26.4143981934,26.4144134521 26.4143981934,26.4144134521 c 1.3207244873,1.3207244873 3.46876525879,1.26382446289 4.74829101562,-0.125762939453 c 0.0,0.0 14.7381896973,-14.7381896973 14.7381896973,-14.7381896973 c 1.392578125,-1.39259338379 1.44947814941,-3.54061889648 0.125762939453,-4.74829101562 c 0.0,0.0 -26.3013458252,-26.417388916 -26.3013458252,-26.417388916 Z M 2.87156677246,16.9857940674 c 0.0,0.0 -19.7573547363,-19.7573699951 -19.7573547363,-19.7573699951 c 0.0,0.0 14.0142059326,-14.2142181396 14.0142059326,-14.2142181396 c 0.0,0.0 19.7573699951,19.7573699951 19.7573699951,19.7573699951 c 0.0,0.0 -14.0142211914,14.2142181396 -14.0142211914,14.2142181396 Z" android:valueType="pathType" - android:interpolator="@android:interpolator/fast_out_slow_in" /> + android:interpolator="@android:interpolator/linear" /> <objectAnimator android:duration="500" android:propertyName="pathData" diff --git a/packages/SystemUI/res/drawable/ic_portrait_from_auto_rotate.xml b/packages/SystemUI/res/drawable/ic_portrait_from_auto_rotate.xml index 17185a7c36b0..bce494c975ed 100644 --- a/packages/SystemUI/res/drawable/ic_portrait_from_auto_rotate.xml +++ b/packages/SystemUI/res/drawable/ic_portrait_from_auto_rotate.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2017 The Android Open 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,50 +14,51 @@ See the License for the specific language governing permissions and limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:name="ic_portrait_to_rotate" android:height="48dp" android:width="48dp" android:viewportHeight="48" android:viewportWidth="48" android:tint="?android:attr/colorControlNormal" > <group - android:name="icon" + android:name="device" android:translateX="24" android:translateY="24" > <group - android:name="icon_pivot" + android:name="device_pivot" android:translateX="-24.15" android:translateY="-24.25" > <group - android:name="arrows" - android:translateX="24.1" - android:translateY="24.1" > - <group - android:name="arrows_pivot" - android:translateX="-24.1" - android:translateY="-24.1" > - <path - android:name="arrow_top" - android:pathData="M 33.1499938965,5.25 c 6.5,3.10000610352 11.1999969482,9.40000915527 11.8999938965,17.0 c 0.0,0.0 3.00001525879,0.0 3.00001525879,0.0 c -1.00001525879,-12.3000030518 -11.3000030518,-22.0 -23.9000091553,-22.0 c -0.399993896484,0.0 -0.899993896484,0.0 -1.30000305176,0.100006103516 c 0.0,0.0 7.60000610352,7.59999084473 7.60000610352,7.59999084473 c 0.0,0.0 2.69999694824,-2.69999694824 2.69999694824,-2.69999694824 Z" - android:fillColor="#FFFFFFFF" - android:fillAlpha="1" /> - <path - android:name="arrow_bottom" - android:pathData="M 15.1499938965,43.25 c -6.5,-3.09999084473 -11.1999969482,-9.5 -11.8999938965,-17.0 c 0.0,0.0 -3.0,0.0 -3.0,0.0 c 1.0,12.3000030518 11.299987793,22.0 23.8999938965,22.0 c 0.399993896484,0.0 0.899993896484,0.0 1.30000305176,-0.0999908447266 c 0.0,0.0 -7.60000610352,-7.60000610352 -7.60000610352,-7.60000610352 c 0.0,0.0 -2.69999694824,2.69999694824 -2.69999694824,2.69999694824 Z" - android:fillColor="#FFFFFFFF" - android:fillAlpha="1" /> - </group> - </group> - <group - android:name="device" + android:name="device_0" android:translateX="24.14999" android:translateY="24.25" > <path - android:name="device_1" + android:name="device_merged" android:pathData="M -3.5,-20.5 c -1.19999694824,-1.19999694824 -3.10000610352,-1.19999694824 -4.19999694824,0.0 c 0.0,0.0 -12.8000030518,12.6999969482 -12.8000030518,12.6999969482 c -1.19999694824,1.19999694824 -1.19999694824,3.10000610352 0.0,4.19999694824 c 0.0,0.0 24.0,24.0000152588 24.0,24.0000152588 c 1.19999694824,1.19999694824 3.10000610352,1.19999694824 4.19999694824,0.0 c 0.0,0.0 12.6999969482,-12.700012207 12.6999969482,-12.700012207 c 1.20001220703,-1.19999694824 1.20001220703,-3.09999084473 0.0,-4.19999694824 c 0.0,0.0 -23.8999938965,-24.0 -23.8999938965,-24.0 Z M 2.84999084473,15.5500183105 c 0.0,0.0 -18.6000061035,-18.5000457764 -18.6000061035,-18.5000457764 c 0.0,0.0 12.5999908447,-12.8000030518 12.5999908447,-12.8000030518 c 0.0,0.0 18.6000213623,18.5000457764 18.6000213623,18.5000457764 c 0.0,0.0 -12.6000061035,12.8000030518 -12.6000061035,12.8000030518 Z" - android:fillColor="#FFFFFFFF" - android:fillAlpha="1" /> + android:fillColor="#FFFFFFFF" /> + </group> + </group> + </group> + <group + android:name="arrows" + android:translateX="24" + android:translateY="24" > + <group + android:name="arrows_pivot" + android:translateX="-24.0798" + android:translateY="-24.23" > + <group + android:name="arrows_0" + android:translateX="12.2505" + android:translateY="37.2145" > + <path + android:name="bottom_merged" + android:pathData="M 20.7395019531,-31.9844970703 c 6.23999023438,2.83999633789 10.6999969482,8.7200012207 11.8399963379,15.7799987793 c 0.119995117188,0.699996948242 0.740005493164,1.2200012207 1.46099853516,1.2200012207 c 0.919998168945,0.0 1.6190032959,-0.84001159668 1.47900390625,-1.74000549316 c -1.75900268555,-10.3800048828 -9.75900268555,-19.1199951172 -22.5800018311,-20.1600036621 c -0.919998168945,-0.0800018310547 -1.43899536133,1.04000854492 -0.800003051758,1.70001220703 c 0.0,0.0 5.12100219727,5.11999511719 5.12100219727,5.11999511719 c 0.378997802734,0.380004882812 0.97900390625,0.380004882812 1.37899780273,0.020004272461 c 0.0,0.0 2.10000610352,-1.94000244141 2.10000610352,-1.94000244141 Z M 2.73950195312,6.01550292969 c -6.26000976562,-2.83999633789 -10.7200012207,-8.76000976562 -11.8399963379,-15.8600006104 c -0.118011474609,-0.667007446289 -0.702011108398,-1.15100097656 -1.38000488281,-1.13999938965 c -0.860000610352,0.0 -1.52000427246,0.759994506836 -1.38000488281,1.61999511719 c 1.54000854492,10.4000091553 9.5,19.2200012207 22.4199981689,20.2799987793 c 0.920013427734,0.0800018310547 1.44100952148,-1.03999328613 0.800003051758,-1.69999694824 c 0.0,0.0 -5.11999511719,-5.11999511719 -5.11999511719,-5.11999511719 c -0.380004882812,-0.376007080078 -0.988998413086,-0.385009765625 -1.38000488281,-0.0200042724609 c 0.0,0.0 -2.11999511719,1.94000244141 -2.11999511719,1.94000244141 Z" + android:fillColor="#FFFFFFFF" /> </group> </group> </group> </vector> + diff --git a/packages/SystemUI/res/drawable/ic_portrait_from_auto_rotate_animation.xml b/packages/SystemUI/res/drawable/ic_portrait_from_auto_rotate_animation.xml index 565ef269743d..b8465f4ba6b6 100644 --- a/packages/SystemUI/res/drawable/ic_portrait_from_auto_rotate_animation.xml +++ b/packages/SystemUI/res/drawable/ic_portrait_from_auto_rotate_animation.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2017 The Android Open 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,21 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. --> -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" - android:drawable="@drawable/ic_portrait_from_auto_rotate" > +<animated-vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/ic_portrait_to_auto_rotate" > <target - android:name="arrows" - android:animation="@anim/ic_portrait_from_auto_rotate_animation_arrows" /> + android:name="device_0" + android:animation="@anim/ic_portrait_to_rotate_device_0_animation" /> <target - android:name="arrow_top" - android:animation="@anim/ic_portrait_from_auto_rotate_animation_arrow_top" /> + android:name="device_merged" + android:animation="@anim/ic_portrait_to_rotate_device_merged_animation" /> <target - android:name="arrow_bottom" - android:animation="@anim/ic_portrait_from_auto_rotate_animation_arrow_bottom" /> + android:name="arrows" + android:animation="@anim/ic_portrait_to_rotate_arrows_animation" /> <target - android:name="device" - android:animation="@anim/ic_portrait_from_auto_rotate_animation_device" /> + android:name="arrows_0" + android:animation="@anim/ic_portrait_to_rotate_arrows_0_animation" /> <target - android:name="device_1" - android:animation="@anim/ic_portrait_from_auto_rotate_animation_device_1" /> + android:name="bottom_merged" + android:animation="@anim/ic_portrait_to_rotate_bottom_merged_animation" /> </animated-vector> diff --git a/packages/SystemUI/res/drawable/ic_portrait_to_auto_rotate.xml b/packages/SystemUI/res/drawable/ic_portrait_to_auto_rotate.xml index 88bf486be459..0ef15f0582f7 100644 --- a/packages/SystemUI/res/drawable/ic_portrait_to_auto_rotate.xml +++ b/packages/SystemUI/res/drawable/ic_portrait_to_auto_rotate.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2017 The Android Open 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,53 +14,54 @@ See the License for the specific language governing permissions and limitations under the License. --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" +<vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:name="ic_rotate_to_portrait" android:height="48dp" android:width="48dp" android:viewportHeight="48" android:viewportWidth="48" android:tint="?android:attr/colorControlNormal" > <group - android:name="icon" + android:name="device" android:translateX="24" android:translateY="24" > <group - android:name="icon_pivot" + android:name="device_pivot" android:translateX="-24.15" android:translateY="-24.25" > <group - android:name="arrows" - android:translateX="24.1" - android:translateY="24.1" - android:scaleX="0.9" - android:scaleY="0.9" - android:rotation="-221" > - <group - android:name="arrows_pivot" - android:translateX="-24.1" - android:translateY="-24.1" > - <path - android:name="arrow_top" - android:pathData="M 33.1499938965,5.25 c 6.5,3.10000610352 11.1999969482,9.40000915527 11.8999938965,17.0 c 0.0,0.0 3.00001525879,0.0 3.00001525879,0.0 c -1.00001525879,-12.3000030518 -11.3000030518,-22.0 -23.9000091553,-22.0 c -0.399993896484,0.0 -0.899993896484,0.0 -1.30000305176,0.100006103516 c 0.0,0.0 7.60000610352,7.59999084473 7.60000610352,7.59999084473 c 0.0,0.0 2.69999694824,-2.69999694824 2.69999694824,-2.69999694824 Z" - android:fillColor="#FFFFFFFF" - android:fillAlpha="0" /> - <path - android:name="arrow_bottom" - android:pathData="M 15.1499938965,43.25 c -6.5,-3.09999084473 -11.1999969482,-9.5 -11.8999938965,-17.0 c 0.0,0.0 -3.0,0.0 -3.0,0.0 c 1.0,12.3000030518 11.299987793,22.0 23.8999938965,22.0 c 0.399993896484,0.0 0.899993896484,0.0 1.30000305176,-0.0999908447266 c 0.0,0.0 -7.60000610352,-7.60000610352 -7.60000610352,-7.60000610352 c 0.0,0.0 -2.69999694824,2.69999694824 -2.69999694824,2.69999694824 Z" - android:fillColor="#FFFFFFFF" - android:fillAlpha="0" /> - </group> - </group> - <group - android:name="device" + android:name="device_0" android:translateX="24.14999" android:translateY="24.25" android:rotation="-135" > <path - android:name="device_1" + android:name="device_merged" android:pathData="M -3.34053039551,-22.9980926514 c -1.3207244873,-1.3207244873 -3.46876525879,-1.26383972168 -4.74829101563,0.125762939453 c 0.0,0.0 -14.8512420654,14.7411804199 -14.8512420654,14.7411804199 c -1.39259338379,1.392578125 -1.44947814941,3.54061889648 -0.125762939453,4.74827575684 c 0.0,0.0 26.4143981934,26.4144134521 26.4143981934,26.4144134521 c 1.3207244873,1.3207244873 3.46876525879,1.26382446289 4.74829101562,-0.125762939453 c 0.0,0.0 14.7381896973,-14.7381896973 14.7381896973,-14.7381896973 c 1.392578125,-1.39259338379 1.44947814941,-3.54061889648 0.125762939453,-4.74829101562 c 0.0,0.0 -26.3013458252,-26.417388916 -26.3013458252,-26.417388916 Z M 2.87156677246,16.9857940674 c 0.0,0.0 -19.7573547363,-19.7573699951 -19.7573547363,-19.7573699951 c 0.0,0.0 14.0142059326,-14.2142181396 14.0142059326,-14.2142181396 c 0.0,0.0 19.7573699951,19.7573699951 19.7573699951,19.7573699951 c 0.0,0.0 -14.0142211914,14.2142181396 -14.0142211914,14.2142181396 Z" + android:fillColor="#FFFFFFFF" /> + </group> + </group> + </group> + <group + android:name="arrows" + android:translateX="24" + android:translateY="24" + android:rotation="-221" > + <group + android:name="arrows_pivot" + android:translateX="-24.0798" + android:translateY="-24.23" > + <group + android:name="arrows_0" + android:translateX="12.2505" + android:translateY="37.2145" + android:scaleX="0.9" + android:scaleY="0.9" > + <path + android:name="bottom_merged" + android:pathData="M 20.7395019531,-31.9844970703 c 6.23999023438,2.83999633789 10.6999969482,8.7200012207 11.8399963379,15.7799987793 c 0.119995117188,0.699996948242 0.740005493164,1.2200012207 1.46099853516,1.2200012207 c 0.919998168945,0.0 1.6190032959,-0.84001159668 1.47900390625,-1.74000549316 c -1.75900268555,-10.3800048828 -9.75900268555,-19.1199951172 -22.5800018311,-20.1600036621 c -0.919998168945,-0.0800018310547 -1.43899536133,1.04000854492 -0.800003051758,1.70001220703 c 0.0,0.0 5.12100219727,5.11999511719 5.12100219727,5.11999511719 c 0.378997802734,0.380004882812 0.97900390625,0.380004882812 1.37899780273,0.020004272461 c 0.0,0.0 2.10000610352,-1.94000244141 2.10000610352,-1.94000244141 Z M 2.73950195312,6.01550292969 c -6.26000976562,-2.83999633789 -10.7200012207,-8.76000976562 -11.8399963379,-15.8600006104 c -0.118011474609,-0.667007446289 -0.702011108398,-1.15100097656 -1.38000488281,-1.13999938965 c -0.860000610352,0.0 -1.52000427246,0.759994506836 -1.38000488281,1.61999511719 c 1.54000854492,10.4000091553 9.5,19.2200012207 22.4199981689,20.2799987793 c 0.920013427734,0.0800018310547 1.44100952148,-1.03999328613 0.800003051758,-1.69999694824 c 0.0,0.0 -5.11999511719,-5.11999511719 -5.11999511719,-5.11999511719 c -0.380004882812,-0.376007080078 -0.988998413086,-0.385009765625 -1.38000488281,-0.0200042724609 c 0.0,0.0 -2.11999511719,1.94000244141 -2.11999511719,1.94000244141 Z" android:fillColor="#FFFFFFFF" - android:fillAlpha="1" /> + android:fillAlpha="0" /> </group> </group> </group> diff --git a/packages/SystemUI/res/drawable/ic_portrait_to_auto_rotate_animation.xml b/packages/SystemUI/res/drawable/ic_portrait_to_auto_rotate_animation.xml index f75617f04bef..6d3fd372b222 100644 --- a/packages/SystemUI/res/drawable/ic_portrait_to_auto_rotate_animation.xml +++ b/packages/SystemUI/res/drawable/ic_portrait_to_auto_rotate_animation.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - Copyright (C) 2014 The Android Open Source Project + Copyright (C) 2017 The Android Open 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,21 +14,22 @@ See the License for the specific language governing permissions and limitations under the License. --> -<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" - android:drawable="@drawable/ic_portrait_to_auto_rotate" > +<animated-vector + xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/ic_portrait_from_auto_rotate" > <target - android:name="arrows" - android:animation="@anim/ic_portrait_to_auto_rotate_animation_arrows" /> + android:name="device_0" + android:animation="@anim/ic_rotate_to_portrait_device_0_animation" /> <target - android:name="arrow_top" - android:animation="@anim/ic_portrait_to_auto_rotate_animation_arrow_top" /> + android:name="device_merged" + android:animation="@anim/ic_rotate_to_portrait_device_merged_animation" /> <target - android:name="arrow_bottom" - android:animation="@anim/ic_portrait_to_auto_rotate_animation_arrow_bottom" /> + android:name="arrows" + android:animation="@anim/ic_rotate_to_portrait_arrows_animation" /> <target - android:name="device" - android:animation="@anim/ic_portrait_to_auto_rotate_animation_device" /> + android:name="arrows_0" + android:animation="@anim/ic_rotate_to_portrait_arrows_0_animation" /> <target - android:name="device_1" - android:animation="@anim/ic_portrait_to_auto_rotate_animation_device_1" /> + android:name="bottom_merged" + android:animation="@anim/ic_rotate_to_portrait_bottom_merged_animation" /> </animated-vector> diff --git a/packages/SystemUI/res/interpolator/ic_rotate_to_portrait_animation_interpolator_0.xml b/packages/SystemUI/res/interpolator/ic_rotate_to_portrait_animation_interpolator_0.xml new file mode 100644 index 000000000000..793e7fff11e0 --- /dev/null +++ b/packages/SystemUI/res/interpolator/ic_rotate_to_portrait_animation_interpolator_0.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2017 The Android Open 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. +--> +<pathInterpolator + xmlns:android="http://schemas.android.com/apk/res/android" + android:pathData="M 0.0,0.0 c 0.4,0.0 0.6,1.0 1.0,1.0" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 20531d395fb5..837cb8fcb3ba 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -741,6 +741,8 @@ <string name="quick_settings_done">Done</string> <!-- QuickSettings: Control panel: Label for connected device. [CHAR LIMIT=NONE] --> <string name="quick_settings_connected">Connected</string> + <!-- QuickSettings: Control panel: Label for connected device, showing remote device battery level. [CHAR LIMIT=NONE] --> + <string name="quick_settings_connected_battery_level">Connected, battery <xliff:g id="battery_level_as_percentage">%1$s</xliff:g></string> <!-- QuickSettings: Control panel: Label for connecting device. [CHAR LIMIT=NONE] --> <string name="quick_settings_connecting">Connecting...</string> <!-- QuickSettings: Tethering. [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index 7d76b9b7c944..68fe9a83c707 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -952,6 +952,10 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } + public boolean isDrawingAppearAnimation() { + return mDrawingAppearAnimation; + } + @Override protected void dispatchDraw(Canvas canvas) { if (mDrawingAppearAnimation) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 429d5ab6f1ab..8046250c9412 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -756,6 +756,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return getShowingLayout().getVisibleNotificationHeader(); } + + /** + * @return the contracted notification header. This can be different from + * {@link #getNotificationHeader()} and also {@link #getVisibleNotificationHeader()} and only + * returns the contracted version. + */ + public NotificationHeaderView getContractedNotificationHeader() { + if (mIsSummaryWithChildren) { + return mChildrenContainer.getHeaderView(); + } + return mPrivateLayout.getContractedNotificationHeader(); + } + public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) { mOnExpandClickListener = onExpandClickListener; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 75704b1612d7..9e059c89ffe1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -1308,6 +1308,14 @@ public class NotificationContentView extends FrameLayout { return header; } + + public NotificationHeaderView getContractedNotificationHeader() { + if (mContractedChild != null) { + return mContractedWrapper.getNotificationHeader(); + } + return null; + } + public NotificationHeaderView getVisibleNotificationHeader() { NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType); return wrapper == null ? null : wrapper.getNotificationHeader(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java index 7f95d48f36d4..43018174de44 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java @@ -267,9 +267,10 @@ public class NotificationHeaderUtil { if (!mApply) { return; } - NotificationHeaderView header = row.getNotificationHeader(); + NotificationHeaderView header = row.getContractedNotificationHeader(); if (header == null) { - mApply = false; + // No header found. We still consider this to be the same to avoid weird flickering + // when for example showing an undo notification return; } Object childData = mExtractor == null ? null : mExtractor.extractData(row); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 7abceaf41c70..a60102854618 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -505,6 +505,11 @@ public class NotificationShelf extends ActivatableNotificationView implements iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale(); iconState.scaleY = iconState.scaleX; iconState.hidden = transitionAmount == 0.0f && !iconState.isAnimating(icon); + boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf(); + if (isAppearing) { + iconState.hidden = true; + iconState.iconAppearAmount = 0.0f; + } iconState.alpha = alpha; iconState.yTranslation = iconYTranslation; if (stayingInShelf) { 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 18dc7e2fa35b..426444158b11 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -613,7 +613,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav if (!updateCurrentView()) { return; } - Log.d(TAG, "reorient", new Throwable()); mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone); if (getRootView() instanceof NavigationBarFrame) { diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index c6e3ed5f30ae..c1a1901f762e 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -4190,6 +4190,8 @@ message MetricsEvent { // OS: O DR FIELD_TIME_ELAPSED_BETWEEN_CHARGE_MS = 1030; + // ---- End O-DR1 Constants, all O-DR1 constants go above this line ---- + // Add new aosp constants above this line. // END OF AOSP CONSTANTS } diff --git a/services/core/Android.mk b/services/core/Android.mk index 2e962d534e67..3ec78f4cd58a 100644 --- a/services/core/Android.mk +++ b/services/core/Android.mk @@ -46,4 +46,16 @@ LOCAL_JACK_FLAGS := \ -D jack.transformations.boost-locked-region-priority.request=com.android.server.am.ActivityManagerService\#boostPriorityForLockedSection,com.android.server.wm.WindowManagerService\#boostPriorityForLockedSection \ -D jack.transformations.boost-locked-region-priority.reset=com.android.server.am.ActivityManagerService\#resetPriorityAfterLockedSection,com.android.server.wm.WindowManagerService\#resetPriorityAfterLockedSection +LOCAL_JAR_PROCESSOR := lockedregioncodeinjection +# Use = instead of := to delay evaluation of ${in} and ${out} +LOCAL_JAR_PROCESSOR_ARGS = \ + --targets \ + "Lcom/android/server/am/ActivityManagerService;,Lcom/android/server/wm/WindowHashMap;" \ + --pre \ + "com/android/server/am/ActivityManagerService.boostPriorityForLockedSection,com/android/server/wm/WindowManagerService.boostPriorityForLockedSection" \ + --post \ + "com/android/server/am/ActivityManagerService.resetPriorityAfterLockedSection,com/android/server/wm/WindowManagerService.resetPriorityAfterLockedSection" \ + -o ${out} \ + -i ${in} + include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index f440100b7621..3a9bf1258d12 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -91,7 +91,6 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_URI_PERMISSION = DEBUG_ALL || false; static final boolean DEBUG_USER_LEAVING = DEBUG_ALL || false; static final boolean DEBUG_VISIBILITY = DEBUG_ALL || false; - static final boolean DEBUG_VISIBLE_BEHIND = DEBUG_ALL_ACTIVITIES || false; static final boolean DEBUG_USAGE_STATS = DEBUG_ALL || false; static final boolean DEBUG_PERMISSIONS_REVIEW = DEBUG_ALL || false; static final boolean DEBUG_WHITELISTS = DEBUG_ALL || false; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 209c6be5dea8..3566c2962727 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -136,7 +136,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVE import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_URI_PERMISSION; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBILITY; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_WHITELISTS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST; @@ -1500,11 +1499,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean mControllerIsAMonkey = false; String mProfileApp = null; ProcessRecord mProfileProc = null; - String mProfileFile; - ParcelFileDescriptor mProfileFd; - int mSamplingInterval = 0; - boolean mAutoStopProfiler = false; - boolean mStreamingOutput = false; + ProfilerInfo mProfilerInfo = null; int mProfileType = 0; final ProcessMap<Pair<Long, String>> mMemWatchProcesses = new ProcessMap<>(); String mMemWatchDumpProcName; @@ -6910,19 +6905,19 @@ public class ActivityManagerService extends IActivityManager.Stub mWaitForDebugger = mOrigWaitForDebugger; } } - String profileFile = app.instr != null ? app.instr.mProfileFile : null; - ParcelFileDescriptor profileFd = null; - int samplingInterval = 0; - boolean profileAutoStop = false; - boolean profileStreamingOutput = false; + + ProfilerInfo profilerInfo = null; + String agent = null; if (mProfileApp != null && mProfileApp.equals(processName)) { mProfileProc = app; - profileFile = mProfileFile; - profileFd = mProfileFd; - samplingInterval = mSamplingInterval; - profileAutoStop = mAutoStopProfiler; - profileStreamingOutput = mStreamingOutput; + profilerInfo = (mProfilerInfo != null && mProfilerInfo.profileFile != null) ? + new ProfilerInfo(mProfilerInfo) : null; + agent = profilerInfo.agent; + } else if (app.instr != null && app.instr.mProfileFile != null) { + profilerInfo = new ProfilerInfo(app.instr.mProfileFile, null, 0, false, false, + null); } + boolean enableTrackAllocation = false; if (mTrackAllocationApp != null && mTrackAllocationApp.equals(processName)) { enableTrackAllocation = true; @@ -6946,12 +6941,10 @@ public class ActivityManagerService extends IActivityManager.Stub + processName + " with config " + getGlobalConfiguration()); ApplicationInfo appInfo = app.instr != null ? app.instr.mTargetInfo : app.info; app.compat = compatibilityInfoForPackageLocked(appInfo); - if (profileFd != null) { - profileFd = profileFd.dup(); + + if (profilerInfo != null && profilerInfo.profileFd != null) { + profilerInfo.profileFd = profilerInfo.profileFd.dup(); } - ProfilerInfo profilerInfo = profileFile == null ? null - : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop, - profileStreamingOutput); // We deprecated Build.SERIAL and it is not accessible to // apps that target the v2 security sandbox. Since access to @@ -6991,6 +6984,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } + // If we were asked to attach an agent on startup, do so now, before we're binding + // application code. + if (agent != null) { + thread.attachAgent(agent); + } + checkTime(startTime, "attachApplicationLocked: immediately before bindApplication"); mStackSupervisor.mActivityMetricsLogger.notifyBindApplication(app); if (app.instr != null) { @@ -7126,11 +7125,7 @@ public class ActivityManagerService extends IActivityManager.Stub mStackSupervisor.activityIdleInternalLocked(token, false /* fromTimeout */, false /* processPausingActivities */, config); if (stopProfiling) { - if ((mProfileProc == r.app) && (mProfileFd != null)) { - try { - mProfileFd.close(); - } catch (IOException e) { - } + if ((mProfileProc == r.app) && mProfilerInfo != null) { clearProfilerLocked(); } } @@ -7404,21 +7399,6 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public final void backgroundResourcesReleased(IBinder token) { - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (this) { - ActivityStack stack = ActivityRecord.getStackLocked(token); - if (stack != null) { - stack.backgroundResourcesReleased(); - } - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - @Override public final void notifyLaunchTaskBehindComplete(IBinder token) { mStackSupervisor.scheduleLaunchTaskBehindComplete(token); } @@ -12754,18 +12734,16 @@ public class ActivityManagerService extends IActivityManager.Stub } } mProfileApp = processName; - mProfileFile = profilerInfo.profileFile; - if (mProfileFd != null) { - try { - mProfileFd.close(); - } catch (IOException e) { + + if (mProfilerInfo != null) { + if (mProfilerInfo.profileFd != null) { + try { + mProfilerInfo.profileFd.close(); + } catch (IOException e) { + } } - mProfileFd = null; } - mProfileFd = profilerInfo.profileFd; - mSamplingInterval = profilerInfo.samplingInterval; - mAutoStopProfiler = profilerInfo.autoStopProfiler; - mStreamingOutput = profilerInfo.streamingOutput; + mProfilerInfo = new ProfilerInfo(profilerInfo); mProfileType = 0; } } @@ -13303,7 +13281,6 @@ public class ActivityManagerService extends IActivityManager.Stub } final boolean translucentChanged = r.changeWindowTranslucency(true); if (translucentChanged) { - r.getStack().releaseBackgroundResources(r); mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); } mWindowManager.setAppFullscreen(token, true); @@ -13343,38 +13320,6 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public boolean requestVisibleBehind(IBinder token, boolean visible) { - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (this) { - final ActivityRecord r = ActivityRecord.isInStackLocked(token); - if (r != null) { - return mStackSupervisor.requestVisibleBehindLocked(r, visible); - } - } - return false; - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - @Override - public boolean isBackgroundVisibleBehind(IBinder token) { - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (this) { - final ActivityStack stack = ActivityRecord.getStackLocked(token); - final boolean visible = stack == null ? false : stack.hasVisibleBehindActivity(); - if (DEBUG_VISIBLE_BEHIND) Slog.d(TAG_VISIBLE_BEHIND, - "isBackgroundVisibleBehind: stack=" + stack + " visible=" + visible); - return visible; - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - @Override public Bundle getActivityOptions(IBinder token) { final long origId = Binder.clearCallingIdentity(); try { @@ -15928,18 +15873,22 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(" mTrackAllocationApp=" + mTrackAllocationApp); } } - if (mProfileApp != null || mProfileProc != null || mProfileFile != null - || mProfileFd != null) { + if (mProfileApp != null || mProfileProc != null || (mProfilerInfo != null && + (mProfilerInfo.profileFile != null || mProfilerInfo.profileFd != null))) { if (dumpPackage == null || dumpPackage.equals(mProfileApp)) { if (needSep) { pw.println(); needSep = false; } pw.println(" mProfileApp=" + mProfileApp + " mProfileProc=" + mProfileProc); - pw.println(" mProfileFile=" + mProfileFile + " mProfileFd=" + mProfileFd); - pw.println(" mSamplingInterval=" + mSamplingInterval + " mAutoStopProfiler=" - + mAutoStopProfiler + " mStreamingOutput=" + mStreamingOutput); - pw.println(" mProfileType=" + mProfileType); + if (mProfilerInfo != null) { + pw.println(" mProfileFile=" + mProfilerInfo.profileFile + " mProfileFd=" + + mProfilerInfo.profileFd); + pw.println(" mSamplingInterval=" + mProfilerInfo.samplingInterval + + " mAutoStopProfiler=" + mProfilerInfo.autoStopProfiler + + " mStreamingOutput=" + mProfilerInfo.streamingOutput); + pw.println(" mProfileType=" + mProfileType); + } } } if (mNativeDebuggingApp != null) { @@ -23258,19 +23207,15 @@ public class ActivityManagerService extends IActivityManager.Stub } private void clearProfilerLocked() { - if (mProfileFd != null) { + if (mProfilerInfo !=null && mProfilerInfo.profileFd != null) { try { - mProfileFd.close(); + mProfilerInfo.profileFd.close(); } catch (IOException e) { } } mProfileApp = null; mProfileProc = null; - mProfileFile = null; - mProfileType = 0; - mAutoStopProfiler = false; - mStreamingOutput = false; - mSamplingInterval = 0; + mProfilerInfo = null; } public boolean profileControl(String process, int userId, boolean start, @@ -23314,10 +23259,10 @@ public class ActivityManagerService extends IActivityManager.Stub proc.thread.profilerControl(start, profilerInfo, profileType); fd = null; try { - mProfileFd.close(); + mProfilerInfo.profileFd.close(); } catch (IOException e) { } - mProfileFd = null; + mProfilerInfo.profileFd = null; } else { stopProfilerLocked(proc, profileType); if (profilerInfo != null && profilerInfo.profileFd != null) { @@ -24496,4 +24441,37 @@ public class ActivityManagerService extends IActivityManager.Stub return mNmi != null; } } + + @Override + public void setShowWhenLocked(IBinder token, boolean showWhenLocked) + throws RemoteException { + synchronized (this) { + final ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r == null) { + return; + } + final long origId = Binder.clearCallingIdentity(); + try { + r.setShowWhenLocked(showWhenLocked); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } + + @Override + public void setTurnScreenOn(IBinder token, boolean turnScreenOn) throws RemoteException { + synchronized (this) { + final ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r == null) { + return; + } + final long origId = Binder.clearCallingIdentity(); + try { + r.setTurnScreenOn(turnScreenOn); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index d7683047143a..f0a886dfcb08 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -114,6 +114,7 @@ final class ActivityManagerShellCommand extends ShellCommand { private int mSamplingInterval; private boolean mAutoStop; private boolean mStreaming; // Streaming the profiling output to a file. + private String mAgent; // Agent to attach on startup. private int mDisplayId; private int mStackId; private int mTaskId; @@ -292,6 +293,8 @@ final class ActivityManagerShellCommand extends ShellCommand { mSamplingInterval = Integer.parseInt(getNextArgRequired()); } else if (opt.equals("--streaming")) { mStreaming = true; + } else if (opt.equals("--attach-agent")) { + mAgent = getNextArgRequired(); } else if (opt.equals("-R")) { mRepeat = Integer.parseInt(getNextArgRequired()); } else if (opt.equals("-S")) { @@ -368,13 +371,16 @@ final class ActivityManagerShellCommand extends ShellCommand { ProfilerInfo profilerInfo = null; - if (mProfileFile != null) { - ParcelFileDescriptor fd = openOutputFileForSystem(mProfileFile); - if (fd == null) { - return 1; + if (mProfileFile != null || mAgent != null) { + ParcelFileDescriptor fd = null; + if (mProfileFile != null) { + fd = openOutputFileForSystem(mProfileFile); + if (fd == null) { + return 1; + } } profilerInfo = new ProfilerInfo(mProfileFile, fd, mSamplingInterval, mAutoStop, - mStreaming); + mStreaming, mAgent); } pw.println("Starting: " + intent); @@ -747,7 +753,8 @@ final class ActivityManagerShellCommand extends ShellCommand { if (fd == null) { return -1; } - profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming); + profilerInfo = new ProfilerInfo(profileFile, fd, mSamplingInterval, false, mStreaming, + null); } try { @@ -2490,6 +2497,7 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" --streaming: stream the profiling output to the specified file"); pw.println(" (use with --start-profiler)"); pw.println(" -P <FILE>: like above, but profiling stops when app goes idle"); + pw.println(" --attach-agent <agent>: attach the given agent before binding"); 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"); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index c7cac3be5bc9..4b2b6a77deb6 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -47,11 +47,13 @@ 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.CONFIG_UI_MODE; import static android.content.pm.ActivityInfo.FLAG_ALWAYS_FOCUSABLE; +import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED; import static android.content.pm.ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; import static android.content.pm.ActivityInfo.FLAG_IMMERSIVE; import static android.content.pm.ActivityInfo.FLAG_MULTIPROCESS; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; import static android.content.pm.ActivityInfo.FLAG_STATE_NOT_NEEDED; +import static android.content.pm.ActivityInfo.FLAG_TURN_SCREEN_ON; import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; @@ -344,6 +346,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo // handle calculating override configuration from the bounds. private final Rect mBounds = new Rect(); + private boolean mShowWhenLocked; + private boolean mTurnScreenOn; + /** * Temp configs used in {@link #ensureActivityConfigurationLocked(int, boolean)} */ @@ -904,6 +909,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo requestedVrComponent = (aInfo.requestedVrComponent == null) ? null : ComponentName.unflattenFromString(aInfo.requestedVrComponent); + + mShowWhenLocked = (aInfo.flags & FLAG_SHOW_WHEN_LOCKED) != 0; + mTurnScreenOn = (aInfo.flags & FLAG_TURN_SCREEN_ON) != 0; } AppWindowContainerController getWindowContainerController() { @@ -1258,13 +1266,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo return (info.flags & FLAG_ALWAYS_FOCUSABLE) != 0; } - /** - * @return true if the activity contains windows that have - * {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} set - */ - boolean hasShowWhenLockedWindows() { - return service.mWindowManager.containsShowWhenLockedWindow(appToken); - } /** * @return true if the activity contains windows that have @@ -1275,20 +1276,16 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } void makeFinishingLocked() { - if (!finishing) { - 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); - } - finishing = true; - if (stopped) { - clearOptionsLocked(); - } + if (finishing) { + return; + } + finishing = true; + if (stopped) { + clearOptionsLocked(); + } - if (service != null) { - service.mTaskChangeNotificationController.notifyTaskStackChanged(); - } + if (service != null) { + service.mTaskChangeNotificationController.notifyTaskStackChanged(); } } @@ -1345,11 +1342,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo intent, getUriPermissionsLocked(), userId); final ReferrerIntent rintent = new ReferrerIntent(intent, referrer); boolean unsent = true; - final ActivityStack stack = getStack(); - final boolean isTopActivityInStack = - stack != null && stack.topRunningActivityLocked() == this; - final boolean isTopActivityWhileSleeping = - service.isSleepingLocked() && isTopActivityInStack; + final boolean isTopActivityWhileSleeping = service.isSleepingLocked() && isTopRunningActivity(); // We want to immediately deliver the intent to the activity if: // - It is currently resumed or paused. i.e. it is currently visible to the user and we want @@ -1621,20 +1614,12 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo * * @see {@link ActivityStack#checkKeyguardVisibility} */ - boolean shouldBeVisibleIgnoringKeyguard(boolean behindTranslucentActivity, - boolean stackVisibleBehind, ActivityRecord visibleBehind, - boolean behindFullscreenActivity) { + boolean shouldBeVisibleIgnoringKeyguard(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; + boolean isVisible = !behindFullscreenActivity || mLaunchTaskBehind; if (service.mSupportsLeanbackOnly && isVisible && isRecentsActivity()) { // On devices that support leanback only (Android TV), Recents activity can only be @@ -1746,11 +1731,14 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo returningOptions = null; - if (stack.getVisibleBehindActivity() == this) { - // When resuming an activity, require it to call requestVisibleBehind() again. - stack.setVisibleBehindActivity(null /* ActivityRecord */); + if (canTurnScreenOn()) { + mStackSupervisor.wakeUp("turnScreenOnFlag"); + } else { + // If the screen is going to turn on because the caller explicitly requested it and + // the keyguard is not showing don't attempt to sleep. Otherwise the Activity will + // pause and then resume again later, which will result in a double life-cycle event. + mStackSupervisor.checkReadyForSleepLocked(); } - mStackSupervisor.checkReadyForSleepLocked(); } final void activityStoppedLocked(Bundle newIcicle, PersistableBundle newPersistentState, @@ -1783,9 +1771,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo mWindowContainerController.notifyAppStopped(); - if (stack.getVisibleBehindActivity() == this) { - mStackSupervisor.requestVisibleBehindLocked(this, false /* visible */); - } if (finishing) { clearOptionsLocked(); } else { @@ -2817,6 +2802,44 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo return info.applicationInfo.uid; } + void setShowWhenLocked(boolean showWhenLocked) { + mShowWhenLocked = showWhenLocked; + } + + /** + * @return true if the activity contains windows that have + * {@link LayoutParams#FLAG_SHOW_WHEN_LOCKED} set or if the activity has set + * {@link #mShowWhenLocked}. + */ + boolean canShowWhenLocked() { + return mShowWhenLocked || service.mWindowManager.containsShowWhenLockedWindow(appToken); + } + + void setTurnScreenOn(boolean turnScreenOn) { + mTurnScreenOn = turnScreenOn; + } + + /** + * Determines whether this ActivityRecord can turn the screen on. It checks whether the flag + * {@link #mTurnScreenOn} is set and checks whether the ActivityRecord should be visible + * depending on Keyguard state + * + * @return true if the screen can be turned on, false otherwise. + */ + boolean canTurnScreenOn() { + final ActivityStack stack = getStack(); + return mTurnScreenOn && stack != null && + stack.checkKeyguardVisibility(this, true /* shouldBeVisible */, true /* isTop */); + } + + boolean getTurnScreenOnFlag() { + return mTurnScreenOn; + } + + boolean isTopRunningActivity() { + return mStackSupervisor.topRunningActivityLocked() == this; + } + @Override public String toString() { if (stringName != null) { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 9cde98598987..77e438a7c610 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -230,9 +230,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai static final int STACK_INVISIBLE = 0; // Stack is considered visible 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; @VisibleForTesting /* The various modes for the method {@link #removeTask}. */ @@ -365,8 +362,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai static final int STOP_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 4; static final int DESTROY_ACTIVITIES_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 5; static final int TRANSLUCENT_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 6; - static final int RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG = - ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 7; private static class ScheduleDestroyArgs { final ProcessRecord mOwner; @@ -440,15 +435,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai notifyActivityDrawnLocked(null); } } break; - case RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG: { - synchronized (mService) { - final ActivityRecord r = getVisibleBehindActivity(); - Slog.e(TAG, "Timeout waiting for cancelVisibleBehind player=" + r); - if (r != null) { - mService.killAppAtUsersRequest(r.app, null); - } - } - } break; } } } @@ -1195,15 +1181,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Sleep still waiting to pause " + mPausingActivity); return true; } - - if (hasVisibleBehindActivity()) { - // Stop visible behind activity before going to sleep. - final ActivityRecord r = getVisibleBehindActivity(); - mStackSupervisor.mStoppingActivities.add(r); - if (DEBUG_STATES) Slog.v(TAG_STATES, "Sleep still waiting to stop visible behind " + r); - return true; - } - return false; } @@ -1422,8 +1399,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // We can't clobber it, because the stop confirmation will not be handled. // We don't need to schedule another stop, we only need to let it happen. prev.state = STOPPING; - } else if ((!prev.visible && !hasVisibleBehindActivity()) - || mService.isSleepingOrShuttingDownLocked()) { + } else if (!prev.visible || mService.isSleepingOrShuttingDownLocked()) { // Clear out any deferred client hide we might currently have. prev.setDeferHidingClient(false); // If we were visible then resumeTopActivities will release resources before @@ -1628,8 +1604,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } /** - * Returns what the stack visibility should be: {@link #STACK_INVISIBLE}, {@link #STACK_VISIBLE} - * or {@link #STACK_VISIBLE_ACTIVITY_BEHIND}. + * Returns what the stack visibility should be: {@link #STACK_INVISIBLE} or + * {@link #STACK_VISIBLE}. * * @param starting The currently starting activity or null if there is none. */ @@ -1654,14 +1630,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final ActivityStack topStack = getTopStackOnDisplay(); final int topStackId = topStack.mStackId; - if (StackId.isBackdropToTranslucentActivity(mStackId) - && hasVisibleBehindActivity() && StackId.isHomeOrRecentsStack(topStackId) - && (topStack.topActivity() == null || !topStack.topActivity().fullscreen)) { - // The fullscreen or assistant stack should be visible if it has a visible behind - // activity behind the home or recents stack that is translucent. - return STACK_VISIBLE_ACTIVITY_BEHIND; - } - if (mStackId == DOCKED_STACK_ID) { // If the assistant stack is focused and translucent, then the docked stack is always // visible @@ -1780,12 +1748,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai boolean aboveTop = top != null; final int stackVisibility = shouldBeVisible(starting); final boolean stackInvisible = stackVisibility != STACK_VISIBLE; - final boolean stackVisibleBehind = stackVisibility == STACK_VISIBLE_ACTIVITY_BEHIND; boolean behindFullscreenActivity = stackInvisible; boolean resumeNextActivity = mStackSupervisor.isFocusedStack(this) && (isInStackLocked(starting) == null); boolean behindTranslucentActivity = false; - final ActivityRecord visibleBehind = getVisibleBehindActivity(); for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { final TaskRecord task = mTaskHistory.get(taskNdx); final ArrayList<ActivityRecord> activities = task.mActivities; @@ -1810,7 +1776,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // Check whether activity should be visible without Keyguard influence final boolean visibleIgnoringKeyguard = r.shouldBeVisibleIgnoringKeyguard( - behindTranslucentActivity, stackVisibleBehind, visibleBehind, behindFullscreenActivity); r.visibleIgnoringKeyguard = visibleIgnoringKeyguard; @@ -1862,7 +1827,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai + stackInvisible + " behindFullscreenActivity=" + behindFullscreenActivity + " mLaunchTaskBehind=" + r.mLaunchTaskBehind); - makeInvisible(r, visibleBehind); + makeInvisible(r); } } if (mStackId == FREEFORM_WORKSPACE_STACK_ID) { @@ -1942,7 +1907,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final boolean isInPinnedStack = r.getStack().getStackId() == PINNED_STACK_ID; final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing(); final boolean keyguardLocked = mStackSupervisor.mKeyguardController.isKeyguardLocked(); - final boolean showWhenLocked = r.hasShowWhenLockedWindows() && !isInPinnedStack; + final boolean showWhenLocked = r.canShowWhenLocked() && !isInPinnedStack; final boolean dismissKeyguard = r.hasDismissKeyguardWindows(); if (shouldBeVisible) { if (dismissKeyguard && mTopDismissingKeyguardActivity == null) { @@ -2028,7 +1993,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return false; } - private void makeInvisible(ActivityRecord r, ActivityRecord visibleBehind) { + // TODO: Should probably be moved into ActivityRecord. + private void makeInvisible(ActivityRecord r) { if (!r.visible) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r); return; @@ -2064,14 +2030,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai case RESUMED: case PAUSING: case PAUSED: - // This case created for transitioning activities from - // translucent to opaque {@link Activity#convertToOpaque}. - if (visibleBehind == r) { - releaseBackgroundResources(r); - } else { - addToStopping(r, true /* scheduleIdle */, - canEnterPictureInPicture /* idleDelayed */); - } + addToStopping(r, true /* scheduleIdle */, + canEnterPictureInPicture /* idleDelayed */); break; default: @@ -2216,12 +2176,18 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } finally { mStackSupervisor.inResumeTopActivity = false; } + // When resuming the top activity, it may be necessary to pause the top activity (for // example, returning to the lock screen. We suppress the normal pause logic in // {@link #resumeTopActivityUncheckedLocked}, since the top activity is resumed at the end. // We call the {@link ActivityStackSupervisor#checkReadyForSleepLocked} again here to ensure - // any necessary pause logic occurs. - mStackSupervisor.checkReadyForSleepLocked(); + // any necessary pause logic occurs. In the case where the Activity will be shown regardless + // of the lock screen, the call to {@link ActivityStackSupervisor#checkReadyForSleepLocked} + // is skipped. + final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */); + if (next == null || !next.canTurnScreenOn()) { + mStackSupervisor.checkReadyForSleepLocked(); + } return result; } @@ -3991,10 +3957,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // Get rid of any pending idle timeouts. removeTimeoutsForActivityLocked(r); - if (getVisibleBehindActivity() == r) { - mStackSupervisor.requestVisibleBehindLocked(r, false); - } - // Clean-up activities are no longer relaunching (e.g. app process died). Notify window // manager so it can update its bookkeeping. mWindowManager.notifyAppRelaunchesCleared(r.appToken); @@ -4298,56 +4260,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } - void releaseBackgroundResources(ActivityRecord r) { - if (hasVisibleBehindActivity() && - !mHandler.hasMessages(RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG)) { - if (r == topRunningActivityLocked() - && shouldBeVisible(null) == STACK_VISIBLE) { - // Don't release the top activity if it has requested to run behind the next - // activity and the stack is currently visible. - return; - } - if (DEBUG_STATES) Slog.d(TAG_STATES, "releaseBackgroundResources activtyDisplay=" + - mActivityContainer.mActivityDisplay + " visibleBehind=" + r + " app=" + r.app + - " thread=" + r.app.thread); - if (r != null && r.app != null && r.app.thread != null) { - try { - r.app.thread.scheduleCancelVisibleBehind(r.appToken); - } catch (RemoteException e) { - } - mHandler.sendEmptyMessageDelayed(RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG, 500); - } else { - Slog.e(TAG, "releaseBackgroundResources: activity " + r + " no longer running"); - backgroundResourcesReleased(); - } - } - } - - final void backgroundResourcesReleased() { - mHandler.removeMessages(RELEASE_BACKGROUND_RESOURCES_TIMEOUT_MSG); - final ActivityRecord r = getVisibleBehindActivity(); - if (r != null) { - mStackSupervisor.mStoppingActivities.add(r); - setVisibleBehindActivity(null); - mStackSupervisor.scheduleIdleTimeoutLocked(null); - } - mStackSupervisor.resumeFocusedStackTopActivityLocked(); - } - - boolean hasVisibleBehindActivity() { - return isAttached() && mActivityContainer.mActivityDisplay.hasVisibleBehindActivity(); - } - - void setVisibleBehindActivity(ActivityRecord r) { - if (isAttached()) { - mActivityContainer.mActivityDisplay.setVisibleBehindActivity(r); - } - } - - ActivityRecord getVisibleBehindActivity() { - return isAttached() ? mActivityContainer.mActivityDisplay.mVisibleBehindActivity : null; - } - private void removeHistoryRecordsForAppLocked(ArrayList<ActivityRecord> list, ProcessRecord app, String listName) { int i = list.size(); @@ -4609,28 +4521,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to back transition: task=" + taskId); - if (mStackId == HOME_STACK_ID && topTask().isHomeTask()) { - // For the case where we are moving the home task back and there is an activity visible - // behind it on the fullscreen or assistant stack, we want to move the focus to the - // visible behind activity to maintain order with what the user is seeing. - ActivityRecord visibleBehind = null; - final ActivityStack fullscreenStack = - mStackSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID); - final ActivityStack assistantStack = - mStackSupervisor.getStack(ASSISTANT_STACK_ID); - if (fullscreenStack != null && fullscreenStack.hasVisibleBehindActivity()) { - visibleBehind = fullscreenStack.getVisibleBehindActivity(); - } else if (assistantStack != null && assistantStack.hasVisibleBehindActivity()) { - visibleBehind = assistantStack.getVisibleBehindActivity(); - } - if (visibleBehind != null) { - mStackSupervisor.moveFocusableActivityStackToFrontLocked(visibleBehind, - "moveTaskToBack"); - mStackSupervisor.resumeFocusedStackTopActivityLocked(); - return true; - } - } - boolean prevIsHome = false; // If true, we should resume the home activity next if the task we are moving to the diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index e0f2a751604a..90d9149a2f66 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -40,6 +40,7 @@ import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; +import static android.os.PowerManager.PARTIAL_WAKE_LOCK; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRIVATE; @@ -60,7 +61,6 @@ 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; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONTAINERS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IDLE; @@ -557,6 +557,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final KeyguardController mKeyguardController; + private PowerManager mPowerManager; + private int mDeferResumeCount; + /** * Description of a request to start a new activity, which has been held * due to app switches being disabled. @@ -605,9 +608,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * initialized. So we initialize our wakelocks afterwards. */ void initPowerManagement() { - PowerManager pm = (PowerManager)mService.mContext.getSystemService(Context.POWER_SERVICE); - mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); - mLaunchingActivity = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*launch*"); + mPowerManager = (PowerManager)mService.mContext.getSystemService(Context.POWER_SERVICE); + mGoingToSleep = mPowerManager + .newWakeLock(PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); + mLaunchingActivity = mPowerManager.newWakeLock(PARTIAL_WAKE_LOCK, "*launch*"); mLaunchingActivity.setReferenceCounted(false); } @@ -1335,184 +1339,187 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return false; } - r.startFreezingScreenLocked(app, 0); - if (r.getStack().checkKeyguardVisibility(r, true /* shouldBeVisible */, true /* isTop */)) { - // We only set the visibility to true if the activity is allowed to be visible based on - // keyguard state. This avoids setting this into motion in window manager that is later - // cancelled due to later calls to ensure visible activities that set visibility back to - // false. - r.setVisibility(true); - } + final TaskRecord task = r.getTask(); + final ActivityStack stack = task.getStack(); - // schedule launch ticks to collect information about slow apps. - r.startLaunchTickingLocked(); + beginDeferResume(); - // 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) { - 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.updateDisplayOverrideConfigurationLocked(config, r, true /* deferResume */, - displayId); - } + try { + r.startFreezingScreenLocked(app, 0); - if (mKeyguardController.isKeyguardLocked()) { - r.notifyUnknownVisibilityLaunched(); - } - final int applicationInfoUid = - (r.info.applicationInfo != null) ? r.info.applicationInfo.uid : -1; - if ((r.userId != app.userId) || (r.appInfo.uid != applicationInfoUid)) { - Slog.wtf(TAG, - "User ID for activity changing for " + r - + " appInfo.uid=" + r.appInfo.uid - + " info.ai.uid=" + applicationInfoUid - + " old=" + r.app + " new=" + app); - } + // schedule launch ticks to collect information about slow apps. + r.startLaunchTickingLocked(); - r.app = app; - app.waitingToKill = null; - r.launchCount++; - r.lastLaunchTime = SystemClock.uptimeMillis(); + r.app = app; - if (DEBUG_ALL) Slog.v(TAG, "Launching: " + r); + // 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) { + 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.updateDisplayOverrideConfigurationLocked(config, r, true /* deferResume */, + displayId); + } - int idx = app.activities.indexOf(r); - if (idx < 0) { - app.activities.add(r); - } - mService.updateLruProcessLocked(app, true, null); - mService.updateOomAdjLocked(); + if (r.getStack().checkKeyguardVisibility(r, true /* shouldBeVisible */, + true /* isTop */)) { + // We only set the visibility to true if the activity is allowed to be visible + // based on + // keyguard state. This avoids setting this into motion in window manager that is + // later cancelled due to later calls to ensure visible activities that set + // visibility back to false. + r.setVisibility(true); + } - final TaskRecord task = r.getTask(); - if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE || - task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV) { - setLockTaskModeLocked(task, LOCK_TASK_MODE_LOCKED, "mLockTaskAuth==LAUNCHABLE", false); - } + if (mKeyguardController.isKeyguardLocked()) { + r.notifyUnknownVisibilityLaunched(); + } + final int applicationInfoUid = + (r.info.applicationInfo != null) ? r.info.applicationInfo.uid : -1; + if ((r.userId != app.userId) || (r.appInfo.uid != applicationInfoUid)) { + Slog.wtf(TAG, + "User ID for activity changing for " + r + + " appInfo.uid=" + r.appInfo.uid + + " info.ai.uid=" + applicationInfoUid + + " old=" + r.app + " new=" + app); + } - final ActivityStack stack = task.getStack(); - try { - if (app.thread == null) { - throw new RemoteException(); - } - List<ResultInfo> results = null; - List<ReferrerIntent> newIntents = null; - if (andResume) { - // We don't need to deliver new intents and/or set results if activity is going - // to pause immediately after launch. - results = r.results; - newIntents = r.newIntents; - } - if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, - "Launching: " + r + " icicle=" + r.icicle + " with results=" + results - + " newIntents=" + newIntents + " andResume=" + andResume); - EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY, r.userId, - System.identityHashCode(r), task.taskId, r.shortComponentName); - if (r.isHomeActivity()) { - // Home process is the root process of the task. - mService.mHomeProcess = task.mActivities.get(0).app; - } - mService.notifyPackageUse(r.intent.getComponent().getPackageName(), - PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY); - r.sleeping = false; - r.forceNewConfig = false; - mService.showUnsupportedZoomDialogIfNeededLocked(r); - mService.showAskCompatModeDialogLocked(r); - r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo); - ProfilerInfo profilerInfo = null; - if (mService.mProfileApp != null && mService.mProfileApp.equals(app.processName)) { - if (mService.mProfileProc == null || mService.mProfileProc == app) { - mService.mProfileProc = app; - final String profileFile = mService.mProfileFile; - if (profileFile != null) { - ParcelFileDescriptor profileFd = mService.mProfileFd; - if (profileFd != null) { - try { - profileFd = profileFd.dup(); - } catch (IOException e) { - if (profileFd != null) { - try { - profileFd.close(); - } catch (IOException o) { - } - profileFd = null; + app.waitingToKill = null; + r.launchCount++; + r.lastLaunchTime = SystemClock.uptimeMillis(); + + if (DEBUG_ALL) Slog.v(TAG, "Launching: " + r); + + int idx = app.activities.indexOf(r); + if (idx < 0) { + app.activities.add(r); + } + mService.updateLruProcessLocked(app, true, null); + mService.updateOomAdjLocked(); + + if (task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE || + task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE_PRIV) { + setLockTaskModeLocked(task, LOCK_TASK_MODE_LOCKED, "mLockTaskAuth==LAUNCHABLE", + false); + } + + try { + if (app.thread == null) { + throw new RemoteException(); + } + List<ResultInfo> results = null; + List<ReferrerIntent> newIntents = null; + if (andResume) { + // We don't need to deliver new intents and/or set results if activity is going + // to pause immediately after launch. + results = r.results; + newIntents = r.newIntents; + } + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, + "Launching: " + r + " icicle=" + r.icicle + " with results=" + results + + " newIntents=" + newIntents + " andResume=" + andResume); + EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY, r.userId, + System.identityHashCode(r), task.taskId, r.shortComponentName); + if (r.isHomeActivity()) { + // Home process is the root process of the task. + mService.mHomeProcess = task.mActivities.get(0).app; + } + mService.notifyPackageUse(r.intent.getComponent().getPackageName(), + PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY); + r.sleeping = false; + r.forceNewConfig = false; + mService.showUnsupportedZoomDialogIfNeededLocked(r); + mService.showAskCompatModeDialogLocked(r); + r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo); + ProfilerInfo profilerInfo = null; + if (mService.mProfileApp != null && mService.mProfileApp.equals(app.processName)) { + if (mService.mProfileProc == null || mService.mProfileProc == app) { + mService.mProfileProc = app; + ProfilerInfo profilerInfoSvc = mService.mProfilerInfo; + if (profilerInfoSvc != null && profilerInfoSvc.profileFile != null) { + if (profilerInfoSvc.profileFd != null) { + try { + profilerInfoSvc.profileFd = profilerInfoSvc.profileFd.dup(); + } catch (IOException e) { + profilerInfoSvc.closeFd(); } } - } - profilerInfo = new ProfilerInfo(profileFile, profileFd, - mService.mSamplingInterval, mService.mAutoStopProfiler, - mService.mStreamingOutput); + profilerInfo = new ProfilerInfo(profilerInfoSvc); + } } } - } - app.hasShownUi = true; - 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. - - final MergedConfiguration mergedConfiguration = new MergedConfiguration( - mService.getGlobalConfiguration(), r.getMergedOverrideConfiguration()); - r.setLastReportedConfiguration(mergedConfiguration); - - logIfTransactionTooLarge(r.intent, r.icicle); - app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, - System.identityHashCode(r), r.info, - // TODO: Have this take the merged configuration instead of separate global and - // override configs. - mergedConfiguration.getGlobalConfiguration(), - mergedConfiguration.getOverrideConfiguration(), 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 - // manager will ensure that only activity can run in the main - // process of the .apk, which is the only thing that will be - // considered heavy-weight. - if (app.processName.equals(app.info.packageName)) { - if (mService.mHeavyWeightProcess != null - && mService.mHeavyWeightProcess != app) { - Slog.w(TAG, "Starting new heavy weight process " + app - + " when already running " - + mService.mHeavyWeightProcess); + app.hasShownUi = true; + 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. + + final MergedConfiguration mergedConfiguration = new MergedConfiguration( + mService.getGlobalConfiguration(), r.getMergedOverrideConfiguration()); + r.setLastReportedConfiguration(mergedConfiguration); + + logIfTransactionTooLarge(r.intent, r.icicle); + app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, + System.identityHashCode(r), r.info, + // TODO: Have this take the merged configuration instead of separate global + // and override configs. + mergedConfiguration.getGlobalConfiguration(), + mergedConfiguration.getOverrideConfiguration(), 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 + // manager will ensure that only activity can run in the main + // process of the .apk, which is the only thing that will be + // considered heavy-weight. + if (app.processName.equals(app.info.packageName)) { + if (mService.mHeavyWeightProcess != null + && mService.mHeavyWeightProcess != app) { + Slog.w(TAG, "Starting new heavy weight process " + app + + " when already running " + + mService.mHeavyWeightProcess); + } + mService.mHeavyWeightProcess = app; + Message msg = mService.mHandler.obtainMessage( + ActivityManagerService.POST_HEAVY_NOTIFICATION_MSG); + msg.obj = r; + mService.mHandler.sendMessage(msg); } - mService.mHeavyWeightProcess = app; - Message msg = mService.mHandler.obtainMessage( - ActivityManagerService.POST_HEAVY_NOTIFICATION_MSG); - msg.obj = r; - mService.mHandler.sendMessage(msg); } - } - } catch (RemoteException e) { - if (r.launchFailed) { - // This is the second time we failed -- finish activity - // and give up. - Slog.e(TAG, "Second failure launching " - + r.intent.getComponent().flattenToShortString() - + ", giving up", e); - mService.appDiedLocked(app); - stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null, - "2nd-crash", false); - return false; - } + } catch (RemoteException e) { + if (r.launchFailed) { + // This is the second time we failed -- finish activity + // and give up. + Slog.e(TAG, "Second failure launching " + + r.intent.getComponent().flattenToShortString() + + ", giving up", e); + mService.appDiedLocked(app); + stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null, + "2nd-crash", false); + return false; + } - // This is the first time we failed -- restart process and - // retry. - r.launchFailed = true; - app.activities.remove(r); - throw e; + // This is the first time we failed -- restart process and + // retry. + r.launchFailed = true; + app.activities.remove(r); + throw e; + } + } finally { + endDeferResume(); } r.launchFailed = false; @@ -1520,7 +1527,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D Slog.w(TAG, "Activity " + r + " being launched, but already in LRU list"); } - if (andResume) { + if (andResume && readyToResume()) { // As part of the process of launching, ActivityThread also performs // a resume. stack.minimalResumeActivityLocked(r); @@ -2074,9 +2081,15 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean resumeFocusedStackTopActivityLocked( ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) { + + if (!readyToResume()) { + return false; + } + if (targetStack != null && isFocusedStack(targetStack)) { return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); } + final ActivityRecord r = mFocusedStack.topRunningActivityLocked(); if (r == null || r.state != RESUMED) { mFocusedStack.resumeTopActivityUncheckedLocked(null, null); @@ -2084,6 +2097,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Kick off any lingering app transitions form the MoveTaskToFront operation. mFocusedStack.executeAppTransition(targetOptions); } + return false; } @@ -2983,13 +2997,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D try { final TaskRecord task = r.getTask(); - - 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); - } - // Resize the pinned stack to match the current size of the task the activity we are // going to be moving is currently contained in. We do this to have the right starting // animation bounds for the pinned stack to the desired bounds the caller wants. @@ -3312,70 +3319,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - boolean requestVisibleBehindLocked(ActivityRecord r, boolean visible) { - 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"); - return false; - } - - if (visible && !StackId.activitiesCanRequestVisibleBehind(stack.mStackId)) { - if (DEBUG_VISIBLE_BEHIND) Slog.d(TAG_VISIBLE_BEHIND, "requestVisibleBehind: r=" + r - + " visible=" + visible + " stackId=" + stack.mStackId - + " can't contain visible behind activities"); - return false; - } - - final boolean isVisible = stack.hasVisibleBehindActivity(); - if (DEBUG_VISIBLE_BEHIND) Slog.d(TAG_VISIBLE_BEHIND, - "requestVisibleBehind r=" + r + " visible=" + visible + " isVisible=" + isVisible); - - final ActivityRecord top = topRunningActivityLocked(); - if (top == null || top == r || (visible == isVisible)) { - if (DEBUG_VISIBLE_BEHIND) Slog.d(TAG_VISIBLE_BEHIND, "requestVisibleBehind: quick return"); - stack.setVisibleBehindActivity(visible ? r : null); - return true; - } - - // A non-top activity is reporting a visibility change. - if (visible && top.fullscreen) { - // Let the caller know that it can't be seen. - if (DEBUG_VISIBLE_BEHIND) Slog.d(TAG_VISIBLE_BEHIND, - "requestVisibleBehind: returning top.fullscreen=" + top.fullscreen - + " top.state=" + top.state + " top.app=" + top.app + " top.app.thread=" - + top.app.thread); - return false; - } else if (!visible && stack.getVisibleBehindActivity() != r) { - // Only the activity set as currently visible behind should actively reset its - // visible behind state. - if (DEBUG_VISIBLE_BEHIND) Slog.d(TAG_VISIBLE_BEHIND, - "requestVisibleBehind: returning visible=" + visible - + " stack.getVisibleBehindActivity()=" + stack.getVisibleBehindActivity() - + " r=" + r); - return false; - } - - stack.setVisibleBehindActivity(visible ? r : null); - if (!visible) { - // If there is a translucent home activity, we need to force it stop being translucent, - // because we can't depend on the application to necessarily perform that operation. - // Check out b/14469711 for details. - final ActivityRecord next = stack.findNextTranslucentActivity(r); - if (next != null && next.isHomeActivity()) { - mService.convertFromTranslucent(next.appToken); - } - } - if (top.app != null && top.app.thread != null) { - // Notify the top app of the change. - try { - top.app.thread.scheduleBackgroundVisibleBehindChanged(top.appToken, visible); - } catch (RemoteException e) { - } - } - return true; - } - // Called when WindowManager has finished animating the launchingBehind activity to the back. private void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) { final TaskRecord task = r.getTask(); @@ -4433,6 +4376,31 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mIsDockMinimized = minimized; } + void wakeUp(String reason) { + mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.am:TURN_ON:" + reason); + } + + /** + * Begin deferring resume to avoid duplicate resumes in one pass. + */ + private void beginDeferResume() { + mDeferResumeCount++; + } + + /** + * End deferring resume and determine if resume can be called. + */ + private void endDeferResume() { + mDeferResumeCount--; + } + + /** + * @return True if resume can be called. + */ + private boolean readyToResume() { + return mDeferResumeCount == 0; + } + private final class ActivityStackSupervisorHandler extends Handler { public ActivityStackSupervisorHandler(Looper looper) { @@ -4963,8 +4931,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * stacks, bottommost behind. Accessed directly by ActivityManager package classes */ final ArrayList<ActivityStack> mStacks = new ArrayList<>(); - ActivityRecord mVisibleBehindActivity; - /** Array of all UIDs that are present on the display. */ private IntArray mDisplayAccessUIDs = new IntArray(); @@ -4998,14 +4964,6 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mStacks.remove(stack); } - void setVisibleBehindActivity(ActivityRecord r) { - mVisibleBehindActivity = r; - } - - boolean hasVisibleBehindActivity() { - return mVisibleBehindActivity != null; - } - @Override public String toString() { return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}"; diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java index a46c85170ba2..372d80df928f 100644 --- a/services/core/java/com/android/server/am/KeyguardController.java +++ b/services/core/java/com/android/server/am/KeyguardController.java @@ -30,7 +30,6 @@ import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AW import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_GOING_AWAY; import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_OCCLUDE; import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE; -import static com.android.server.wm.AppTransition.TRANSIT_NONE; import static com.android.server.wm.AppTransition.TRANSIT_UNSET; import android.os.IBinder; @@ -144,6 +143,13 @@ class KeyguardController { failCallback(callback); return; } + + // If the client has requested to dismiss the keyguard and the Activity has the flag to + // turn the screen on, wakeup the screen if it's the top Activity. + if (activityRecord.getTurnScreenOnFlag() && activityRecord.isTopRunningActivity()) { + mStackSupervisor.wakeUp("dismissKeyguard"); + } + mWindowManager.dismissKeyguard(callback); } diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 468cb2990b67..ad74ff88e80f 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -102,6 +102,22 @@ public final class ContentService extends IContentService.Stub { } } + + @Override + public void onStartUser(int userHandle) { + mService.onStartUser(userHandle); + } + + @Override + public void onUnlockUser(int userHandle) { + mService.onUnlockUser(userHandle); + } + + @Override + public void onStopUser(int userHandle) { + mService.onStopUser(userHandle); + } + @Override public void onCleanupUser(int userHandle) { synchronized (mService.mCache) { @@ -162,6 +178,18 @@ public final class ContentService extends IContentService.Stub { } } + void onStartUser(int userHandle) { + if (mSyncManager != null) mSyncManager.onStartUser(userHandle); + } + + void onUnlockUser(int userHandle) { + if (mSyncManager != null) mSyncManager.onUnlockUser(userHandle); + } + + void onStopUser(int userHandle) { + if (mSyncManager != null) mSyncManager.onStopUser(userHandle); + } + @Override protected synchronized void dump(FileDescriptor fd, PrintWriter pw_, String[] args) { if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw_)) return; diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 35591420af50..c250005204ba 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -405,6 +405,7 @@ public class SyncManager { public void onReceive(Context context, Intent intent) { Log.w(TAG, "Writing sync state before shutdown..."); getSyncStorageEngine().writeAllState(); + mLogger.log("Shutting down."); } }; @@ -674,8 +675,23 @@ public class SyncManager { // before we started checking for account access because they already know // the account (they run before) which is the genie is out of the bottle. whiteListExistingSyncAdaptersIfNeeded(); + + mLogger.log("Sync manager initialized."); + } + + public void onStartUser(int userHandle) { + mLogger.log("onStartUser: user=", userHandle); + } + + public void onUnlockUser(int userHandle) { + mLogger.log("onUnlockUser: user=", userHandle); + } + + public void onStopUser(int userHandle) { + mLogger.log("onStopUser: user=", userHandle); } + private void whiteListExistingSyncAdaptersIfNeeded() { if (!mSyncStorageEngine.shouldGrantSyncAdaptersAccountAccess()) { return; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 553e3c6691e9..2d645c0eea90 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2680,11 +2680,6 @@ public class UserManagerService extends IUserManager.Stub { addRemovingUserIdLocked(userHandle); } - try { - mAppOpsService.removeUser(userHandle); - } catch (RemoteException e) { - Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user", e); - } // Set this to a partially created user, so that the user will be purged // on next startup, in case the runtime stops now before stopping and // removing the user completely. @@ -2694,6 +2689,11 @@ public class UserManagerService extends IUserManager.Stub { userData.info.flags |= UserInfo.FLAG_DISABLED; writeUserLP(userData); } + try { + mAppOpsService.removeUser(userHandle); + } catch (RemoteException e) { + Log.w(LOG_TAG, "Unable to notify AppOpsService of removing user", e); + } if (userData.info.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID && userData.info.isManagedProfile()) { diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 89dbc2a1c1c4..5d2d4b6d6090 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1677,8 +1677,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { } boolean isUserSetupComplete() { - return Settings.Secure.getIntForUser(mContext.getContentResolver(), + boolean isSetupComplete = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0; + if (mHasFeatureLeanback) { + isSetupComplete &= isTvUserSetupComplete(); + } + return isSetupComplete; + } + + private boolean isTvUserSetupComplete() { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.TV_USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0; } private void handleShortPressOnHome() { diff --git a/services/core/java/com/android/server/vr/Vr2dDisplay.java b/services/core/java/com/android/server/vr/Vr2dDisplay.java index 8335243d590f..fa98f1744393 100644 --- a/services/core/java/com/android/server/vr/Vr2dDisplay.java +++ b/services/core/java/com/android/server/vr/Vr2dDisplay.java @@ -100,6 +100,7 @@ class Vr2dDisplay { private Runnable mStopVDRunnable; private boolean mIsVrModeOverrideEnabled; private boolean mIsVrModeEnabled; + private boolean mIsVirtualDisplayAllowed = true; public Vr2dDisplay(DisplayManager displayManager, ActivityManagerInternal activityManagerInternal, IVrManager vrManager) { @@ -124,10 +125,11 @@ class Vr2dDisplay { */ private void updateVirtualDisplay() { if (DEBUG) { - Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", override: " + mIsVrModeOverrideEnabled); + Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", override: " + mIsVrModeOverrideEnabled + + ", isAllowed: " + mIsVirtualDisplayAllowed); } - if (mIsVrModeEnabled || mIsVrModeOverrideEnabled) { + if (shouldRunVirtualDisplay()) { // TODO: Consider not creating the display until ActivityManager needs one on // which to display a 2D application. startVirtualDisplay(); @@ -190,33 +192,43 @@ class Vr2dDisplay { * * <p>Requires {@link android.Manifest.permission#ACCESS_VR_MANAGER} permission.</p> * - * @param compatDisplayProperties Properties of the virtual display for 2D applications + * @param displayProperties Properties of the virtual display for 2D applications * in VR mode. */ - public void setVirtualDisplayProperties(Vr2dDisplayProperties compatDisplayProperties) { + public void setVirtualDisplayProperties(Vr2dDisplayProperties displayProperties) { synchronized(mVdLock) { if (DEBUG) { - Log.i(TAG, "VD setVirtualDisplayProperties: res = " - + compatDisplayProperties.getWidth() + "X" - + compatDisplayProperties.getHeight() + ", dpi = " - + compatDisplayProperties.getDpi()); + Log.i(TAG, "VD setVirtualDisplayProperties: " + + displayProperties.toString()); } - if (compatDisplayProperties.getWidth() < MIN_VR_DISPLAY_WIDTH || - compatDisplayProperties.getHeight() < MIN_VR_DISPLAY_HEIGHT || - compatDisplayProperties.getDpi() < MIN_VR_DISPLAY_DPI) { - throw new IllegalArgumentException ( - "Illegal argument: height, width, dpi cannot be negative. res = " - + compatDisplayProperties.getWidth() + "X" - + compatDisplayProperties.getHeight() - + ", dpi = " + compatDisplayProperties.getDpi()); + int width = displayProperties.getWidth(); + int height = displayProperties.getHeight(); + int dpi = displayProperties.getDpi(); + boolean resized = false; + + if (width < MIN_VR_DISPLAY_WIDTH || height < MIN_VR_DISPLAY_HEIGHT || + dpi < MIN_VR_DISPLAY_DPI) { + Log.i(TAG, "Ignoring Width/Height/Dpi values of " + width + "," + height + "," + + dpi); + } else { + Log.i(TAG, "Setting width/height/dpi to " + width + "," + height + "," + dpi); + mVirtualDisplayWidth = width; + mVirtualDisplayHeight = height; + mVirtualDisplayDpi = dpi; + resized = true; } - mVirtualDisplayWidth = compatDisplayProperties.getWidth(); - mVirtualDisplayHeight = compatDisplayProperties.getHeight(); - mVirtualDisplayDpi = compatDisplayProperties.getDpi(); + if ((displayProperties.getFlags() & Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED) + == Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED) { + mIsVirtualDisplayAllowed = true; + } else if ((displayProperties.getRemovedFlags() & + Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED) + == Vr2dDisplayProperties.FLAG_VIRTUAL_DISPLAY_ENABLED) { + mIsVirtualDisplayAllowed = false; + } - if (mVirtualDisplay != null) { + if (mVirtualDisplay != null && resized && mIsVirtualDisplayAllowed) { mVirtualDisplay.resize(mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi); ImageReader oldImageReader = mImageReader; @@ -224,6 +236,9 @@ class Vr2dDisplay { startImageReader(); oldImageReader.close(); } + + // Start/Stop the virtual display in case the updates indicated that we should. + updateVirtualDisplay(); } } @@ -297,7 +312,7 @@ class Vr2dDisplay { mStopVDRunnable = new Runnable() { @Override public void run() { - if (mIsVrModeEnabled) { + if (shouldRunVirtualDisplay()) { Log.i(TAG, "Virtual Display destruction stopped: VrMode is back on."); } else { Log.i(TAG, "Stopping Virtual Display"); @@ -366,4 +381,8 @@ class Vr2dDisplay { mImageReader = null; } } + + private boolean shouldRunVirtualDisplay() { + return mIsVirtualDisplayAllowed && (mIsVrModeEnabled || mIsVrModeOverrideEnabled); + } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a95a0cf9808c..ceddc4e23fe5 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -19,12 +19,14 @@ package com.android.server.wm; import static android.Manifest.permission.MANAGE_APP_TOKENS; import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS; +import static android.Manifest.permission.RESTRICTED_VR_ACCESS; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_USER_HANDLE; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.ROOT_UID; import static android.os.Process.SHELL_UID; import static android.os.Process.SYSTEM_UID; @@ -6305,6 +6307,9 @@ public class WindowManagerService extends IWindowManager.Stub @Override public Region getCurrentImeTouchRegion() { + if (mContext.checkCallingOrSelfPermission(RESTRICTED_VR_ACCESS) != PERMISSION_GRANTED) { + throw new SecurityException("getCurrentImeTouchRegion is restricted to VR services"); + } synchronized (mWindowMap) { final Region r = new Region(); if (mInputMethodWindow != null) { diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index fdcf9df50426..c8c629fd5ea5 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -33,6 +33,7 @@ LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \ $(LOCAL_REL_DIR)/com_android_server_vr_VrManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \ + $(LOCAL_REL_DIR)/com_android_server_UsbDescriptorParser.cpp \ $(LOCAL_REL_DIR)/com_android_server_UsbMidiDevice.cpp \ $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \ $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \ diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp index 5d5728da61af..61c53e6d30b3 100644 --- a/services/core/jni/com_android_server_GraphicsStatsService.cpp +++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp @@ -43,8 +43,9 @@ static void addToDump(JNIEnv* env, jobject, jlong dumpPtr, jstring jpath, jstrin std::string path; const ProfileData* data = nullptr; LOG_ALWAYS_FATAL_IF(jdata == nullptr && jpath == nullptr, "Path and data can't both be null"); + ScopedByteArrayRO buffer{env}; if (jdata != nullptr) { - ScopedByteArrayRO buffer(env, jdata); + buffer.reset(jdata); LOG_ALWAYS_FATAL_IF(buffer.size() != sizeof(ProfileData), "Buffer size %zu doesn't match expected %zu!", buffer.size(), sizeof(ProfileData)); data = reinterpret_cast<const ProfileData*>(buffer.get()); diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp new file mode 100644 index 000000000000..98c5ec1bd4d5 --- /dev/null +++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 The Android Open 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 "UsbHostManagerJNI" +#include "utils/Log.h" + +#include "jni.h" +#include "JNIHelp.h" + +#include <usbhost/usbhost.h> + +#define MAX_DESCRIPTORS_LENGTH 16384 + +// com.android.server.usb.descriptors +extern "C" { +jbyteArray JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getRawDescriptors( + JNIEnv* env, jobject thiz, jstring deviceAddr) { + const char *deviceAddrStr = env->GetStringUTFChars(deviceAddr, NULL); + struct usb_device* device = usb_device_open(deviceAddrStr); + env->ReleaseStringUTFChars(deviceAddr, deviceAddrStr); + + if (!device) { + ALOGE("usb_device_open failed"); + return NULL; + } + + int fd = usb_device_get_fd(device); + if (fd < 0) { + return NULL; + } + + // from android_hardware_UsbDeviceConnection_get_desc() + jbyte buffer[MAX_DESCRIPTORS_LENGTH]; + lseek(fd, 0, SEEK_SET); + int numBytes = read(fd, buffer, sizeof(buffer)); + + usb_device_close(device); + + jbyteArray ret = NULL; + if (numBytes != 0) { + ret = env->NewByteArray(numBytes); + env->SetByteArrayRegion(ret, 0, numBytes, buffer); + } + return ret; +} + +} // extern "C" + + diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java index 711c36b8b1d4..48464e5e0248 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java @@ -98,25 +98,4 @@ public class ActivityStackTests extends ActivityTestsBase { testStack.stopActivityLocked(activityRecord); } - - /** - * This test verifies that {@link ActivityStack#STACK_VISIBLE_ACTIVITY_BEHIND} is returned from - * {@link ActivityStack#shouldBeVisible(ActivityRecord)} from a fullscreen workspace stack with - * a visible behind activity when top focused stack is the home stack. - */ - @Test - public void testShouldBeVisibleWithVisibleBehindActivity() throws Exception { - final ActivityManagerService service = createActivityManagerService(); - final TaskRecord task = createTask(service, testActivityComponent, - ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID); - final ActivityStack fullscreenWorkspaceStackId = task.getStack(); - final ActivityStack homeStack = service.mStackSupervisor.getStack( - ActivityManager.StackId.HOME_STACK_ID, true /*createStaticStackIfNeeded*/, - true /*onTop*/); - final ActivityRecord activityRecord = createActivity(service, testActivityComponent, task); - service.mStackSupervisor.setFocusStackUnchecked("testEmptyStackShouldBeVisible", homeStack); - service.mStackSupervisor.requestVisibleBehindLocked(activityRecord, true); - assertEquals(ActivityStack.STACK_VISIBLE_ACTIVITY_BEHIND, - fullscreenWorkspaceStackId.shouldBeVisible(null /*starting*/)); - } } diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index d315b181c357..68c1d5f6ec7f 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -65,6 +65,9 @@ public final class UsbAlsaManager { private final HashMap<UsbDevice,UsbAudioDevice> mAudioDevices = new HashMap<UsbDevice,UsbAudioDevice>(); + private boolean mIsInputHeadset; // as reported by UsbDescriptorParser + private boolean mIsOutputHeadset; // as reported by UsbDescriptorParser + private final HashMap<UsbDevice,UsbMidiDevice> mMidiDevices = new HashMap<UsbDevice,UsbMidiDevice>(); @@ -184,9 +187,14 @@ public final class UsbAlsaManager { try { // Playback Device if (audioDevice.mHasPlayback) { - int device = (audioDevice == mAccessoryAudioDevice ? - AudioSystem.DEVICE_OUT_USB_ACCESSORY : - AudioSystem.DEVICE_OUT_USB_DEVICE); + int device; + if (mIsOutputHeadset) { + device = AudioSystem.DEVICE_OUT_USB_HEADSET; + } else { + device = (audioDevice == mAccessoryAudioDevice + ? AudioSystem.DEVICE_OUT_USB_ACCESSORY + : AudioSystem.DEVICE_OUT_USB_DEVICE); + } if (DEBUG) { Slog.i(TAG, "pre-call device:0x" + Integer.toHexString(device) + " addr:" + address + " name:" + audioDevice.getDeviceName()); @@ -197,9 +205,14 @@ public final class UsbAlsaManager { // Capture Device if (audioDevice.mHasCapture) { - int device = (audioDevice == mAccessoryAudioDevice ? - AudioSystem.DEVICE_IN_USB_ACCESSORY : - AudioSystem.DEVICE_IN_USB_DEVICE); + int device; + if (mIsInputHeadset) { + device = AudioSystem.DEVICE_IN_USB_HEADSET; + } else { + device = (audioDevice == mAccessoryAudioDevice + ? AudioSystem.DEVICE_IN_USB_ACCESSORY + : AudioSystem.DEVICE_IN_USB_DEVICE); + } mAudioService.setWiredDeviceConnectionState( device, state, address, audioDevice.getDeviceName(), TAG); } @@ -343,12 +356,16 @@ public final class UsbAlsaManager { return selectAudioCard(mCardsParser.getDefaultCard()); } - /* package */ void usbDeviceAdded(UsbDevice usbDevice) { - if (DEBUG) { - Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() + - " nm:" + usbDevice.getProductName()); + /* package */ void usbDeviceAdded(UsbDevice usbDevice, + boolean isInputHeadset, boolean isOutputHeadset) { + if (DEBUG) { + Slog.d(TAG, "deviceAdded(): " + usbDevice.getManufacturerName() + + " nm:" + usbDevice.getProductName()); } + mIsInputHeadset = isInputHeadset; + mIsOutputHeadset = isOutputHeadset; + // Is there an audio interface in there? boolean isAudioDevice = false; diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 1a24d9571e72..a7180c989a6e 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -683,8 +683,9 @@ public class UsbDeviceManager { // Set the new USB configuration. setUsbConfig(oemFunctions); - if (UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_MTP) - || UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_PTP)) { + if (mBootCompleted + && (UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_MTP) + || UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) { // Start up dependent services. updateUsbStateBroadcastIfNeeded(true); } diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index 737b572d9f29..66292f80e903 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -31,6 +31,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.usb.descriptors.UsbDescriptorParser; import java.util.ArrayList; import java.util.HashMap; @@ -257,7 +258,14 @@ public class UsbHostManager { getCurrentUserSettings().deviceAttachedForFixedHandler(mNewDevice, usbDeviceConnectionHandler); } - mUsbAlsaManager.usbDeviceAdded(mNewDevice); + // deviceName is something like: "/dev/bus/usb/001/001" + UsbDescriptorParser parser = new UsbDescriptorParser(); + if (parser.parseDevice(mNewDevice.getDeviceName())) { + Slog.i(TAG, "---- isHeadset[in:" + parser.isInputHeadset() + + " , out:" + parser.isOutputHeadset() + "]"); + mUsbAlsaManager.usbDeviceAdded(mNewDevice, + parser.isInputHeadset(), parser.isOutputHeadset()); + } } else { Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded"); } diff --git a/services/usb/java/com/android/server/usb/descriptors/ByteStream.java b/services/usb/java/com/android/server/usb/descriptors/ByteStream.java new file mode 100644 index 000000000000..d678931fd01a --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/ByteStream.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +import android.annotation.NonNull; + +/** + * @hide + * A stream interface wrapping a byte array. Very much like a java.io.ByteArrayInputStream + * but with the capability to "back up" in situations where the parser discovers that a + * UsbDescriptor has overrun its length. + */ +public class ByteStream { + private static final String TAG = "ByteStream"; + + /** The byte array being wrapped */ + @NonNull + private final byte[] mBytes; // this is never null. + + /** + * The index into the byte array to be read next. + * This value is altered by reading data out of the stream + * (using either the getByte() or unpack*() methods), or alternatively + * by explicitly offseting the stream position with either + * advance() or reverse(). + */ + private int mIndex; + + /* + * This member used with resetReadCount() & getReadCount() can be used to determine how many + * bytes a UsbDescriptor subclass ACTUALLY reads (as opposed to what its length field says). + * using this info, the parser can mark a descriptor as valid or invalid and correct the stream + * position with advance() & reverse() to keep from "getting lost" in the descriptor stream. + */ + private int mReadCount; + + /** + * Create a ByteStream object wrapping the specified byte array. + * + * @param bytes The byte array containing the raw descriptor information retrieved from + * the USB device. + * @throws IllegalArgumentException + */ + public ByteStream(@NonNull byte[] bytes) { + if (bytes == null) { + throw new IllegalArgumentException(); + } + mBytes = bytes; + } + + /** + * Resets the running count of bytes read so that later we can see how much more has been read. + */ + public void resetReadCount() { + mReadCount = 0; + } + + /** + * Retrieves the running count of bytes read from the stream. + */ + public int getReadCount() { + return mReadCount; + } + + /** + * @return The value of the next byte in the stream without advancing the stream. + * Does not affect the running count as the byte hasn't been "consumed". + * @throws IndexOutOfBoundsException + */ + public byte peekByte() { + if (available() > 0) { + return mBytes[mIndex + 1]; + } else { + throw new IndexOutOfBoundsException(); + } + } + + /** + * @return the next byte from the stream and advances the stream and the read count. Note + * that this is a signed byte (as is the case of byte in Java). The user may need to understand + * from context if it should be interpreted as an unsigned value. + * @throws IndexOutOfBoundsException + */ + public byte getByte() { + if (available() > 0) { + mReadCount++; + return mBytes[mIndex++]; + } else { + throw new IndexOutOfBoundsException(); + } + } + + /** + * Reads 2 bytes in *little endian format* from the stream and composes a 16-bit integer. + * As we are storing the 2-byte value in a 4-byte integer, the upper 2 bytes are always + * 0, essentially making the returned value *unsigned*. + * @return The 16-bit integer (packed into the lower 2 bytes of an int) encoded by the + * next 2 bytes in the stream. + * @throws IndexOutOfBoundsException + */ + public int unpackUsbWord() { + if (available() >= 2) { + int b0 = getByte(); + int b1 = getByte(); + return ((b1 << 8) & 0x0000FF00) | (b0 & 0x000000FF); + } else { + throw new IndexOutOfBoundsException(); + } + } + + /** + * Reads 3 bytes in *little endian format* from the stream and composes a 24-bit integer. + * As we are storing the 3-byte value in a 4-byte integer, the upper byte is always + * 0, essentially making the returned value *unsigned*. + * @return The 24-bit integer (packed into the lower 3 bytes of an int) encoded by the + * next 3 bytes in the stream. + * @throws IndexOutOfBoundsException + */ + public int unpackUsbTriple() { + if (available() >= 3) { + int b0 = getByte(); + int b1 = getByte(); + int b2 = getByte(); + return ((b2 << 16) & 0x00FF0000) | ((b1 << 8) & 0x0000FF00) | (b0 & 0x000000FF); + } else { + throw new IndexOutOfBoundsException(); + } + } + + /** + * Advances the logical position in the stream. Affects the running count also. + * @param numBytes The number of bytes to advance. + * @throws IndexOutOfBoundsException + * @throws IllegalArgumentException + */ + public void advance(int numBytes) { + if (numBytes < 0) { + // Positive offsets only + throw new IllegalArgumentException(); + } + // do arithmetic and comparison in long to ovoid potention integer overflow + long longNewIndex = (long) mIndex + (long) numBytes; + if (longNewIndex < (long) mBytes.length) { + mReadCount += numBytes; + mIndex += numBytes; + } else { + throw new IndexOutOfBoundsException(); + } + } + + /** + * Reverse the logical position in the stream. Affects the running count also. + * @param numBytes The (positive) number of bytes to reverse. + * @throws IndexOutOfBoundsException + * @throws IllegalArgumentException + */ + public void reverse(int numBytes) { + if (numBytes < 0) { + // Positive (reverse) offsets only + throw new IllegalArgumentException(); + } + if (mIndex >= numBytes) { + mReadCount -= numBytes; + mIndex -= numBytes; + } else { + throw new IndexOutOfBoundsException(); + } + } + + /** + * @return The number of bytes available to be read in the stream. + */ + public int available() { + return mBytes.length - mIndex; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java new file mode 100644 index 000000000000..96fcc6a0b8db --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Audio Control Endpoint. + * audio10.pdf section 4.4.2.1 + */ +public class UsbACAudioControlEndpoint extends UsbACEndpoint { + private static final String TAG = "ACAudioControlEndpoint"; + + private byte mAddress; // 2:1 The address of the endpoint on the USB device. + // D7: Direction. 1 = IN endpoint + // D6..4: Reserved, reset to zero + // D3..0: The endpoint number. + private byte mAttribs; // 3:1 (see ATTRIBSMASK_* below + private int mMaxPacketSize; // 4:2 Maximum packet size this endpoint is capable of sending + // or receiving when this configuration is selected. + private byte mInterval; // 6:1 + + static final byte ADDRESSMASK_DIRECTION = (byte) 0x80; + static final byte ADDRESSMASK_ENDPOINT = 0x0F; + + static final byte ATTRIBSMASK_SYNC = 0x0C; + static final byte ATTRIBMASK_TRANS = 0x03; + + public UsbACAudioControlEndpoint(int length, byte type, byte subclass) { + super(length, type, subclass); + } + + public byte getAddress() { + return mAddress; + } + + public byte getAttribs() { + return mAttribs; + } + + public int getMaxPacketSize() { + return mMaxPacketSize; + } + + public byte getInterval() { + return mInterval; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + super.parseRawDescriptors(stream); + + mAddress = stream.getByte(); + mAttribs = stream.getByte(); + mMaxPacketSize = stream.unpackUsbWord(); + mInterval = stream.getByte(); + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java new file mode 100644 index 000000000000..d387883d3049 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Streaming Endpoint + * see audio10.pdf section 3.7.2 + */ +public class UsbACAudioStreamEndpoint extends UsbACEndpoint { + private static final String TAG = "ACAudioStreamEndpoint"; + + //TODO data fields... + public UsbACAudioStreamEndpoint(int length, byte type, byte subclass) { + super(length, type, subclass); + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + super.parseRawDescriptors(stream); + + //TODO Read fields + stream.advance(mLength - stream.getReadCount()); + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java new file mode 100644 index 000000000000..223496ab016e --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +import android.util.Log; + +/** + * @hide + * An audio class-specific Endpoint + * see audio10.pdf section 4.4.1.2 + */ +abstract class UsbACEndpoint extends UsbDescriptor { + private static final String TAG = "ACEndpoint"; + + protected final byte mSubclass; // from the mSubclass member of the "enclosing" + // Interface Descriptor, not the stream. + protected byte mSubtype; // 2:1 HEADER descriptor subtype + + UsbACEndpoint(int length, byte type, byte subclass) { + super(length, type); + mSubclass = subclass; + } + + public byte getSubclass() { + return mSubclass; + } + + public byte getSubtype() { + return mSubtype; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mSubtype = stream.getByte(); + + return mLength; + } + + public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, + int length, byte type) { + UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); + byte subClass = interfaceDesc.getUsbSubclass(); + switch (subClass) { + case AUDIO_AUDIOCONTROL: + return new UsbACAudioControlEndpoint(length, type, subClass); + + case AUDIO_AUDIOSTREAMING: + return new UsbACAudioStreamEndpoint(length, type, subClass); + + case AUDIO_MIDISTREAMING: + return new UsbACMidiEndpoint(length, type, subClass); + + default: + Log.w(TAG, "Unknown Audio Class Endpoint id:0x" + Integer.toHexString(subClass)); + return null; + } + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java new file mode 100644 index 000000000000..739fe5503a1d --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Feature Unit Interface + * see audio10.pdf section 3.5.5 + */ +public class UsbACFeatureUnit extends UsbACInterface { + private static final String TAG = "ACFeatureUnit"; + + // audio10.pdf section 4.3.2.5 + public static final int CONTROL_MASK_MUTE = 0x0001; + public static final int CONTROL_MASK_VOL = 0x0002; + public static final int CONTROL_MASK_BASS = 0x0004; + public static final int CONTROL_MASK_MID = 0x0008; + public static final int CONTROL_MASK_TREB = 0x0010; + public static final int CONTROL_MASK_EQ = 0x0020; + public static final int CONTROL_MASK_AGC = 0x0040; + public static final int CONTROL_MASK_DELAY = 0x0080; + public static final int CONTROL_MASK_BOOST = 0x0100; // BASS boost + public static final int CONTROL_MASK_LOUD = 0x0200; // LOUDNESS + + private int mNumChannels; + + private byte mUnitID; // 3:1 Constant uniquely identifying the Unit within the audio function. + // This value is used in all requests to address this Unit + private byte mSourceID; // 4:1 ID of the Unit or Terminal to which this Feature Unit + // is connected. + private byte mControlSize; // 5:1 Size in bytes of an element of the mControls array: n + private int[] mControls; // 6:? bitmask (see above) of supported controls in a given + // logical channel + private byte mUnitName; // ?:1 Index of a string descriptor, describing this Feature Unit. + + public UsbACFeatureUnit(int length, byte type, byte subtype, byte subClass) { + super(length, type, subtype, subClass); + } + + public int getNumChannels() { + return mNumChannels; + } + + public byte getUnitID() { + return mUnitID; + } + + public byte getSourceID() { + return mSourceID; + } + + public byte getControlSize() { + return mControlSize; + } + + public int[] getControls() { + return mControls; + } + + public byte getUnitName() { + return mUnitName; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbACHeader.java new file mode 100644 index 000000000000..e31438c58e06 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACHeader.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Interface Header. + * see audio10.pdf section 4.3.2 + */ +public class UsbACHeader extends UsbACInterface { + private static final String TAG = "ACHeader"; + + private int mADCRelease; // 3:2 Audio Device Class Specification Release (BCD). + private int mTotalLength; // 5:2 Total number of bytes returned for the class-specific + // AudioControl interface descriptor. Includes the combined length + // of this descriptor header and all Unit and Terminal descriptors. + private byte mNumInterfaces = 0; // 7:1 The number of AudioStreaming and MIDIStreaming + // interfaces in the Audio Interface Collection to which this + // AudioControl interface belongs: n + private byte[] mInterfaceNums = null; // 8:n List of Audio/MIDI streaming interface + // numbers associate with this endpoint + private byte mControls; // Vers 2.0 thing + + public UsbACHeader(int length, byte type, byte subtype, byte subclass) { + super(length, type, subtype, subclass); + } + + public int getADCRelease() { + return mADCRelease; + } + + public int getTotalLength() { + return mTotalLength; + } + + public byte getNumInterfaces() { + return mNumInterfaces; + } + + public byte[] getInterfaceNums() { + return mInterfaceNums; + } + + public byte getControls() { + return mControls; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mADCRelease = stream.unpackUsbWord(); + + mTotalLength = stream.unpackUsbWord(); + if (mADCRelease >= 0x200) { + mControls = stream.getByte(); + } else { + mNumInterfaces = stream.getByte(); + mInterfaceNums = new byte[mNumInterfaces]; + for (int index = 0; index < mNumInterfaces; index++) { + mInterfaceNums[index] = stream.getByte(); + } + } + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACInputTerminal.java new file mode 100644 index 000000000000..653a7de5457e --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACInputTerminal.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Input Terminal interface. + * see audio10.pdf section 4.3.2.1 + */ +public class UsbACInputTerminal extends UsbACTerminal { + private static final String TAG = "ACInputTerminal"; + + private byte mNrChannels; // 7:1 1 Channel (0x01) + // Number of logical output channels in the + // Terminal’s output audio channel cluster + private int mChannelConfig; // 8:2 Mono (0x0000) + private byte mChannelNames; // 10:1 Unused (0x00) + private byte mTerminal; // 11:1 Unused (0x00) + + public UsbACInputTerminal(int length, byte type, byte subtype, byte subclass) { + super(length, type, subtype, subclass); + } + + public byte getNrChannels() { + return mNrChannels; + } + + public int getChannelConfig() { + return mChannelConfig; + } + + public byte getChannelNames() { + return mChannelNames; + } + + public byte getTerminal() { + return mTerminal; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + super.parseRawDescriptors(stream); + + mNrChannels = stream.getByte(); + mChannelConfig = stream.unpackUsbWord(); + mChannelNames = stream.getByte(); + mTerminal = stream.getByte(); + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java new file mode 100644 index 000000000000..0ab7fccd2c3b --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +import android.util.Log; + +/** + * @hide + * An audio class-specific Interface. + * see audio10.pdf section 4.3.2 + */ +public abstract class UsbACInterface extends UsbDescriptor { + private static final String TAG = "ACInterface"; + + // Audio Control Subtypes + public static final byte ACI_UNDEFINED = 0; + public static final byte ACI_HEADER = 1; + public static final byte ACI_INPUT_TERMINAL = 2; + public static final byte ACI_OUTPUT_TERMINAL = 3; + public static final byte ACI_MIXER_UNIT = 4; + public static final byte ACI_SELECTOR_UNIT = 5; + public static final byte ACI_FEATURE_UNIT = 6; + public static final byte ACI_PROCESSING_UNIT = 7; + public static final byte ACI_EXTENSION_UNIT = 8; + + // Audio Streaming Subtypes + public static final byte ASI_UNDEFINED = 0; + public static final byte ASI_GENERAL = 1; + public static final byte ASI_FORMAT_TYPE = 2; + public static final byte ASI_FORMAT_SPECIFIC = 3; + + // MIDI Streaming Subtypes + public static final byte MSI_UNDEFINED = 0; + public static final byte MSI_HEADER = 1; + public static final byte MSI_IN_JACK = 2; + public static final byte MSI_OUT_JACK = 3; + public static final byte MSI_ELEMENT = 4; + + // Sample format IDs (encodings) + // FORMAT_I + public static final int FORMAT_I_UNDEFINED = 0x0000; + public static final int FORMAT_I_PCM = 0x0001; + public static final int FORMAT_I_PCM8 = 0x0002; + public static final int FORMAT_I_IEEE_FLOAT = 0x0003; + public static final int FORMAT_I_ALAW = 0x0004; + public static final int FORMAT_I_MULAW = 0x0005; + // FORMAT_II + public static final int FORMAT_II_UNDEFINED = 0x1000; + public static final int FORMAT_II_MPEG = 0x1001; + public static final int FORMAT_II_AC3 = 0x1002; + // FORMAT_III + public static final int FORMAT_III_UNDEFINED = 0x2000; + public static final int FORMAT_III_IEC1937AC3 = 0x2001; + public static final int FORMAT_III_IEC1937_MPEG1_Layer1 = 0x2002; + public static final int FORMAT_III_IEC1937_MPEG1_Layer2 = 0x2003; + public static final int FORMAT_III_IEC1937_MPEG2_EXT = 0x2004; + public static final int FORMAT_III_IEC1937_MPEG2_Layer1LS = 0x2005; + + protected final byte mSubtype; // 2:1 HEADER descriptor subtype + protected final byte mSubclass; // from the mSubclass member of the + // "enclosing" Interface Descriptor + + public UsbACInterface(int length, byte type, byte subtype, byte subclass) { + super(length, type); + mSubtype = subtype; + mSubclass = subclass; + } + + public byte getSubtype() { + return mSubtype; + } + + public byte getSubclass() { + return mSubclass; + } + + private static UsbDescriptor allocAudioControlDescriptor(ByteStream stream, + int length, byte type, byte subtype, byte subClass) { + switch (subtype) { + case ACI_HEADER: + return new UsbACHeader(length, type, subtype, subClass); + + case ACI_INPUT_TERMINAL: + return new UsbACInputTerminal(length, type, subtype, subClass); + + case ACI_OUTPUT_TERMINAL: + return new UsbACOutputTerminal(length, type, subtype, subClass); + + case ACI_SELECTOR_UNIT: + return new UsbACSelectorUnit(length, type, subtype, subClass); + + case ACI_FEATURE_UNIT: + return new UsbACFeatureUnit(length, type, subtype, subClass); + + case ACI_MIXER_UNIT: + return new UsbACMixerUnit(length, type, subtype, subClass); + + case ACI_PROCESSING_UNIT: + case ACI_EXTENSION_UNIT: + case ACI_UNDEFINED: + // break; Fall through until we implement this descriptor + default: + Log.w(TAG, "Unknown Audio Class Interface subtype:0x" + + Integer.toHexString(subtype)); + return null; + } + } + + private static UsbDescriptor allocAudioStreamingDescriptor(ByteStream stream, + int length, byte type, byte subtype, byte subClass) { + switch (subtype) { + case ASI_GENERAL: + return new UsbASGeneral(length, type, subtype, subClass); + + case ASI_FORMAT_TYPE: + return UsbASFormat.allocDescriptor(stream, length, type, subtype, subClass); + + case ASI_FORMAT_SPECIFIC: + case ASI_UNDEFINED: + // break; Fall through until we implement this descriptor + default: + Log.w(TAG, "Unknown Audio Streaming Interface subtype:0x" + + Integer.toHexString(subtype)); + return null; + } + } + + private static UsbDescriptor allocMidiStreamingDescriptor(int length, byte type, + byte subtype, byte subClass) { + switch (subtype) { + case MSI_HEADER: + return new UsbMSMidiHeader(length, type, subtype, subClass); + + case MSI_IN_JACK: + return new UsbMSMidiInputJack(length, type, subtype, subClass); + + case MSI_OUT_JACK: + return new UsbMSMidiOutputJack(length, type, subtype, subClass); + + case MSI_ELEMENT: + // break; + // Fall through until we implement that descriptor + + case MSI_UNDEFINED: + // break; Fall through until we implement this descriptor + default: + Log.w(TAG, "Unknown MIDI Streaming Interface subtype:0x" + + Integer.toHexString(subtype)); + return null; + } + } + + /** + * Allocates an audio class interface subtype based on subtype and subclass. + */ + public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, ByteStream stream, + int length, byte type) { + byte subtype = stream.getByte(); + UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); + byte subClass = interfaceDesc.getUsbSubclass(); + switch (subClass) { + case AUDIO_AUDIOCONTROL: + return allocAudioControlDescriptor(stream, length, type, subtype, subClass); + + case AUDIO_AUDIOSTREAMING: + return allocAudioStreamingDescriptor(stream, length, type, subtype, subClass); + + case AUDIO_MIDISTREAMING: + return allocMidiStreamingDescriptor(length, type, subtype, subClass); + + default: + Log.w(TAG, "Unknown Audio Class Interface Subclass: 0x" + + Integer.toHexString(subClass)); + return null; + } + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java new file mode 100644 index 000000000000..9c072426cc49 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Midi Endpoint. + * see midi10.pdf section 6.2.2 + */ +public class UsbACMidiEndpoint extends UsbACEndpoint { + private static final String TAG = "ACMidiEndpoint"; + + private byte mNumJacks; + private byte[] mJackIds; + + public UsbACMidiEndpoint(int length, byte type, byte subclass) { + super(length, type, subclass); + } + + public byte getNumJacks() { + return mNumJacks; + } + + public byte[] getJackIds() { + return mJackIds; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + super.parseRawDescriptors(stream); + + mNumJacks = stream.getByte(); + mJackIds = new byte[mNumJacks]; + for (int jack = 0; jack < mNumJacks; jack++) { + mJackIds[jack] = stream.getByte(); + } + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java new file mode 100644 index 000000000000..552b5ae308d6 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Mixer Interface. + * see audio10.pdf section 4.3.2.3 + */ +public class UsbACMixerUnit extends UsbACInterface { + private static final String TAG = "ACMixerUnit"; + + private byte mUnitID; // 3:1 + private byte mNumInputs; // 4:1 Number of Input Pins of this Unit. + private byte[] mInputIDs; // 5...:1 ID of the Unit or Terminal to which the Input Pins + // are connected. + private byte mNumOutputs; // The number of output channels + private int mChannelConfig; // Spacial location of output channels + private byte mChanNameID; // First channel name string descriptor ID + private byte[] mControls; // bitmasks of which controls are present for each channel + private byte mNameID; // string descriptor ID of mixer name + + public UsbACMixerUnit(int length, byte type, byte subtype, byte subClass) { + super(length, type, subtype, subClass); + } + + public byte getUnitID() { + return mUnitID; + } + + public byte getNumInputs() { + return mNumInputs; + } + + public byte[] getInputIDs() { + return mInputIDs; + } + + public byte getNumOutputs() { + return mNumOutputs; + } + + public int getChannelConfig() { + return mChannelConfig; + } + + public byte getChanNameID() { + return mChanNameID; + } + + public byte[] getControls() { + return mControls; + } + + public byte getNameID() { + return mNameID; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mUnitID = stream.getByte(); + mNumInputs = stream.getByte(); + mInputIDs = new byte[mNumInputs]; + for (int input = 0; input < mNumInputs; input++) { + mInputIDs[input] = stream.getByte(); + } + mNumOutputs = stream.getByte(); + mChannelConfig = stream.unpackUsbWord(); + mChanNameID = stream.getByte(); + + int controlArraySize; + int totalChannels = mNumInputs * mNumOutputs; + if (totalChannels % 8 == 0) { + controlArraySize = totalChannels / 8; + } else { + controlArraySize = totalChannels / 8 + 1; + } + mControls = new byte[controlArraySize]; + for (int index = 0; index < controlArraySize; index++) { + mControls[index] = stream.getByte(); + } + + mNameID = stream.getByte(); + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACOutputTerminal.java new file mode 100644 index 000000000000..f957e3dbe217 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACOutputTerminal.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Output Terminal Interface. + * see audio10.pdf section 4.3.2.2 + */ +public class UsbACOutputTerminal extends UsbACTerminal { + private static final String TAG = "ACOutputTerminal"; + + private byte mSourceID; // 7:1 From Input Terminal. (0x01) + private byte mTerminal; // 8:1 Unused. + + public UsbACOutputTerminal(int length, byte type, byte subtype, byte subClass) { + super(length, type, subtype, subClass); + } + + public byte getSourceID() { + return mSourceID; + } + + public byte getTerminal() { + return mTerminal; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + super.parseRawDescriptors(stream); + + mSourceID = stream.getByte(); + mTerminal = stream.getByte(); + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java new file mode 100644 index 000000000000..b1f60bdcf6ed --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Selector Unit Interface. + * see audio10.pdf section 4.3.2.4 + */ +public class UsbACSelectorUnit extends UsbACInterface { + private static final String TAG = "ACSelectorUnit"; + + private byte mUnitID; // 3:1 Constant uniquely identifying the Unit within the audio function. + // This value is used in all requests to address this Unit. + private byte mNumPins; // 4:1 Number of input pins in this unit + private byte[] mSourceIDs; // 5+mNumPins:1 ID of the Unit or Terminal to which the first + // Input Pin of this Selector Unit is connected. + private byte mNameIndex; // Index of a string descriptor, describing the Selector Unit. + + public UsbACSelectorUnit(int length, byte type, byte subtype, byte subClass) { + super(length, type, subtype, subClass); + } + + public byte getUnitID() { + return mUnitID; + } + + public byte getNumPins() { + return mNumPins; + } + + public byte[] getSourceIDs() { + return mSourceIDs; + } + + public byte getNameIndex() { + return mNameIndex; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mUnitID = stream.getByte(); + mNumPins = stream.getByte(); + mSourceIDs = new byte[mNumPins]; + for (int index = 0; index < mNumPins; index++) { + mSourceIDs[index] = stream.getByte(); + } + mNameIndex = stream.getByte(); + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java new file mode 100644 index 000000000000..ea80208ee3f3 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + */ +public abstract class UsbACTerminal extends UsbACInterface { + // Note that these fields are the same for both the + // audio class-specific Output Terminal Interface.(audio10.pdf section 4.3.2.2) + // and audio class-specific Input Terminal interface.(audio10.pdf section 4.3.2.1) + // so we may as well unify the parsing here. + protected byte mTerminalID; // 3:1 ID of this Output Terminal. (0x02) + protected int mTerminalType; // 4:2 USB Streaming. (0x0101) + protected byte mAssocTerminal; // 6:1 Unused (0x00) + + public UsbACTerminal(int length, byte type, byte subtype, byte subclass) { + super(length, type, subtype, subclass); + } + + public byte getTerminalID() { + return mTerminalID; + } + + public int getTerminalType() { + return mTerminalType; + } + + public byte getAssocTerminal() { + return mAssocTerminal; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mTerminalID = stream.getByte(); + mTerminalType = stream.unpackUsbWord(); + mAssocTerminal = stream.getByte(); + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java b/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java new file mode 100644 index 000000000000..d7c84c6a0965 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Format Interface. + * Subclasses: UsbACFormatI and UsbACFormatII. + * see audio10.pdf section 4.5.3 & & Frmts10.pdf + */ +public abstract class UsbASFormat extends UsbACInterface { + private static final String TAG = "ASFormat"; + + private final byte mFormatType; // 3:1 FORMAT_TYPE_* + + public static final byte FORMAT_TYPE_I = 1; + public static final byte FORMAT_TYPE_II = 2; + + public UsbASFormat(int length, byte type, byte subtype, byte formatType, byte mSubclass) { + super(length, type, subtype, mSubclass); + mFormatType = formatType; + } + + public byte getFormatType() { + return mFormatType; + } + + /** + * Allocates the audio-class format subtype associated with the format type read from the + * stream. + */ + public static UsbDescriptor allocDescriptor(ByteStream stream, int length, byte type, + byte subtype, byte subclass) { + + byte formatType = stream.getByte(); + //TODO + // There is an issue parsing format descriptors on (some) USB 2.0 pro-audio interfaces + // Since we don't need this info for headset detection, just skip these descriptors + // for now to avoid the (low) possibility of an IndexOutOfBounds exception. + switch (formatType) { +// case FORMAT_TYPE_I: +// return new UsbASFormatI(length, type, subtype, formatType, subclass); +// +// case FORMAT_TYPE_II: +// return new UsbASFormatII(length, type, subtype, formatType, subclass); + + default: + return null; + } + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbASFormatI.java b/services/usb/java/com/android/server/usb/descriptors/UsbASFormatI.java new file mode 100644 index 000000000000..347a6cffb525 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbASFormatI.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Format I interface. + * see Frmts10.pdf section 2.2 + */ +public class UsbASFormatI extends UsbASFormat { + private static final String TAG = "ASFormatI"; + + private byte mNumChannels; // 4:1 + private byte mSubframeSize; // 5:1 frame size in bytes + private byte mBitResolution; // 6:1 sample size in bits + private byte mSampleFreqType; // 7:1 + private int[] mSampleRates; // if mSamFreqType == 0, there will be 2 values: the + // min & max rates otherwise mSamFreqType rates. + // All 3-byte values. All rates in Hz + + public UsbASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) { + super(length, type, subtype, formatType, subclass); + } + + public byte getNumChannels() { + return mNumChannels; + } + + public byte getSubframeSize() { + return mSubframeSize; + } + + public byte getBitResolution() { + return mBitResolution; + } + + public byte getSampleFreqType() { + return mSampleFreqType; + } + + public int[] getSampleRates() { + return mSampleRates; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mNumChannels = stream.getByte(); + mSubframeSize = stream.getByte(); + mBitResolution = stream.getByte(); + mSampleFreqType = stream.getByte(); + if (mSampleFreqType == 0) { + mSampleRates = new int[2]; + mSampleRates[0] = stream.unpackUsbTriple(); + mSampleRates[1] = stream.unpackUsbTriple(); + } else { + mSampleRates = new int[mSampleFreqType]; + for (int index = 0; index < mSampleFreqType; index++) { + mSampleRates[index] = stream.unpackUsbTriple(); + } + } + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbASFormatII.java b/services/usb/java/com/android/server/usb/descriptors/UsbASFormatII.java new file mode 100644 index 000000000000..abdc62145aa2 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbASFormatII.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Format II interface. + * see Frmts10.pdf section 2.3 + */ +public class UsbASFormatII extends UsbASFormat { + private static final String TAG = "ASFormatII"; + + private int mMaxBitRate; // 4:2 Indicates the maximum number of bits per second this + // interface can handle. Expressed in kbits/s. + private int mSamplesPerFrame; // 6:2 Indicates the number of PCM audio samples contained + // in one encoded audio frame. + private byte mSamFreqType; // Indicates how the sampling frequency can be programmed: + // 0: Continuous sampling frequency + // 1..255: The number of discrete sampling frequencies supported + // by the isochronous data endpoint of the AudioStreaming + // interface (ns) + private int[] mSampleRates; // if mSamFreqType == 0, there will be 2 values: + // the min & max rates. otherwise mSamFreqType rates. + // All 3-byte values. All rates in Hz + + public UsbASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) { + super(length, type, subtype, formatType, subclass); + } + + public int getMaxBitRate() { + return mMaxBitRate; + } + + public int getSamplesPerFrame() { + return mSamplesPerFrame; + } + + public byte getSamFreqType() { + return mSamFreqType; + } + + public int[] getSampleRates() { + return mSampleRates; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mMaxBitRate = stream.unpackUsbWord(); + mSamplesPerFrame = stream.unpackUsbWord(); + mSamFreqType = stream.getByte(); + int numFreqs = mSamFreqType == 0 ? 2 : mSamFreqType; + mSampleRates = new int[numFreqs]; + for (int index = 0; index < numFreqs; index++) { + mSampleRates[index] = stream.unpackUsbTriple(); + } + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbASGeneral.java b/services/usb/java/com/android/server/usb/descriptors/UsbASGeneral.java new file mode 100644 index 000000000000..c4f42d318213 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbASGeneral.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific General interface. + * see audio10.pdf section 4.5.2 + */ +public class UsbASGeneral extends UsbACInterface { + private static final String TAG = "ACGeneral"; + + // audio10.pdf - section 4.5.2 + private byte mTerminalLink; // 3:1 The Terminal ID of the Terminal to which the endpoint + // of this interface is connected. + private byte mDelay; // 4:1 Delay introduced by the data path (see Section 3.4, + // “Inter Channel Synchronization”). Expressed in number of frames. + private int mFormatTag; // 5:2 The Audio Data Format that has to be used to communicate + // with this interface. + + public UsbASGeneral(int length, byte type, byte subtype, byte subclass) { + super(length, type, subtype, subclass); + } + + public byte getTerminalLink() { + return mTerminalLink; + } + + public byte getDelay() { + return mDelay; + } + + public int getFormatTag() { + return mFormatTag; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mTerminalLink = stream.getByte(); + mDelay = stream.getByte(); + mFormatTag = stream.unpackUsbWord(); + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbBinaryParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbBinaryParser.java new file mode 100644 index 000000000000..185cee20b090 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbBinaryParser.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +import android.hardware.usb.UsbDeviceConnection; +import android.util.Log; + +import com.android.server.usb.descriptors.report.UsbStrings; + +/** + * @hide + * A class that just walks the descriptors and does a hex dump of the contained values. + * Usefull as a debugging tool. + */ +public class UsbBinaryParser { + private static final String TAG = "UsbBinaryParser"; + private static final boolean LOGGING = false; + + private void dumpDescriptor(ByteStream stream, int length, byte type, StringBuilder builder) { + + // Log + if (LOGGING) { + Log.i(TAG, "l:" + length + " t:" + Integer.toHexString(type) + " " + + UsbStrings.getDescriptorName(type)); + StringBuilder sb = new StringBuilder(); + for (int index = 2; index < length; index++) { + sb.append("0x" + Integer.toHexString(stream.getByte() & 0xFF) + " "); + } + Log.i(TAG, sb.toString()); + } else { + // Screen Dump + builder.append("<p>"); + builder.append("<b> l:" + length + + " t:0x" + Integer.toHexString(type) + " " + + UsbStrings.getDescriptorName(type) + "</b><br>"); + for (int index = 2; index < length; index++) { + builder.append("0x" + Integer.toHexString(stream.getByte() & 0xFF) + " "); + } + builder.append("</p>"); + } + } + + /** + * Walk through descriptor stream and generate an HTML text report of the contents. + * TODO: This should be done in the model of UsbDescriptorsParser/Reporter model. + */ + public void parseDescriptors(UsbDeviceConnection connection, byte[] descriptors, + StringBuilder builder) { + + builder.append("<tt>"); + ByteStream stream = new ByteStream(descriptors); + while (stream.available() > 0) { + int length = (int) stream.getByte() & 0x000000FF; + byte type = stream.getByte(); + dumpDescriptor(stream, length, type, builder); + } + builder.append("</tt>"); + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java new file mode 100644 index 000000000000..8ae6d0f1ee7e --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An USB Config Descriptor. + * see usb11.pdf section 9.6.2 + */ +public class UsbConfigDescriptor extends UsbDescriptor { + private static final String TAG = "Config"; + + private int mTotalLength; // 2:2 Total length in bytes of data returned + private byte mNumInterfaces; // 4:1 Number of Interfaces + private byte mConfigValue; // 5:1 Value to use as an argument to select this configuration + private byte mConfigIndex; // 6:1 Index of String Descriptor describing this configuration + private byte mAttribs; // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered) + // D6 Self Powered + // D5 Remote Wakeup + // D4..0 Reserved, set to 0. + private byte mMaxPower; // 8:1 Maximum Power Consumption in 2mA units + + UsbConfigDescriptor(int length, byte type) { + super(length, type); + } + + public int getTotalLength() { + return mTotalLength; + } + + public byte getNumInterfaces() { + return mNumInterfaces; + } + + public byte getConfigValue() { + return mConfigValue; + } + + public byte getConfigIndex() { + return mConfigIndex; + } + + public byte getAttribs() { + return mAttribs; + } + + public byte getMaxPower() { + return mMaxPower; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mTotalLength = stream.unpackUsbWord(); + mNumInterfaces = stream.getByte(); + mConfigValue = stream.getByte(); + mConfigIndex = stream.getByte(); + mAttribs = stream.getByte(); + mMaxPower = stream.getByte(); + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java new file mode 100644 index 000000000000..63b2d7f6aed7 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDeviceConnection; +import android.util.Log; + +/* + * Some notes about UsbDescriptor and its subclasses. + * + * It is assumed that the user of the UsbDescriptorParser knows what they are doing + * so NO PROTECTION is implemented against "improper" use. Such uses are specifically: + * allocating a UsbDescriptor (subclass) object outside of the context of parsing/reading + * a rawdescriptor stream and perhaps accessing fields which have not been inialized (by + * parsing/reading or course). + */ + +/** + * @hide + * Common superclass for all USB Descriptors. + */ +public abstract class UsbDescriptor { + private static final String TAG = "Descriptor"; + + protected final int mLength; // 0:1 bLength Number Size of the Descriptor in Bytes (18 bytes) + // we store this as an int because Java bytes are SIGNED. + protected final byte mType; // 1:1 bDescriptorType Constant Device Descriptor (0x01) + + private byte[] mRawData; + + private static final int SIZE_STRINGBUFFER = 256; + private static byte[] sStringBuffer = new byte[SIZE_STRINGBUFFER]; + + // Status + public static final int STATUS_UNPARSED = 0; + public static final int STATUS_PARSED_OK = 1; + public static final int STATUS_PARSED_UNDERRUN = 2; + public static final int STATUS_PARSED_OVERRUN = 3; + private int mStatus = STATUS_UNPARSED; + + private static String[] sStatusStrings = { + "UNPARSED", "PARSED - OK", "PARSED - UNDERRUN", "PARSED - OVERRUN"}; + + // Descriptor Type IDs + public static final byte DESCRIPTORTYPE_DEVICE = 0x01; // 1 + public static final byte DESCRIPTORTYPE_CONFIG = 0x02; // 2 + public static final byte DESCRIPTORTYPE_STRING = 0x03; // 3 + public static final byte DESCRIPTORTYPE_INTERFACE = 0x04; // 4 + public static final byte DESCRIPTORTYPE_ENDPOINT = 0x05; // 5 + public static final byte DESCRIPTORTYPE_INTERFACEASSOC = 0x0B; // 11 + public static final byte DESCRIPTORTYPE_BOS = 0x0F; // 15 + public static final byte DESCRIPTORTYPE_CAPABILITY = 0x10; // 16 + + public static final byte DESCRIPTORTYPE_HID = 0x21; // 33 + public static final byte DESCRIPTORTYPE_REPORT = 0x22; // 34 + public static final byte DESCRIPTORTYPE_PHYSICAL = 0x23; // 35 + public static final byte DESCRIPTORTYPE_AUDIO_INTERFACE = 0x24; // 36 + public static final byte DESCRIPTORTYPE_AUDIO_ENDPOINT = 0x25; // 37 + public static final byte DESCRIPTORTYPE_HUB = 0x29; // 41 + public static final byte DESCRIPTORTYPE_SUPERSPEED_HUB = 0x2A; // 42 + public static final byte DESCRIPTORTYPE_ENDPOINT_COMPANION = 0x30; // 48 + + // Class IDs + public static final byte CLASSID_DEVICE = 0x00; + public static final byte CLASSID_AUDIO = 0x01; + public static final byte CLASSID_COM = 0x02; + public static final byte CLASSID_HID = 0x03; + // public static final byte CLASSID_??? = 0x04; + public static final byte CLASSID_PHYSICAL = 0x05; + public static final byte CLASSID_IMAGE = 0x06; + public static final byte CLASSID_PRINTER = 0x07; + public static final byte CLASSID_STORAGE = 0x08; + public static final byte CLASSID_HUB = 0x09; + public static final byte CLASSID_CDC_CONTROL = 0x0A; + public static final byte CLASSID_SMART_CARD = 0x0B; + //public static final byte CLASSID_??? = 0x0C; + public static final byte CLASSID_SECURITY = 0x0D; + public static final byte CLASSID_VIDEO = 0x0E; + public static final byte CLASSID_HEALTHCARE = 0x0F; + public static final byte CLASSID_AUDIOVIDEO = 0x10; + public static final byte CLASSID_BILLBOARD = 0x11; + public static final byte CLASSID_TYPECBRIDGE = 0x12; + public static final byte CLASSID_DIAGNOSTIC = (byte) 0xDC; + public static final byte CLASSID_WIRELESS = (byte) 0xE0; + public static final byte CLASSID_MISC = (byte) 0xEF; + public static final byte CLASSID_APPSPECIFIC = (byte) 0xFE; + public static final byte CLASSID_VENDSPECIFIC = (byte) 0xFF; + + // Audio Subclass codes + public static final byte AUDIO_SUBCLASS_UNDEFINED = 0x00; + public static final byte AUDIO_AUDIOCONTROL = 0x01; + public static final byte AUDIO_AUDIOSTREAMING = 0x02; + public static final byte AUDIO_MIDISTREAMING = 0x03; + + // Request IDs + public static final int REQUEST_GET_STATUS = 0x00; + public static final int REQUEST_CLEAR_FEATURE = 0x01; + public static final int REQUEST_SET_FEATURE = 0x03; + public static final int REQUEST_GET_ADDRESS = 0x05; + public static final int REQUEST_GET_DESCRIPTOR = 0x06; + public static final int REQUEST_SET_DESCRIPTOR = 0x07; + public static final int REQUEST_GET_CONFIGURATION = 0x08; + public static final int REQUEST_SET_CONFIGURATION = 0x09; + + /** + * @throws IllegalArgumentException + */ + UsbDescriptor(int length, byte type) { + // a descriptor has at least a length byte and type byte + // one could imagine an empty one otherwise + if (length < 2) { + // huh? + throw new IllegalArgumentException(); + } + + mLength = length; + mType = type; + } + + public int getLength() { + return mLength; + } + + public byte getType() { + return mType; + } + + public int getStatus() { + return mStatus; + } + + public void setStatus(int status) { + mStatus = status; + } + + public String getStatusString() { + return sStatusStrings[mStatus]; + } + + public byte[] getRawData() { + return mRawData; + } + + /** + * Called by the parser for any necessary cleanup. + */ + public void postParse(ByteStream stream) { + // Status + int bytesRead = stream.getReadCount(); + if (bytesRead < mLength) { + // Too cold... + stream.advance(mLength - bytesRead); + mStatus = STATUS_PARSED_UNDERRUN; + Log.w(TAG, "UNDERRUN t:0x" + Integer.toHexString(mType) + + " r:" + bytesRead + " < l:" + mLength); + } else if (bytesRead > mLength) { + // Too hot... + stream.reverse(bytesRead - mLength); + mStatus = STATUS_PARSED_OVERRUN; + Log.w(TAG, "OVERRRUN t:0x" + Integer.toHexString(mType) + + " r:" + bytesRead + " > l:" + mLength); + } else { + // Just right! + mStatus = STATUS_PARSED_OK; + } + } + + /** + * Reads data fields from specified raw-data stream. + */ + public int parseRawDescriptors(ByteStream stream) { + int numRead = stream.getReadCount(); + int dataLen = mLength - numRead; + if (dataLen > 0) { + mRawData = new byte[dataLen]; + for (int index = 0; index < dataLen; index++) { + mRawData[index] = stream.getByte(); + } + } + return mLength; + } + + /** + * Gets a string for the specified index from the USB Device's string descriptors. + */ + public static String getUsbDescriptorString(UsbDeviceConnection connection, byte strIndex) { + String usbStr = ""; + if (strIndex != 0) { + try { + int rdo = connection.controlTransfer( + UsbConstants.USB_DIR_IN | UsbConstants.USB_TYPE_STANDARD, + REQUEST_GET_DESCRIPTOR, + (DESCRIPTORTYPE_STRING << 8) | strIndex, + 0, + sStringBuffer, + 0xFF, + 0); + if (rdo >= 0) { + usbStr = new String(sStringBuffer, 2, rdo - 2, "UTF-16LE"); + } else { + usbStr = "?"; + } + } catch (Exception e) { + Log.e(TAG, "Can not communicate with USB device", e); + } + } + return usbStr; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java new file mode 100644 index 000000000000..7c074dadadf9 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +import android.util.Log; + +import java.util.ArrayList; + +/** + * @hide + * Class for parsing a binary stream of USB Descriptors. + */ +public class UsbDescriptorParser { + private static final String TAG = "DescriptorParser"; + + // Descriptor Objects + private ArrayList<UsbDescriptor> mDescriptors = new ArrayList<UsbDescriptor>(); + + private UsbDeviceDescriptor mDeviceDescriptor; + private UsbInterfaceDescriptor mCurInterfaceDescriptor; + + public UsbDescriptorParser() {} + + /** + * The probability (as returned by getHeadsetProbability() at which we conclude + * the peripheral is a headset. + */ + private static final float IN_HEADSET_TRIGGER = 0.75f; + private static final float OUT_HEADSET_TRIGGER = 0.75f; + + private UsbDescriptor allocDescriptor(ByteStream stream) { + stream.resetReadCount(); + + int length = (int) stream.getByte() & 0x000000FF; + byte type = stream.getByte(); + + UsbDescriptor descriptor = null; + switch (type) { + /* + * Standard + */ + case UsbDescriptor.DESCRIPTORTYPE_DEVICE: + descriptor = mDeviceDescriptor = new UsbDeviceDescriptor(length, type); + break; + + case UsbDescriptor.DESCRIPTORTYPE_CONFIG: + descriptor = new UsbConfigDescriptor(length, type); + break; + + case UsbDescriptor.DESCRIPTORTYPE_INTERFACE: + descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type); + break; + + case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT: + descriptor = new UsbEndpointDescriptor(length, type); + break; + + /* + * HID + */ + case UsbDescriptor.DESCRIPTORTYPE_HID: + descriptor = new UsbHIDDescriptor(length, type); + break; + + /* + * Other + */ + case UsbDescriptor.DESCRIPTORTYPE_INTERFACEASSOC: + descriptor = new UsbInterfaceAssoc(length, type); + break; + + /* + * Audio Class Specific + */ + case UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE: + descriptor = UsbACInterface.allocDescriptor(this, stream, length, type); + break; + + case UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT: + descriptor = UsbACEndpoint.allocDescriptor(this, length, type); + break; + + default: + break; + } + + if (descriptor == null) { + // Unknown Descriptor + Log.i(TAG, "Unknown Descriptor len:" + length + " type:0x" + + Integer.toHexString(type)); + descriptor = new UsbUnknown(length, type); + } + + return descriptor; + } + + public UsbDeviceDescriptor getDeviceDescriptor() { + return mDeviceDescriptor; + } + + public UsbInterfaceDescriptor getCurInterface() { + return mCurInterfaceDescriptor; + } + + /** + * @hide + */ + public boolean parseDescriptors(byte[] descriptors) { + try { + mDescriptors.clear(); + + ByteStream stream = new ByteStream(descriptors); + while (stream.available() > 0) { + UsbDescriptor descriptor = allocDescriptor(stream); + if (descriptor != null) { + // Parse + descriptor.parseRawDescriptors(stream); + mDescriptors.add(descriptor); + + // Clean up + descriptor.postParse(stream); + } + } + return true; + } catch (Exception ex) { + Log.e(TAG, "Exception parsing USB descriptors.", ex); + } + return false; + } + + /** + * @hide + */ + public boolean parseDevice(String deviceAddr) { + byte[] rawDescriptors = getRawDescriptors(deviceAddr); + return rawDescriptors != null && parseDescriptors(rawDescriptors); + } + + private native byte[] getRawDescriptors(String deviceAddr); + + public int getParsingSpec() { + return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0; + } + + public ArrayList<UsbDescriptor> getDescriptors() { + return mDescriptors; + } + + /** + * @hide + */ + public ArrayList<UsbDescriptor> getDescriptors(byte type) { + ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); + for (UsbDescriptor descriptor : mDescriptors) { + if (descriptor.getType() == type) { + list.add(descriptor); + } + } + return list; + } + + /** + * @hide + */ + public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(byte usbClass) { + ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); + for (UsbDescriptor descriptor : mDescriptors) { + // ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE + if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_INTERFACE) { + if (descriptor instanceof UsbInterfaceDescriptor) { + UsbInterfaceDescriptor intrDesc = (UsbInterfaceDescriptor) descriptor; + if (intrDesc.getUsbClass() == usbClass) { + list.add(descriptor); + } + } else { + Log.w(TAG, "Unrecognized Interface l:" + descriptor.getLength() + + " t:0x" + Integer.toHexString(descriptor.getType())); + } + } + } + return list; + } + + /** + * @hide + */ + public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, byte subclass) { + ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); + for (UsbDescriptor descriptor : mDescriptors) { + if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) { + // ensure that this isn't an unrecognized DESCRIPTORTYPE_AUDIO_INTERFACE + if (descriptor instanceof UsbACInterface) { + UsbACInterface acDescriptor = (UsbACInterface) descriptor; + if (acDescriptor.getSubtype() == subtype + && acDescriptor.getSubclass() == subclass) { + list.add(descriptor); + } + } else { + Log.w(TAG, "Unrecognized Audio Interface l:" + descriptor.getLength() + + " t:0x" + Integer.toHexString(descriptor.getType())); + } + } + } + return list; + } + + /** + * @hide + */ + public boolean hasHIDDescriptor() { + ArrayList<UsbDescriptor> descriptors = + getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_HID); + return !descriptors.isEmpty(); + } + + /** + * @hide + */ + public boolean hasMIDIInterface() { + ArrayList<UsbDescriptor> descriptors = + getInterfaceDescriptorsForClass(UsbDescriptor.CLASSID_AUDIO); + for (UsbDescriptor descriptor : descriptors) { + // enusure that this isn't an unrecognized interface descriptor + if (descriptor instanceof UsbInterfaceDescriptor) { + UsbInterfaceDescriptor interfaceDescr = (UsbInterfaceDescriptor) descriptor; + if (interfaceDescr.getUsbSubclass() == UsbDescriptor.AUDIO_MIDISTREAMING) { + return true; + } + } else { + Log.w(TAG, "Undefined Audio Class Interface l:" + descriptor.getLength() + + " t:0x" + Integer.toHexString(descriptor.getType())); + } + } + return false; + } + + /** + * @hide + */ + public float getInputHeadsetProbability() { + if (hasMIDIInterface()) { + return 0.0f; + } + + float probability = 0.0f; + ArrayList<UsbDescriptor> acDescriptors; + + // Look for a microphone + boolean hasMic = false; + acDescriptors = getACInterfaceDescriptors(UsbACInterface.ACI_INPUT_TERMINAL, + UsbACInterface.AUDIO_AUDIOCONTROL); + for (UsbDescriptor descriptor : acDescriptors) { + if (descriptor instanceof UsbACInputTerminal) { + UsbACInputTerminal inDescr = (UsbACInputTerminal) descriptor; + if (inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_IN_MIC + || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET + || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED + || inDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_EXTERN_LINE) { + hasMic = true; + break; + } + } else { + Log.w(TAG, "Undefined Audio Input terminal l:" + descriptor.getLength() + + " t:0x" + Integer.toHexString(descriptor.getType())); + } + } + + // Look for a "speaker" + boolean hasSpeaker = false; + acDescriptors = + getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, + UsbACInterface.AUDIO_AUDIOCONTROL); + for (UsbDescriptor descriptor : acDescriptors) { + if (descriptor instanceof UsbACOutputTerminal) { + UsbACOutputTerminal outDescr = (UsbACOutputTerminal) descriptor; + if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER + || outDescr.getTerminalType() + == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES + || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) { + hasSpeaker = true; + break; + } + } else { + Log.w(TAG, "Undefined Audio Output terminal l:" + descriptor.getLength() + + " t:0x" + Integer.toHexString(descriptor.getType())); + } + } + + if (hasMic && hasSpeaker) { + probability += 0.75f; + } + + if (hasMic && hasHIDDescriptor()) { + probability += 0.25f; + } + + return probability; + } + + /** + * getInputHeadsetProbability() reports a probability of a USB Input peripheral being a + * headset. The probability range is between 0.0f (definitely NOT a headset) and + * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient + * to count on the peripheral being a headset. + */ + public boolean isInputHeadset() { + return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER; + } + + /** + * @hide + */ + public float getOutputHeadsetProbability() { + if (hasMIDIInterface()) { + return 0.0f; + } + + float probability = 0.0f; + ArrayList<UsbDescriptor> acDescriptors; + + // Look for a "speaker" + boolean hasSpeaker = false; + acDescriptors = + getACInterfaceDescriptors(UsbACInterface.ACI_OUTPUT_TERMINAL, + UsbACInterface.AUDIO_AUDIOCONTROL); + for (UsbDescriptor descriptor : acDescriptors) { + if (descriptor instanceof UsbACOutputTerminal) { + UsbACOutputTerminal outDescr = (UsbACOutputTerminal) descriptor; + if (outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_OUT_SPEAKER + || outDescr.getTerminalType() + == UsbTerminalTypes.TERMINAL_OUT_HEADPHONES + || outDescr.getTerminalType() == UsbTerminalTypes.TERMINAL_BIDIR_HEADSET) { + hasSpeaker = true; + break; + } + } else { + Log.w(TAG, "Undefined Audio Output terminal l:" + descriptor.getLength() + + " t:0x" + Integer.toHexString(descriptor.getType())); + } + } + + if (hasSpeaker) { + probability += 0.75f; + } + + if (hasSpeaker && hasHIDDescriptor()) { + probability += 0.25f; + } + + return probability; + } + + /** + * getOutputHeadsetProbability() reports a probability of a USB Output peripheral being a + * headset. The probability range is between 0.0f (definitely NOT a headset) and + * 1.0f (definitely IS a headset). A probability of 0.75f seems sufficient + * to count on the peripheral being a headset. + */ + public boolean isOutputHeadset() { + return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER; + } + +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java new file mode 100644 index 000000000000..90848caba852 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * A USB Device Descriptor. + * see usb11.pdf section 9.6.1 + */ +/* public */ public class UsbDeviceDescriptor extends UsbDescriptor { + private static final String TAG = "Device"; + + private int mSpec; // 2:2 bcdUSB 2 BCD USB Specification Number - BCD + private byte mDevClass; // 4:1 class code + private byte mDevSubClass; // 5:1 subclass code + private byte mProtocol; // 6:1 protocol + private byte mPacketSize; // 7:1 Maximum Packet Size for Zero Endpoint. + // Valid Sizes are 8, 16, 32, 64 + private int mVendorID; // 8:2 vendor ID + private int mProductID; // 10:2 product ID + private int mDeviceRelease; // 12:2 Device Release number - BCD + private byte mMfgIndex; // 14:1 Index of Manufacturer String Descriptor + private byte mProductIndex; // 15:1 Index of Product String Descriptor + private byte mSerialNum; // 16:1 Index of Serial Number String Descriptor + private byte mNumConfigs; // 17:1 Number of Possible Configurations + + UsbDeviceDescriptor(int length, byte type) { + super(length, type); + } + + public int getSpec() { + return mSpec; + } + + public byte getDevClass() { + return mDevClass; + } + + public byte getDevSubClass() { + return mDevSubClass; + } + + public byte getProtocol() { + return mProtocol; + } + + public byte getPacketSize() { + return mPacketSize; + } + + public int getVendorID() { + return mVendorID; + } + + public int getProductID() { + return mProductID; + } + + public int getDeviceRelease() { + return mDeviceRelease; + } + + public byte getMfgIndex() { + return mMfgIndex; + } + + public byte getProductIndex() { + return mProductIndex; + } + + public byte getSerialNum() { + return mSerialNum; + } + + public byte getNumConfigs() { + return mNumConfigs; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mSpec = stream.unpackUsbWord(); + mDevClass = stream.getByte(); + mDevSubClass = stream.getByte(); + mProtocol = stream.getByte(); + mPacketSize = stream.getByte(); + mVendorID = stream.unpackUsbWord(); + mProductID = stream.unpackUsbWord(); + mDeviceRelease = stream.unpackUsbWord(); + mMfgIndex = stream.getByte(); + mProductIndex = stream.getByte(); + mSerialNum = stream.getByte(); + mNumConfigs = stream.getByte(); + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java new file mode 100644 index 000000000000..def670093e6e --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * A Usb Endpoint Descriptor. + * see usb11.pdf section 9.6.4 + */ +public class UsbEndpointDescriptor extends UsbDescriptor { + private static final String TAG = "EndPoint"; + + public static final byte MASK_ENDPOINT_ADDRESS = 0b0001111; + public static final byte MASK_ENDPOINT_DIRECTION = (byte) 0b10000000; + public static final byte DIRECTION_OUTPUT = 0x00; + public static final byte DIRECTION_INPUT = (byte) 0x80; + + public static final byte MASK_ATTRIBS_TRANSTYPE = 0b00000011; + public static final byte TRANSTYPE_CONTROL = 0x00; + public static final byte TRANSTYPE_ISO = 0x01; + public static final byte TRANSTYPE_BULK = 0x02; + public static final byte TRANSTYPE_INTERRUPT = 0x03; + + public static final byte MASK_ATTRIBS_SYNCTYPE = 0b00001100; + public static final byte SYNCTYPE_NONE = 0b00000000; + public static final byte SYNCTYPE_ASYNC = 0b00000100; + public static final byte SYNCTYPE_ADAPTSYNC = 0b00001000; + public static final byte SYNCTYPE_RESERVED = 0b00001100; + + public static final byte MASK_ATTRIBS_USEAGE = 0b00110000; + public static final byte USEAGE_DATA = 0b00000000; + public static final byte USEAGE_FEEDBACK = 0b00010000; + public static final byte USEAGE_EXPLICIT = 0b00100000; + public static final byte USEAGE_RESERVED = 0b00110000; + + private byte mEndpointAddress; // 2:1 Endpoint Address + // Bits 0..3b Endpoint Number. + // Bits 4..6b Reserved. Set to Zero + // Bits 7 Direction 0 = Out, 1 = In + // (Ignored for Control Endpoints) + private byte mAttributes; // 3:1 Various flags + // Bits 0..1 Transfer Type: + // 00 = Control, 01 = Isochronous, 10 = Bulk, 11 = Interrupt + // Bits 2..7 are reserved. If Isochronous endpoint, + // Bits 3..2 = Synchronisation Type (Iso Mode) + // 00 = No Synchonisation + // 01 = Asynchronous + // 10 = Adaptive + // 11 = Synchronous + // Bits 5..4 = Usage Type (Iso Mode) + // 00: Data Endpoint + // 01:Feedback Endpoint 10 + // Explicit Feedback Data Endpoint + // 11: Reserved + private int mPacketSize; // 4:2 Maximum Packet Size this endpoint is capable of + // sending or receiving + private byte mInterval; // 6:1 Interval for polling endpoint data transfers. Value in + // frame counts. + // Ignored for Bulk & Control Endpoints. Isochronous must equal + // 1 and field may range from 1 to 255 for interrupt endpoints. + private byte mRefresh; + private byte mSyncAddress; + + public UsbEndpointDescriptor(int length, byte type) { + super(length, type); + } + + public byte getEndpointAddress() { + return mEndpointAddress; + } + + public byte getAttributes() { + return mAttributes; + } + + public int getPacketSize() { + return mPacketSize; + } + + public byte getInterval() { + return mInterval; + } + + public byte getRefresh() { + return mRefresh; + } + + public byte getSyncAddress() { + return mSyncAddress; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mEndpointAddress = stream.getByte(); + mAttributes = stream.getByte(); + mPacketSize = stream.unpackUsbWord(); + mInterval = stream.getByte(); + if (mLength == 9) { + mRefresh = stream.getByte(); + mSyncAddress = stream.getByte(); + } + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbHIDDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbHIDDescriptor.java new file mode 100644 index 000000000000..56c07ec9a071 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbHIDDescriptor.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * A USB HID (Human Interface Descriptor). + * see HID1_11.pdf - 6.2.1 + */ +public class UsbHIDDescriptor extends UsbDescriptor { + private static final String TAG = "HID"; + + private int mRelease; // 2:2 the HID Class Specification release. + private byte mCountryCode; // 4:1 country code of the localized hardware. + private byte mNumDescriptors; // number of descriptors (always at least one + // i.e. Report descriptor.) + private byte mDescriptorType; // 6:1 type of class descriptor. + // See Section 7.1.2: Set_Descriptor + // Request for a table of class descriptor constants. + private int mDescriptorLen; // 7:2 Numeric expression that is the total size of + // the Report descriptor. + + public UsbHIDDescriptor(int length, byte type) { + super(length, type); + } + + public int getRelease() { + return mRelease; + } + + public byte getCountryCode() { + return mCountryCode; + } + + public byte getNumDescriptors() { + return mNumDescriptors; + } + + public byte getDescriptorType() { + return mDescriptorType; + } + + public int getDescriptorLen() { + return mDescriptorLen; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mRelease = stream.unpackUsbWord(); + mCountryCode = stream.getByte(); + mNumDescriptors = stream.getByte(); + mDescriptorType = stream.getByte(); + mDescriptorLen = stream.unpackUsbWord(); + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceAssoc.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceAssoc.java new file mode 100644 index 000000000000..4b18a01b1c8b --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceAssoc.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * A USB Interface Association Descriptor. + * found this one here: http://www.usb.org/developers/docs/whitepapers/iadclasscode_r10.pdf + * also: https://msdn.microsoft.com/en-us/library/windows/hardware/ff540054(v=vs.85).aspx + */ +public class UsbInterfaceAssoc extends UsbDescriptor { + private static final String TAG = "InterfaceAssoc"; + + private byte mFirstInterface; + private byte mInterfaceCount; + private byte mFunctionClass; + private byte mFunctionSubClass; + private byte mFunctionProtocol; + private byte mFunction; + + public UsbInterfaceAssoc(int length, byte type) { + super(length, type); + } + + public byte getFirstInterface() { + return mFirstInterface; + } + + public byte getInterfaceCount() { + return mInterfaceCount; + } + + public byte getFunctionClass() { + return mFunctionClass; + } + + public byte getFunctionSubClass() { + return mFunctionSubClass; + } + + public byte getFunctionProtocol() { + return mFunctionProtocol; + } + + public byte getFunction() { + return mFunction; + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mFirstInterface = stream.getByte(); + mInterfaceCount = stream.getByte(); + mFunctionClass = stream.getByte(); + mFunctionSubClass = stream.getByte(); + mFunctionProtocol = stream.getByte(); + mFunction = stream.getByte(); + + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java new file mode 100644 index 000000000000..21b5e0cbaa1b --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * A common super-class for all USB Interface Descritor subtypes. + * see usb11.pdf section 9.6.3 + */ +public class UsbInterfaceDescriptor extends UsbDescriptor { + private static final String TAG = "Interface"; + + protected byte mInterfaceNumber; // 2:1 Number of Interface + protected byte mAlternateSetting; // 3:1 Value used to select alternative setting + protected byte mNumEndpoints; // 4:1 Number of Endpoints used for this interface + protected byte mUsbClass; // 5:1 Class Code + protected byte mUsbSubclass; // 6:1 Subclass Code + protected byte mProtocol; // 7:1 Protocol Code + protected byte mDescrIndex; // 8:1 Index of String Descriptor Describing this interface + + UsbInterfaceDescriptor(int length, byte type) { + super(length, type); + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + mInterfaceNumber = stream.getByte(); + mAlternateSetting = stream.getByte(); + mNumEndpoints = stream.getByte(); + mUsbClass = stream.getByte(); + mUsbSubclass = stream.getByte(); + mProtocol = stream.getByte(); + mDescrIndex = stream.getByte(); + + return mLength; + } + + public byte getInterfaceNumber() { + return mInterfaceNumber; + } + + public byte getAlternateSetting() { + return mAlternateSetting; + } + + public byte getNumEndpoints() { + return mNumEndpoints; + } + + public byte getUsbClass() { + return mUsbClass; + } + + public byte getUsbSubclass() { + return mUsbSubclass; + } + + public byte getProtocol() { + return mProtocol; + } + + public byte getDescrIndex() { + return mDescrIndex; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java new file mode 100644 index 000000000000..4452b23cb6ae --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Midi Streaming Interface. + * see midi10.pdf section 6.1.2.1 + */ +public class UsbMSMidiHeader extends UsbACInterface { + private static final String TAG = "MSMidiHeader"; + + public UsbMSMidiHeader(int length, byte type, byte subtype, byte subclass) { + super(length, type, subtype, subclass); + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + // TODO - read data memebers + stream.advance(mLength - stream.getReadCount()); + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java new file mode 100644 index 000000000000..2d33ba7727dd --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Midi Input Jack Interface. + * see midi10.pdf section B.4.3 + */ +public class UsbMSMidiInputJack extends UsbACInterface { + private static final String TAG = "MSMidiInputJack"; + + UsbMSMidiInputJack(int length, byte type, byte subtype, byte subclass) { + super(length, type, subtype, subclass); + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + // TODO - read data memebers + stream.advance(mLength - stream.getReadCount()); + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java new file mode 100644 index 000000000000..bd2dc11d57df --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * An audio class-specific Midi Output Jack Interface. + * see midi10.pdf section B.4.4 + */ +public class UsbMSMidiOutputJack extends UsbACInterface { + private static final String TAG = "MSMidiOutputJack"; + + public UsbMSMidiOutputJack(int length, byte type, byte subtype, byte subclass) { + super(length, type, subtype, subclass); + } + + @Override + public int parseRawDescriptors(ByteStream stream) { + // TODO - read data memebers + stream.advance(mLength - stream.getReadCount()); + return mLength; + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbTerminalTypes.java b/services/usb/java/com/android/server/usb/descriptors/UsbTerminalTypes.java new file mode 100644 index 000000000000..b5214625126a --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbTerminalTypes.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * A class for decoding information in Terminal Descriptors. + * see termt10.pdf + */ +public class UsbTerminalTypes { + private static final String TAG = "TerminalTypes"; + + // USB + public static final int TERMINAL_USB_STREAMING = 0x0101; + + // Inputs + public static final int TERMINAL_IN_UNDEFINED = 0x0200; + public static final int TERMINAL_IN_MIC = 0x0201; + public static final int TERMINAL_IN_DESKTOP_MIC = 0x0202; + public static final int TERMINAL_IN_PERSONAL_MIC = 0x0203; + public static final int TERMINAL_IN_OMNI_MIC = 0x0204; + public static final int TERMINAL_IN_MIC_ARRAY = 0x0205; + public static final int TERMINAL_IN_PROC_MIC_ARRAY = 0x0206; + + // Outputs + public static final int TERMINAL_OUT_UNDEFINED = 0x0300; + public static final int TERMINAL_OUT_SPEAKER = 0x0301; + public static final int TERMINAL_OUT_HEADPHONES = 0x0302; + public static final int TERMINAL_OUT_HEADMOUNTED = 0x0303; + public static final int TERMINAL_OUT_DESKTOPSPEAKER = 0x0304; + public static final int TERMINAL_OUT_ROOMSPEAKER = 0x0305; + public static final int TERMINAL_OUT_COMSPEAKER = 0x0306; + public static final int TERMINAL_OUT_LFSPEAKER = 0x0307; + + // Bi-directional + public static final int TERMINAL_BIDIR_UNDEFINED = 0x0400; + public static final int TERMINAL_BIDIR_HANDSET = 0x0401; + public static final int TERMINAL_BIDIR_HEADSET = 0x0402; + public static final int TERMINAL_BIDIR_SKRPHONE = 0x0403; + public static final int TERMINAL_BIDIR_SKRPHONE_SUPRESS = 0x0404; + public static final int TERMINAL_BIDIR_SKRPHONE_CANCEL = 0x0405; + + // Telephony + public static final int TERMINAL_TELE_UNDEFINED = 0x0500; + public static final int TERMINAL_TELE_PHONELINE = 0x0501; + public static final int TERMINAL_TELE_PHONE = 0x0502; + public static final int TERMINAL_TELE_DOWNLINEPHONE = 0x0503; + + // External + public static final int TERMINAL_EXTERN_UNDEFINED = 0x0600; + public static final int TERMINAL_EXTERN_ANALOG = 0x0601; + public static final int TERMINAL_EXTERN_DIGITAL = 0x0602; + public static final int TERMINAL_EXTERN_LINE = 0x0603; + public static final int TERMINAL_EXTERN_LEGACY = 0x0604; + public static final int TERMINAL_EXTERN_SPIDF = 0x0605; + public static final int TERMINAL_EXTERN_1394DA = 0x0606; + public static final int TERMINAL_EXTERN_1394DV = 0x0607; + + public static final int TERMINAL_EMBED_UNDEFINED = 0x0700; + public static final int TERMINAL_EMBED_CALNOISE = 0x0701; + public static final int TERMINAL_EMBED_EQNOISE = 0x0702; + public static final int TERMINAL_EMBED_CDPLAYER = 0x0703; + public static final int TERMINAL_EMBED_DAT = 0x0704; + public static final int TERMINAL_EMBED_DCC = 0x0705; + public static final int TERMINAL_EMBED_MINIDISK = 0x0706; + public static final int TERMINAL_EMBED_ANALOGTAPE = 0x0707; + public static final int TERMINAL_EMBED_PHONOGRAPH = 0x0708; + public static final int TERMINAL_EMBED_VCRAUDIO = 0x0709; + public static final int TERMINAL_EMBED_VIDDISKAUDIO = 0x070A; + public static final int TERMINAL_EMBED_DVDAUDIO = 0x070B; + public static final int TERMINAL_EMBED_TVAUDIO = 0x070C; + public static final int TERMINAL_EMBED_SATELLITEAUDIO = 0x070D; + public static final int TERMINAL_EMBED_CABLEAUDIO = 0x070E; + public static final int TERMINAL_EMBED_DSSAUDIO = 0x070F; + public static final int TERMINAL_EMBED_RADIOAUDIO = 0x0710; + public static final int TERMINAL_EMBED_RADIOTRANSMITTER = 0x0711; + public static final int TERMINAL_EMBED_MULTITRACK = 0x0712; + public static final int TERMINAL_EMBED_SYNTHESIZER = 0x0713; + +} diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbUnknown.java b/services/usb/java/com/android/server/usb/descriptors/UsbUnknown.java new file mode 100644 index 000000000000..a6fe8bba3508 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/UsbUnknown.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors; + +/** + * @hide + * A holder for any unrecognized descriptor encountered in the descriptor stream. + */ +public class UsbUnknown extends UsbDescriptor { + static final String TAG = "Unknown"; + + public UsbUnknown(int length, byte type) { + super(length, type); + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/report/HTMLReporter.java b/services/usb/java/com/android/server/usb/descriptors/report/HTMLReporter.java new file mode 100644 index 000000000000..c98789d880a0 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/report/HTMLReporter.java @@ -0,0 +1,572 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors.report; + +import android.hardware.usb.UsbDeviceConnection; + +import com.android.server.usb.descriptors.UsbACAudioControlEndpoint; +import com.android.server.usb.descriptors.UsbACAudioStreamEndpoint; +import com.android.server.usb.descriptors.UsbACFeatureUnit; +import com.android.server.usb.descriptors.UsbACHeader; +import com.android.server.usb.descriptors.UsbACInputTerminal; +import com.android.server.usb.descriptors.UsbACInterface; +import com.android.server.usb.descriptors.UsbACMidiEndpoint; +import com.android.server.usb.descriptors.UsbACMixerUnit; +import com.android.server.usb.descriptors.UsbACOutputTerminal; +import com.android.server.usb.descriptors.UsbACSelectorUnit; +import com.android.server.usb.descriptors.UsbACTerminal; +import com.android.server.usb.descriptors.UsbASFormat; +import com.android.server.usb.descriptors.UsbASFormatI; +import com.android.server.usb.descriptors.UsbASFormatII; +import com.android.server.usb.descriptors.UsbASGeneral; +import com.android.server.usb.descriptors.UsbConfigDescriptor; +import com.android.server.usb.descriptors.UsbDescriptor; +import com.android.server.usb.descriptors.UsbDeviceDescriptor; +import com.android.server.usb.descriptors.UsbEndpointDescriptor; +import com.android.server.usb.descriptors.UsbHIDDescriptor; +import com.android.server.usb.descriptors.UsbInterfaceAssoc; +import com.android.server.usb.descriptors.UsbInterfaceDescriptor; +import com.android.server.usb.descriptors.UsbMSMidiHeader; +import com.android.server.usb.descriptors.UsbMSMidiInputJack; +import com.android.server.usb.descriptors.UsbMSMidiOutputJack; +import com.android.server.usb.descriptors.UsbUnknown; + +/** + * Implements the Reporter inteface to provide HTML reporting for UsbDescriptor subclasses. + */ +public class HTMLReporter implements Reporter { + private final StringBuilder mStringBuilder; + private final UsbDeviceConnection mConnection; + + public HTMLReporter(StringBuilder stringBuilder, UsbDeviceConnection connection) { + mStringBuilder = stringBuilder; + mConnection = connection; + } + + /* + * HTML Helpers + */ + private void writeHeader(int level, String text) { + mStringBuilder + .append("<h").append(level).append('>') + .append(text) + .append("</h").append(level).append('>'); + } + + private void openParagraph() { + mStringBuilder.append("<p>"); + } + + private void closeParagraph() { + mStringBuilder.append("</p>"); + } + + private void writeParagraph(String text) { + openParagraph(); + mStringBuilder.append(text); + closeParagraph(); + } + + private void openList() { + mStringBuilder.append("<ul>"); + } + + private void closeList() { + mStringBuilder.append("</ul>"); + } + + private void openListItem() { + mStringBuilder.append("<li>"); + } + + private void closeListItem() { + mStringBuilder.append("</li>"); + } + + private void writeListItem(String text) { + openListItem(); + mStringBuilder.append(text); + closeListItem(); + } + + /* + * Data Formating Helpers + */ + private static String getHexString(byte value) { + return "0x" + Integer.toHexString(((int) value) & 0xFF).toUpperCase(); + } + + private static String getBCDString(int value) { + int major = value >> 8; + int minor = (value >> 4) & 0x0F; + int subminor = value & 0x0F; + + return "" + major + "." + minor + subminor; + } + + private static String getHexString(int value) { + int intValue = value & 0xFFFF; + return "0x" + Integer.toHexString(intValue).toUpperCase(); + } + + private void dumpHexArray(byte[] rawData, StringBuilder builder) { + if (rawData != null) { + // Assume the type and Length and perhaps sub-type have been displayed + openParagraph(); + for (int index = 0; index < rawData.length; index++) { + builder.append(getHexString(rawData[index]) + " "); + } + closeParagraph(); + } + } + + /** + * Decode ACTUAL UsbDescriptor sub classes and call type-specific report methods. + */ + @Override + public void report(UsbDescriptor descriptor) { + if (descriptor instanceof UsbDeviceDescriptor) { + tsReport((UsbDeviceDescriptor) descriptor); + } else if (descriptor instanceof UsbConfigDescriptor) { + tsReport((UsbConfigDescriptor) descriptor); + } else if (descriptor instanceof UsbInterfaceDescriptor) { + tsReport((UsbInterfaceDescriptor) descriptor); + } else if (descriptor instanceof UsbEndpointDescriptor) { + tsReport((UsbEndpointDescriptor) descriptor); + } else if (descriptor instanceof UsbHIDDescriptor) { + tsReport((UsbHIDDescriptor) descriptor); + } else if (descriptor instanceof UsbACAudioControlEndpoint) { + tsReport((UsbACAudioControlEndpoint) descriptor); + } else if (descriptor instanceof UsbACAudioStreamEndpoint) { + tsReport((UsbACAudioStreamEndpoint) descriptor); + } else if (descriptor instanceof UsbACHeader) { + tsReport((UsbACHeader) descriptor); + } else if (descriptor instanceof UsbACFeatureUnit) { + tsReport((UsbACFeatureUnit) descriptor); + } else if (descriptor instanceof UsbACInputTerminal) { + tsReport((UsbACInputTerminal) descriptor); + } else if (descriptor instanceof UsbACOutputTerminal) { + tsReport((UsbACOutputTerminal) descriptor); + } else if (descriptor instanceof UsbACMidiEndpoint) { + tsReport((UsbACMidiEndpoint) descriptor); + } else if (descriptor instanceof UsbACMixerUnit) { + tsReport((UsbACMixerUnit) descriptor); + } else if (descriptor instanceof UsbACSelectorUnit) { + tsReport((UsbACSelectorUnit) descriptor); + } else if (descriptor instanceof UsbASFormatI) { + tsReport((UsbASFormatI) descriptor); + } else if (descriptor instanceof UsbASFormatII) { + tsReport((UsbASFormatII) descriptor); + } else if (descriptor instanceof UsbASFormat) { + tsReport((UsbASFormat) descriptor); + } else if (descriptor instanceof UsbASGeneral) { + tsReport((UsbASGeneral) descriptor); + } else if (descriptor instanceof UsbInterfaceAssoc) { + tsReport((UsbInterfaceAssoc) descriptor); + } else if (descriptor instanceof UsbMSMidiHeader) { + tsReport((UsbMSMidiHeader) descriptor); + } else if (descriptor instanceof UsbMSMidiInputJack) { + tsReport((UsbMSMidiInputJack) descriptor); + } else if (descriptor instanceof UsbMSMidiOutputJack) { + tsReport((UsbMSMidiOutputJack) descriptor); + } else if (descriptor instanceof UsbUnknown) { + tsReport((UsbUnknown) descriptor); + } else if (descriptor instanceof UsbACInterface) { + tsReport((UsbACInterface) descriptor); + } else if (descriptor instanceof UsbDescriptor) { + tsReport((UsbDescriptor) descriptor); + } + } + + // + // Type-specific report() implementations + // + private void tsReport(UsbDescriptor descriptor) { + int length = descriptor.getLength(); + byte type = descriptor.getType(); + int status = descriptor.getStatus(); + + String descTypeStr = UsbStrings.getDescriptorName(type); + writeParagraph(descTypeStr + ":" + type + " l:" + length + " s:" + status); + } + + private void tsReport(UsbDeviceDescriptor descriptor) { + writeHeader(1, "Device len:" + descriptor.getLength()); + openList(); + + int spec = descriptor.getSpec(); + writeListItem("spec:" + getBCDString(spec)); + + byte devClass = descriptor.getDevClass(); + String classStr = UsbStrings.getClassName(devClass); + byte devSubClass = descriptor.getDevSubClass(); + String subClasStr = UsbStrings.getClassName(devSubClass); + writeListItem("class " + devClass + ":" + classStr + " subclass" + + devSubClass + ":" + subClasStr); + writeListItem("vendorID:" + descriptor.getVendorID() + + " prodID:" + descriptor.getProductID() + + " prodRel:" + getBCDString(descriptor.getDeviceRelease())); + + byte mfgIndex = descriptor.getMfgIndex(); + String manufacturer = UsbDescriptor.getUsbDescriptorString(mConnection, mfgIndex); + byte productIndex = descriptor.getProductIndex(); + String product = UsbDescriptor.getUsbDescriptorString(mConnection, productIndex); + + writeListItem("mfg " + mfgIndex + ":" + manufacturer + + " prod " + productIndex + ":" + product); + closeList(); + } + + private void tsReport(UsbConfigDescriptor descriptor) { + writeHeader(2, "Config #" + descriptor.getConfigValue() + + " len:" + descriptor.getLength()); + + openList(); + writeListItem(descriptor.getNumInterfaces() + " interfaces."); + writeListItem("attribs:" + getHexString(descriptor.getAttribs())); + closeList(); + } + + private void tsReport(UsbInterfaceDescriptor descriptor) { + byte usbClass = descriptor.getUsbClass(); + byte usbSubclass = descriptor.getUsbSubclass(); + String descr = UsbStrings.getDescriptorName(descriptor.getType()); + String className = UsbStrings.getClassName(usbClass); + String subclassName = ""; + if (usbClass == UsbDescriptor.CLASSID_AUDIO) { + subclassName = UsbStrings.getAudioSubclassName(usbSubclass); + } + + writeHeader(2, descr + " #" + descriptor.getInterfaceNumber() + + " len:" + descriptor.getLength()); + String descrStr = + UsbDescriptor.getUsbDescriptorString(mConnection, descriptor.getDescrIndex()); + if (descrStr.length() > 0) { + mStringBuilder.append("<br>" + descrStr); + } + openList(); + writeListItem("class " + getHexString(usbClass) + ":" + className + + " subclass " + getHexString(usbSubclass) + ":" + subclassName); + writeListItem("" + descriptor.getNumEndpoints() + " endpoints"); + closeList(); + } + + private void tsReport(UsbEndpointDescriptor descriptor) { + writeHeader(3, "Endpoint " + getHexString(descriptor.getType()) + + " len:" + descriptor.getLength()); + openList(); + + byte address = descriptor.getEndpointAddress(); + writeListItem("address:" + + getHexString(address & UsbEndpointDescriptor.MASK_ENDPOINT_ADDRESS) + + ((address & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION) + == UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]")); + + byte attributes = descriptor.getAttributes(); + openListItem(); + mStringBuilder.append("attribs:" + getHexString(attributes) + " "); + switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_TRANSTYPE) { + case UsbEndpointDescriptor.TRANSTYPE_CONTROL: + mStringBuilder.append("Control"); + break; + case UsbEndpointDescriptor.TRANSTYPE_ISO: + mStringBuilder.append("Iso"); + break; + case UsbEndpointDescriptor.TRANSTYPE_BULK: + mStringBuilder.append("Bulk"); + break; + case UsbEndpointDescriptor.TRANSTYPE_INTERRUPT: + mStringBuilder.append("Interrupt"); + break; + } + closeListItem(); + + // These flags are only relevant for ISO transfer type + if ((attributes & UsbEndpointDescriptor.MASK_ATTRIBS_TRANSTYPE) + == UsbEndpointDescriptor.TRANSTYPE_ISO) { + openListItem(); + mStringBuilder.append("sync:"); + switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_SYNCTYPE) { + case UsbEndpointDescriptor.SYNCTYPE_NONE: + mStringBuilder.append("NONE"); + break; + case UsbEndpointDescriptor.SYNCTYPE_ASYNC: + mStringBuilder.append("ASYNC"); + break; + case UsbEndpointDescriptor.SYNCTYPE_ADAPTSYNC: + mStringBuilder.append("ADAPTIVE ASYNC"); + break; + } + closeListItem(); + + openListItem(); + mStringBuilder.append("useage:"); + switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_USEAGE) { + case UsbEndpointDescriptor.USEAGE_DATA: + mStringBuilder.append("DATA"); + break; + case UsbEndpointDescriptor.USEAGE_FEEDBACK: + mStringBuilder.append("FEEDBACK"); + break; + case UsbEndpointDescriptor.USEAGE_EXPLICIT: + mStringBuilder.append("EXPLICIT FEEDBACK"); + break; + case UsbEndpointDescriptor.USEAGE_RESERVED: + mStringBuilder.append("RESERVED"); + break; + } + closeListItem(); + } + writeListItem("package size:" + descriptor.getPacketSize()); + writeListItem("interval:" + descriptor.getInterval()); + closeList(); + } + + private void tsReport(UsbHIDDescriptor descriptor) { + String descr = UsbStrings.getDescriptorName(descriptor.getType()); + writeHeader(2, descr + " len:" + descriptor.getLength()); + openList(); + writeListItem("spec:" + getBCDString(descriptor.getRelease())); + writeListItem("type:" + getBCDString(descriptor.getDescriptorType())); + writeListItem("descriptor.getNumDescriptors() descriptors len:" + + descriptor.getDescriptorLen()); + closeList(); + } + + private void tsReport(UsbACAudioControlEndpoint descriptor) { + writeHeader(3, "AC Audio Control Endpoint:" + getHexString(descriptor.getType()) + + " length:" + descriptor.getLength()); + } + + private void tsReport(UsbACAudioStreamEndpoint descriptor) { + writeHeader(3, "AC Audio Streaming Endpoint:" + + getHexString(descriptor.getType()) + + " length:" + descriptor.getLength()); + } + + private void tsReport(UsbACHeader descriptor) { + tsReport((UsbACInterface) descriptor); + + openList(); + writeListItem("spec:" + getBCDString(descriptor.getADCRelease())); + int numInterfaces = descriptor.getNumInterfaces(); + writeListItem("" + numInterfaces + " interfaces"); + if (numInterfaces > 0) { + openListItem(); + mStringBuilder.append("["); + byte[] interfaceNums = descriptor.getInterfaceNums(); + if (numInterfaces != 0 && interfaceNums != null) { + for (int index = 0; index < numInterfaces; index++) { + mStringBuilder.append("" + interfaceNums[index]); + if (index < numInterfaces - 1) { + mStringBuilder.append(" "); + } + } + } + mStringBuilder.append("]"); + closeListItem(); + } + writeListItem("controls:" + getHexString(descriptor.getControls())); + closeList(); + } + + private void tsReport(UsbACFeatureUnit descriptor) { + tsReport((UsbACInterface) descriptor); + } + + private void tsReport(UsbACInterface descriptor) { + String subClassName = + descriptor.getSubclass() == UsbDescriptor.AUDIO_AUDIOCONTROL + ? "AC Control" + : "AC Streaming"; + byte subtype = descriptor.getSubtype(); + String subTypeStr = UsbStrings.getACControlInterfaceName(subtype); + writeHeader(4, subClassName + " - " + getHexString(subtype) + + ":" + subTypeStr + " len:" + descriptor.getLength()); + } + + private void tsReport(UsbACTerminal descriptor) { + tsReport((UsbACInterface) descriptor); + } + + private void tsReport(UsbACInputTerminal descriptor) { + tsReport((UsbACTerminal) descriptor); + + openList(); + writeListItem("ID:" + getHexString(descriptor.getTerminalID())); + int terminalType = descriptor.getTerminalType(); + writeListItem("Type:<b>" + getHexString(terminalType) + ":" + + UsbStrings.getTerminalName(terminalType) + "</b>"); + writeListItem("AssocTerminal:" + getHexString(descriptor.getAssocTerminal())); + writeListItem("" + descriptor.getNrChannels() + " chans. config:" + + getHexString(descriptor.getChannelConfig())); + closeList(); + } + + private void tsReport(UsbACOutputTerminal descriptor) { + tsReport((UsbACTerminal) descriptor); + + openList(); + writeListItem("ID:" + getHexString(descriptor.getTerminalID())); + int terminalType = descriptor.getTerminalType(); + writeListItem("Type:<b>" + getHexString(terminalType) + ":" + + UsbStrings.getTerminalName(terminalType) + "</b>"); + writeListItem("AssocTerminal:" + getHexString(descriptor.getAssocTerminal())); + writeListItem("Source:" + getHexString(descriptor.getSourceID())); + closeList(); + } + + private void tsReport(UsbACMidiEndpoint descriptor) { + writeHeader(3, "AC Midi Endpoint:" + getHexString(descriptor.getType()) + + " length:" + descriptor.getLength()); + openList(); + writeListItem("" + descriptor.getNumJacks() + " jacks."); + closeList(); + } + + private void tsReport(UsbACMixerUnit descriptor) { + tsReport((UsbACInterface) descriptor); + openList(); + + writeListItem("Unit ID:" + getHexString(descriptor.getUnitID())); + byte numInputs = descriptor.getNumInputs(); + byte[] inputIDs = descriptor.getInputIDs(); + openListItem(); + mStringBuilder.append("Num Inputs:" + numInputs + " ["); + for (int input = 0; input < numInputs; input++) { + mStringBuilder.append("" + getHexString(inputIDs[input])); + if (input < numInputs - 1) { + mStringBuilder.append(" "); + } + } + mStringBuilder.append("]"); + closeListItem(); + + writeListItem("Num Outputs:" + descriptor.getNumOutputs()); + writeListItem("Chan Config:" + getHexString(descriptor.getChannelConfig())); + + byte[] controls = descriptor.getControls(); + openListItem(); + mStringBuilder.append("controls:" + controls.length + " ["); + for (int ctrl = 0; ctrl < controls.length; ctrl++) { + mStringBuilder.append("" + controls[ctrl]); + if (ctrl < controls.length - 1) { + mStringBuilder.append(" "); + } + } + mStringBuilder.append("]"); + closeListItem(); + closeList(); + // byte mChanNameID; // First channel name string descriptor ID + // byte mNameID; // string descriptor ID of mixer name + } + + private void tsReport(UsbACSelectorUnit descriptor) { + tsReport((UsbACInterface) descriptor); + } + + private void tsReport(UsbASFormat descriptor) { + writeHeader(4, "AC Streaming Format " + + (descriptor.getFormatType() == UsbASFormat.FORMAT_TYPE_I ? "I" : "II") + + " - " + getHexString(descriptor.getSubtype()) + ":" + + " len:" + descriptor.getLength()); + } + + private void tsReport(UsbASFormatI descriptor) { + tsReport((UsbASFormat) descriptor); + openList(); + writeListItem("chans:" + descriptor.getNumChannels()); + writeListItem("subframe size:" + descriptor.getSubframeSize()); + writeListItem("bit resolution:" + descriptor.getBitResolution()); + byte sampleFreqType = descriptor.getSampleFreqType(); + int[] sampleRates = descriptor.getSampleRates(); + writeListItem("sample freq type:" + sampleFreqType); + if (sampleFreqType == 0) { + openList(); + writeListItem("min:" + sampleRates[0]); + writeListItem("max:" + sampleRates[1]); + closeList(); + } else { + openList(); + for (int index = 0; index < sampleFreqType; index++) { + writeListItem("" + sampleRates[index]); + } + closeList(); + } + closeList(); + } + + private void tsReport(UsbASFormatII descriptor) { + tsReport((UsbASFormat) descriptor); + openList(); + writeListItem("max bit rate:" + descriptor.getMaxBitRate()); + writeListItem("samples per frame:" + descriptor.getMaxBitRate()); + byte sampleFreqType = descriptor.getSamFreqType(); + int[] sampleRates = descriptor.getSampleRates(); + writeListItem("sample freq type:" + sampleFreqType); + if (sampleFreqType == 0) { + openList(); + writeListItem("min:" + sampleRates[0]); + writeListItem("max:" + sampleRates[1]); + closeList(); + } else { + openList(); + for (int index = 0; index < sampleFreqType; index++) { + writeListItem("" + sampleRates[index]); + } + closeList(); + } + + closeList(); + } + + private void tsReport(UsbASGeneral descriptor) { + tsReport((UsbACInterface) descriptor); + openList(); + int formatTag = descriptor.getFormatTag(); + writeListItem("fmt:" + UsbStrings.getAudioFormatName(formatTag) + " - " + + getHexString(formatTag)); + closeList(); + } + + private void tsReport(UsbInterfaceAssoc descriptor) { + tsReport((UsbDescriptor) descriptor); + } + + private void tsReport(UsbMSMidiHeader descriptor) { + writeHeader(3, "MS Midi Header:" + getHexString(descriptor.getType()) + + " subType:" + getHexString(descriptor.getSubclass()) + + " length:" + descriptor.getSubclass()); + } + + private void tsReport(UsbMSMidiInputJack descriptor) { + writeHeader(3, "MS Midi Input Jack:" + getHexString(descriptor.getType()) + + " subType:" + getHexString(descriptor.getSubclass()) + + " length:" + descriptor.getSubclass()); + } + + private void tsReport(UsbMSMidiOutputJack descriptor) { + writeHeader(3, "MS Midi Output Jack:" + getHexString(descriptor.getType()) + + " subType:" + getHexString(descriptor.getSubclass()) + + " length:" + descriptor.getSubclass()); + } + + private void tsReport(UsbUnknown descriptor) { + writeParagraph("<i><b>Unknown Descriptor " + getHexString(descriptor.getType()) + + " len:" + descriptor.getLength() + "</b></i>"); + dumpHexArray(descriptor.getRawData(), mStringBuilder); + } +} diff --git a/services/usb/java/com/android/server/usb/descriptors/report/Reporter.java b/services/usb/java/com/android/server/usb/descriptors/report/Reporter.java new file mode 100644 index 000000000000..2944c10796f6 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/report/Reporter.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors.report; + +import com.android.server.usb.descriptors.UsbDescriptor; + +/** + * Declares the Reporter interface to provide HTML reporting for UsbDescriptor (sub)classes. + * + * NOTE: It is the responsibility of the implementor of this interface to correctly + * interpret/decode the SPECIFIC UsbDescriptor subclass (perhaps with 'instanceof') that is + * passed and handle that in the appropriate manner. This appears to be a + * not very object-oriented approach, and that is true. This approach DOES however move the + * complexity and 'plumbing' of reporting into the Reporter implementation and avoids needing + * a (trivial) type-specific call to 'report()' in each UsbDescriptor (sub)class, instead + * having just one in the top-level UsbDescriptor class. It also removes the need to add new + * type-specific 'report()' methods to be added to Reporter interface whenever a + * new UsbDescriptor subclass is defined. This seems like a pretty good trade-off. + * + * See HTMLReporter.java in this package for an example of type decoding. + */ +public interface Reporter { + /** + * Generate report for this UsbDescriptor descriptor + */ + void report(UsbDescriptor descriptor); +} diff --git a/services/usb/java/com/android/server/usb/descriptors/report/Reporting.java b/services/usb/java/com/android/server/usb/descriptors/report/Reporting.java new file mode 100644 index 000000000000..c13111b3e81c --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/report/Reporting.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors.report; + +/** + * Declares the interface for classes that provide reporting functionality. + * (This is the double-indirection aspect of the "Visitor" pattern. + */ +public interface Reporting { + /** + * Declares the report method that UsbDescriptor subclasses call. + */ + void report(Reporter reporter); +} diff --git a/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java new file mode 100644 index 000000000000..0461150abd27 --- /dev/null +++ b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2017 The Android Open 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.usb.descriptors.report; + +import com.android.server.usb.descriptors.UsbACInterface; +import com.android.server.usb.descriptors.UsbDescriptor; +import com.android.server.usb.descriptors.UsbTerminalTypes; + +import java.util.HashMap; + +/** + * @hide + * A class to provide human-readable strings for various USB constants. + */ +public class UsbStrings { + private static final String TAG = "UsbStrings"; + + private static HashMap<Byte, String> sDescriptorNames; + private static HashMap<Byte, String> sACControlInterfaceNames; + private static HashMap<Byte, String> sACStreamingInterfaceNames; + private static HashMap<Byte, String> sClassNames; + private static HashMap<Byte, String> sAudioSubclassNames; + private static HashMap<Integer, String> sAudioEncodingNames; + private static HashMap<Integer, String> sTerminalNames; + + private static void initDescriptorNames() { + sDescriptorNames = new HashMap<Byte, String>(); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_DEVICE, "Device"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_CONFIG, "Config"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_STRING, "String"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_INTERFACE, "Interface"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_ENDPOINT, "Endpoint"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_BOS, "BOS (whatever that means)"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_INTERFACEASSOC, + "Interface Association"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_CAPABILITY, "Capability"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_HID, "HID"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_REPORT, "Report"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_PHYSICAL, "Physical"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE, + "Audio Class Interface"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT, "Audio Class Endpoint"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_HUB, "Hub"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_SUPERSPEED_HUB, "Superspeed Hub"); + sDescriptorNames.put(UsbDescriptor.DESCRIPTORTYPE_ENDPOINT_COMPANION, + "Endpoint Companion"); + } + + private static void initACControlInterfaceNames() { + sACControlInterfaceNames = new HashMap<Byte, String>(); + sACControlInterfaceNames.put(UsbACInterface.ACI_UNDEFINED, "Undefined"); + sACControlInterfaceNames.put(UsbACInterface.ACI_HEADER, "Header"); + sACControlInterfaceNames.put(UsbACInterface.ACI_INPUT_TERMINAL, "Input Terminal"); + sACControlInterfaceNames.put(UsbACInterface.ACI_OUTPUT_TERMINAL, "Output Terminal"); + sACControlInterfaceNames.put(UsbACInterface.ACI_MIXER_UNIT, "Mixer Unit"); + sACControlInterfaceNames.put(UsbACInterface.ACI_SELECTOR_UNIT, "Selector Unit"); + sACControlInterfaceNames.put(UsbACInterface.ACI_FEATURE_UNIT, "Feature Unit"); + sACControlInterfaceNames.put(UsbACInterface.ACI_PROCESSING_UNIT, "Processing Unit"); + sACControlInterfaceNames.put(UsbACInterface.ACI_EXTENSION_UNIT, "Extension Unit"); + } + + private static void initACStreamingInterfaceNames() { + sACStreamingInterfaceNames = new HashMap<Byte, String>(); + sACStreamingInterfaceNames.put(UsbACInterface.ASI_UNDEFINED, "Undefined"); + sACStreamingInterfaceNames.put(UsbACInterface.ASI_GENERAL, "General"); + sACStreamingInterfaceNames.put(UsbACInterface.ASI_FORMAT_TYPE, "Format Type"); + sACStreamingInterfaceNames.put(UsbACInterface.ASI_FORMAT_SPECIFIC, "Format Specific"); + } + + private static void initClassNames() { + sClassNames = new HashMap<Byte, String>(); + sClassNames.put(UsbDescriptor.CLASSID_DEVICE, "Device"); + sClassNames.put(UsbDescriptor.CLASSID_AUDIO, "Audio"); + sClassNames.put(UsbDescriptor.CLASSID_COM, "Communications"); + sClassNames.put(UsbDescriptor.CLASSID_HID, "HID"); + sClassNames.put(UsbDescriptor.CLASSID_PHYSICAL, "Physical"); + sClassNames.put(UsbDescriptor.CLASSID_IMAGE, "Image"); + sClassNames.put(UsbDescriptor.CLASSID_PRINTER, "Printer"); + sClassNames.put(UsbDescriptor.CLASSID_STORAGE, "Storage"); + sClassNames.put(UsbDescriptor.CLASSID_HUB, "Hub"); + sClassNames.put(UsbDescriptor.CLASSID_CDC_CONTROL, "CDC Control"); + sClassNames.put(UsbDescriptor.CLASSID_SMART_CARD, "Smart Card"); + sClassNames.put(UsbDescriptor.CLASSID_SECURITY, "Security"); + sClassNames.put(UsbDescriptor.CLASSID_VIDEO, "Video"); + sClassNames.put(UsbDescriptor.CLASSID_HEALTHCARE, "Healthcare"); + sClassNames.put(UsbDescriptor.CLASSID_AUDIOVIDEO, "Audio/Video"); + sClassNames.put(UsbDescriptor.CLASSID_BILLBOARD, "Billboard"); + sClassNames.put(UsbDescriptor.CLASSID_TYPECBRIDGE, "Type C Bridge"); + sClassNames.put(UsbDescriptor.CLASSID_DIAGNOSTIC, "Diagnostic"); + sClassNames.put(UsbDescriptor.CLASSID_WIRELESS, "Wireless"); + sClassNames.put(UsbDescriptor.CLASSID_MISC, "Misc"); + sClassNames.put(UsbDescriptor.CLASSID_APPSPECIFIC, "Application Specific"); + sClassNames.put(UsbDescriptor.CLASSID_VENDSPECIFIC, "Vendor Specific"); + } + + private static void initAudioSubclassNames() { + sAudioSubclassNames = new HashMap<Byte, String>(); + sAudioSubclassNames.put(UsbDescriptor.AUDIO_SUBCLASS_UNDEFINED, "Undefinded"); + sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOCONTROL, "Audio Control"); + sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOSTREAMING, "Audio Streaming"); + sAudioSubclassNames.put(UsbDescriptor.AUDIO_MIDISTREAMING, "MIDI Streaming"); + } + + private static void initAudioEncodingNames() { + sAudioEncodingNames = new HashMap<Integer, String>(); + sAudioEncodingNames.put(UsbACInterface.FORMAT_I_UNDEFINED, "Format I Undefined"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_I_PCM, "Format I PCM"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_I_PCM8, "Format I PCM8"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_I_IEEE_FLOAT, "Format I FLOAT"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_I_ALAW, "Format I ALAW"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_I_MULAW, "Format I MuLAW"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_II_UNDEFINED, "FORMAT_II Undefined"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_II_MPEG, "FORMAT_II MPEG"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_II_AC3, "FORMAT_II AC3"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_III_UNDEFINED, "FORMAT_III Undefined"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_III_IEC1937AC3, "FORMAT_III IEC1937 AC3"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_III_IEC1937_MPEG1_Layer1, + "FORMAT_III MPEG1 Layer 1"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_III_IEC1937_MPEG1_Layer2, + "FORMAT_III MPEG1 Layer 2"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_III_IEC1937_MPEG2_EXT, + "FORMAT_III MPEG2 EXT"); + sAudioEncodingNames.put(UsbACInterface.FORMAT_III_IEC1937_MPEG2_Layer1LS, + "FORMAT_III MPEG2 Layer1LS"); + } + + private static void initTerminalNames() { + sTerminalNames = new HashMap<Integer, String>(); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_USB_STREAMING, "USB Streaming"); + + sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_UNDEFINED, "Undefined"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_MIC, "Microphone"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_DESKTOP_MIC, "Desktop Microphone"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_PERSONAL_MIC, + "Personal (headset) Microphone"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_OMNI_MIC, "Omni Microphone"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_MIC_ARRAY, "Microphone Array"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_IN_PROC_MIC_ARRAY, + "Proecessing Microphone Array"); + + sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_UNDEFINED, "Undefined"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_SPEAKER, "Speaker"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_HEADPHONES, "Headphones"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_HEADMOUNTED, "Head Mounted Speaker"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_DESKTOPSPEAKER, "Desktop Speaker"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_ROOMSPEAKER, "Room Speaker"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_COMSPEAKER, "Communications Speaker"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_OUT_LFSPEAKER, "Low Frequency Speaker"); + + sTerminalNames.put(UsbTerminalTypes.TERMINAL_BIDIR_UNDEFINED, "Undefined"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_BIDIR_HANDSET, "Handset"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_BIDIR_HEADSET, "Headset"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_BIDIR_SKRPHONE, "Speaker Phone"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_BIDIR_SKRPHONE_SUPRESS, + "Speaker Phone (echo supressing)"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_BIDIR_SKRPHONE_CANCEL, + "Speaker Phone (echo canceling)"); + + sTerminalNames.put(UsbTerminalTypes.TERMINAL_TELE_UNDEFINED, "Undefined"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_TELE_PHONELINE, "Phone Line"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_TELE_PHONE, "Telephone"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_TELE_DOWNLINEPHONE, "Down Line Phone"); + + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_UNDEFINED, "Undefined"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_ANALOG, "Analog Connector"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_DIGITAL, "Digital Connector"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_LINE, "Line Connector"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_LEGACY, "Legacy Audio Connector"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_SPIDF, "S/PIDF Interface"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_1394DA, "1394 Audio"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EXTERN_1394DV, "1394 Audio/Video"); + + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_UNDEFINED, "Undefined"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_CALNOISE, "Calibration Nose"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_EQNOISE, "EQ Noise"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_CDPLAYER, "CD Player"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_DAT, "DAT"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_DCC, "DCC"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_MINIDISK, "Mini Disk"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_ANALOGTAPE, "Analog Tap"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_PHONOGRAPH, "Phonograph"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_VCRAUDIO, "VCR Audio"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_VIDDISKAUDIO, "Video Disk Audio"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_DVDAUDIO, "DVD Audio"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_TVAUDIO, "TV Audio"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_SATELLITEAUDIO, "Satellite Audio"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_CABLEAUDIO, "Cable Tuner Audio"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_DSSAUDIO, "DSS Audio"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_RADIOTRANSMITTER, "Radio Transmitter"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_MULTITRACK, "Multitrack Recorder"); + sTerminalNames.put(UsbTerminalTypes.TERMINAL_EMBED_SYNTHESIZER, "Synthesizer"); + } + + /** + * Retrieves the terminal name for the specified terminal type ID. + */ + public static String getTerminalName(int terminalType) { + String name = sTerminalNames.get(terminalType); + return name != null + ? name + : "Unknown Terminal Type 0x" + Integer.toHexString(terminalType); + } + /** + * Initializes string tables. + */ + public static void allocUsbStrings() { + initDescriptorNames(); + initACControlInterfaceNames(); + initACStreamingInterfaceNames(); + initClassNames(); + initAudioSubclassNames(); + initAudioEncodingNames(); + initTerminalNames(); + } + + /** + * Initializes string tables. + */ + public static void releaseUsbStrings() { + sDescriptorNames = null; + sACControlInterfaceNames = null; + sACStreamingInterfaceNames = null; + sClassNames = null; + sAudioSubclassNames = null; + sAudioEncodingNames = null; + sTerminalNames = null; + } + + /** + * Retrieves the name for the specified descriptor ID. + */ + public static String getDescriptorName(byte descriptorID) { + String name = sDescriptorNames.get(descriptorID); + int iDescriptorID = descriptorID & 0xFF; + return name != null + ? name + : "Unknown Descriptor [0x" + Integer.toHexString(iDescriptorID) + + ":" + iDescriptorID + "]"; + } + + /** + * Retrieves the audio-class control interface name for the specified audio-class subtype. + */ + public static String getACControlInterfaceName(byte subtype) { + String name = sACControlInterfaceNames.get(subtype); + int iSubType = subtype & 0xFF; + return name != null + ? name + : "Unknown subtype [0x" + Integer.toHexString(iSubType) + + ":" + iSubType + "]"; + } + + /** + * Retrieves the audio-class streaming interface name for the specified audio-class subtype. + */ + public static String getACStreamingInterfaceName(byte subtype) { + String name = sACStreamingInterfaceNames.get(subtype); + int iSubType = subtype & 0xFF; + return name != null + ? name + : "Unknown Subtype [0x" + Integer.toHexString(iSubType) + ":" + + iSubType + "]"; + } + + /** + * Retrieves the name for the specified USB class ID. + */ + public static String getClassName(byte classID) { + String name = sClassNames.get(classID); + int iClassID = classID & 0xFF; + return name != null + ? name + : "Unknown Class ID [0x" + Integer.toHexString(iClassID) + ":" + + iClassID + "]"; + } + + /** + * Retrieves the name for the specified USB audio subclass ID. + */ + public static String getAudioSubclassName(byte subClassID) { + String name = sAudioSubclassNames.get(subClassID); + int iSubclassID = subClassID & 0xFF; + return name != null + ? name + : "Unknown Audio Subclass [0x" + Integer.toHexString(iSubclassID) + ":" + + iSubclassID + "]"; + } + + /** + * Retrieves the name for the specified USB audio format ID. + */ + public static String getAudioFormatName(int formatID) { + String name = sAudioEncodingNames.get(formatID); + return name != null + ? name + : "Unknown Format (encoding) ID [0x" + Integer.toHexString(formatID) + ":" + + formatID + "]"; + } +} diff --git a/telephony/java/android/telephony/MbmsDownloadManager.java b/telephony/java/android/telephony/MbmsDownloadManager.java index 14ba7e5d274a..b3f46c22ba0a 100644 --- a/telephony/java/android/telephony/MbmsDownloadManager.java +++ b/telephony/java/android/telephony/MbmsDownloadManager.java @@ -194,27 +194,23 @@ public class MbmsDownloadManager { private AtomicReference<IMbmsDownloadService> mService = new AtomicReference<>(null); private final IMbmsDownloadManagerCallback mCallback; - private final String mDownloadAppName; - private MbmsDownloadManager(Context context, IMbmsDownloadManagerCallback callback, - String downloadAppName, int subId) { + private MbmsDownloadManager(Context context, IMbmsDownloadManagerCallback callback, int subId) { mContext = context; mCallback = callback; - mDownloadAppName = downloadAppName; mSubscriptionId = subId; } /** * Create a new MbmsDownloadManager using the system default data subscription ID. - * See {@link #create(Context, IMbmsDownloadManagerCallback, String, int)} + * See {@link #create(Context, IMbmsDownloadManagerCallback, int)} * * @hide */ public static MbmsDownloadManager create(Context context, - IMbmsDownloadManagerCallback listener, String downloadAppName) + IMbmsDownloadManagerCallback listener) throws MbmsException { - return create(context, listener, downloadAppName, - SubscriptionManager.getDefaultSubscriptionId()); + return create(context, listener, SubscriptionManager.getDefaultSubscriptionId()); } /** @@ -229,15 +225,13 @@ public class MbmsDownloadManager { * * @param context The instance of {@link Context} to use * @param listener A callback to get asynchronous error messages and file service updates. - * @param downloadAppName The app name, as negotiated with the eMBMS provider * @param subscriptionId The data subscription ID to use * @hide */ public static MbmsDownloadManager create(Context context, - IMbmsDownloadManagerCallback listener, String downloadAppName, int subscriptionId) + IMbmsDownloadManagerCallback listener, int subscriptionId) throws MbmsException { - MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, downloadAppName, - subscriptionId); + MbmsDownloadManager mdm = new MbmsDownloadManager(context, listener, subscriptionId); mdm.bindAndInitialize(); return mdm; } @@ -250,8 +244,7 @@ public class MbmsDownloadManager { IMbmsDownloadService downloadService = IMbmsDownloadService.Stub.asInterface(service); try { - downloadService.initialize( - mDownloadAppName, mSubscriptionId, mCallback); + downloadService.initialize(mSubscriptionId, mCallback); } catch (RemoteException e) { Log.e(LOG_TAG, "Service died before initialization"); return; @@ -293,8 +286,7 @@ public class MbmsDownloadManager { throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); } try { - int returnCode = downloadService.getFileServices( - mDownloadAppName, mSubscriptionId, classList); + int returnCode = downloadService.getFileServices(mSubscriptionId, classList); if (returnCode != MbmsException.SUCCESS) { throw new MbmsException(returnCode); } @@ -345,8 +337,7 @@ public class MbmsDownloadManager { } try { - int result = downloadService.setTempFileRootDirectory( - mDownloadAppName, mSubscriptionId, filePath); + int result = downloadService.setTempFileRootDirectory(mSubscriptionId, filePath); if (result != MbmsException.SUCCESS) { throw new MbmsException(result); } @@ -396,7 +387,6 @@ public class MbmsDownloadManager { tempRootDirectory.mkdirs(); setTempFileRootDirectory(tempRootDirectory); } - request.setAppName(mDownloadAppName); checkValidDownloadDestination(request); writeDownloadRequestToken(request); @@ -404,6 +394,7 @@ public class MbmsDownloadManager { downloadService.download(request, callback); } catch (RemoteException e) { mService.set(null); + throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); } } @@ -422,18 +413,31 @@ public class MbmsDownloadManager { } /** - * Attempts to cancel the specified DownloadRequest. + * Attempts to cancel the specified {@link DownloadRequest}. * - * May throw a RemoteException. + * If the middleware is not aware of the specified download request, an MbmsException will be + * thrown with error code {@link MbmsException#ERROR_UNKNOWN_DOWNLOAD_REQUEST}. * - * Synchronous responses may include - * <li>SUCCESS</li> - * <li>ERROR_MSDC_CONCURRENT_SERVICE_LIMIT_REACHED</li> - * <li>ERROR_MSDC_UNKNOWN_REQUEST</li> + * If this method returns without throwing an exception, you may assume that cancellation + * was successful. + * @param downloadRequest The download request that you wish to cancel. */ - public int cancelDownload(DownloadRequest downloadRequest) { - // TODO: don't forget to delete the token - return 0; + public void cancelDownload(DownloadRequest downloadRequest) throws MbmsException { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); + } + + try { + int result = downloadService.cancelDownload(downloadRequest); + if (result != MbmsException.SUCCESS) { + throw new MbmsException(result); + } + } catch (RemoteException e) { + mService.set(null); + throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); + } + deleteDownloadRequestToken(downloadRequest); } /** @@ -472,6 +476,21 @@ public class MbmsDownloadManager { return 0; } + public void dispose() { + try { + IMbmsDownloadService downloadService = mService.get(); + if (downloadService == null) { + Log.i(LOG_TAG, "Service already dead"); + return; + } + downloadService.dispose(mSubscriptionId); + mService.set(null); + } catch (RemoteException e) { + // Ignore + Log.i(LOG_TAG, "Remote exception while disposing of service"); + } + } + /** * Retrieves the {@link ComponentName} for the {@link android.content.BroadcastReceiver} that * the various intents from the middleware should be targeted towards. @@ -502,32 +521,13 @@ public class MbmsDownloadManager { return null; } - public void dispose() { - try { - IMbmsDownloadService downloadService = mService.get(); - if (downloadService == null) { - Log.i(LOG_TAG, "Service already dead"); - return; - } - downloadService.dispose(mDownloadAppName, mSubscriptionId); - mService.set(null); - } catch (RemoteException e) { - // Ignore - Log.i(LOG_TAG, "Remote exception while disposing of service"); - } - } - private void writeDownloadRequestToken(DownloadRequest request) { - File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext, - request.getFileServiceInfo()); - if (!tempFileLocation.exists()) { - tempFileLocation.mkdirs(); + File token = getDownloadRequestTokenPath(request); + if (!token.getParentFile().exists()) { + token.getParentFile().mkdirs(); } - String downloadTokenFileName = request.getHash() - + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX; - File token = new File(tempFileLocation, downloadTokenFileName); if (token.exists()) { - Log.w(LOG_TAG, "Download token " + downloadTokenFileName + " already exists"); + Log.w(LOG_TAG, "Download token " + token.getName() + " already exists"); return; } try { @@ -541,6 +541,25 @@ public class MbmsDownloadManager { } } + private void deleteDownloadRequestToken(DownloadRequest request) { + File token = getDownloadRequestTokenPath(request); + if (!token.isFile()) { + Log.w(LOG_TAG, "Attempting to delete non-existent download token at " + token); + return; + } + if (!token.delete()) { + Log.w(LOG_TAG, "Couldn't delete download token at " + token); + } + } + + private File getDownloadRequestTokenPath(DownloadRequest request) { + File tempFileLocation = MbmsUtils.getEmbmsTempFileDirForService(mContext, + request.getFileServiceInfo()); + String downloadTokenFileName = request.getHash() + + MbmsDownloadReceiver.DOWNLOAD_TOKEN_SUFFIX; + return new File(tempFileLocation, downloadTokenFileName); + } + /** * Verifies the following: * If a request is multi-part, diff --git a/telephony/java/android/telephony/MbmsStreamingManager.java b/telephony/java/android/telephony/MbmsStreamingManager.java index af7f333390d8..fb3d5025dafa 100644 --- a/telephony/java/android/telephony/MbmsStreamingManager.java +++ b/telephony/java/android/telephony/MbmsStreamingManager.java @@ -43,16 +43,14 @@ public class MbmsStreamingManager { private AtomicReference<IMbmsStreamingService> mService = new AtomicReference<>(null); private MbmsStreamingManagerCallback mCallbackToApp; - private final String mAppName; private final Context mContext; private int mSubscriptionId = INVALID_SUBSCRIPTION_ID; /** @hide */ private MbmsStreamingManager(Context context, MbmsStreamingManagerCallback listener, - String streamingAppName, int subscriptionId) { + int subscriptionId) { mContext = context; - mAppName = streamingAppName; mCallbackToApp = listener; mSubscriptionId = subscriptionId; } @@ -67,27 +65,24 @@ public class MbmsStreamingManager { * @param context The {@link Context} to use. * @param listener A callback object on which you wish to receive results of asynchronous * operations. - * @param streamingAppName The name of the streaming app, as specified by the carrier. * @param subscriptionId The subscription ID to use. */ public static MbmsStreamingManager create(Context context, - MbmsStreamingManagerCallback listener, String streamingAppName, int subscriptionId) + MbmsStreamingManagerCallback listener, int subscriptionId) throws MbmsException { - MbmsStreamingManager manager = new MbmsStreamingManager(context, listener, - streamingAppName, subscriptionId); + MbmsStreamingManager manager = new MbmsStreamingManager(context, listener, subscriptionId); manager.bindAndInitialize(); return manager; } /** * Create a new MbmsStreamingManager using the system default data subscription ID. - * See {@link #create(Context, MbmsStreamingManagerCallback, String, int)}. + * See {@link #create(Context, MbmsStreamingManagerCallback, int)}. */ public static MbmsStreamingManager create(Context context, - MbmsStreamingManagerCallback listener, String streamingAppName) + MbmsStreamingManagerCallback listener) throws MbmsException { - return create(context, listener, streamingAppName, - SubscriptionManager.getDefaultSubscriptionId()); + return create(context, listener, SubscriptionManager.getDefaultSubscriptionId()); } /** @@ -101,7 +96,7 @@ public class MbmsStreamingManager { return; } try { - streamingService.dispose(mAppName, mSubscriptionId); + streamingService.dispose(mSubscriptionId); } catch (RemoteException e) { // Ignore for now } @@ -132,8 +127,7 @@ public class MbmsStreamingManager { throw new MbmsException(MbmsException.ERROR_MIDDLEWARE_NOT_BOUND); } try { - int returnCode = streamingService.getStreamingServices( - mAppName, mSubscriptionId, classList); + int returnCode = streamingService.getStreamingServices(mSubscriptionId, classList); if (returnCode != MbmsException.SUCCESS) { throw new MbmsException(returnCode); } @@ -168,7 +162,7 @@ public class MbmsStreamingManager { try { int returnCode = streamingService.startStreaming( - mAppName, mSubscriptionId, serviceInfo.getServiceId(), listener); + mSubscriptionId, serviceInfo.getServiceId(), listener); if (returnCode != MbmsException.SUCCESS) { throw new MbmsException(returnCode); } @@ -178,8 +172,7 @@ public class MbmsStreamingManager { throw new MbmsException(MbmsException.ERROR_SERVICE_LOST); } - return new StreamingService( - mAppName, mSubscriptionId, streamingService, serviceInfo, listener); + return new StreamingService(mSubscriptionId, streamingService, serviceInfo, listener); } private void bindAndInitialize() throws MbmsException { @@ -190,12 +183,12 @@ public class MbmsStreamingManager { IMbmsStreamingService streamingService = IMbmsStreamingService.Stub.asInterface(service); try { - streamingService.initialize(mCallbackToApp, mAppName, mSubscriptionId); + streamingService.initialize(mCallbackToApp, mSubscriptionId); } catch (RemoteException e) { Log.e(LOG_TAG, "Service died before initialization"); return; } - mService.set(null); + mService.set(streamingService); } @Override diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.java b/telephony/java/android/telephony/mbms/DownloadRequest.java index 907b0cbd8004..345729d82dea 100644 --- a/telephony/java/android/telephony/mbms/DownloadRequest.java +++ b/telephony/java/android/telephony/mbms/DownloadRequest.java @@ -22,11 +22,11 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Base64; -import java.lang.IllegalStateException; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Objects; /** * A Parcelable class describing a pending Cell-Broadcast download request @@ -83,7 +83,7 @@ public class DownloadRequest implements Parcelable { public DownloadRequest build() { return new DownloadRequest(id, serviceInfo, source, dest, - subscriptionId, appIntent, null, version); + subscriptionId, appIntent, version); } } @@ -94,18 +94,16 @@ public class DownloadRequest implements Parcelable { private final int subscriptionId; private final String serializedResultIntentForApp; private final int version; - private String appName; // not the Android app Name, the embms app name private DownloadRequest(int id, FileServiceInfo serviceInfo, Uri source, Uri dest, - int sub, String appIntent, String name, int version) { + int sub, String appIntent, int version) { downloadId = id; fileServiceInfo = serviceInfo; sourceUri = source; destinationUri = dest; subscriptionId = sub; serializedResultIntentForApp = appIntent; - appName = name; this.version = version; } @@ -120,7 +118,6 @@ public class DownloadRequest implements Parcelable { destinationUri = dr.destinationUri; subscriptionId = dr.subscriptionId; serializedResultIntentForApp = dr.serializedResultIntentForApp; - appName = dr.appName; version = dr.version; } @@ -131,7 +128,6 @@ public class DownloadRequest implements Parcelable { destinationUri = in.readParcelable(getClass().getClassLoader()); subscriptionId = in.readInt(); serializedResultIntentForApp = in.readString(); - appName = in.readString(); version = in.readInt(); } @@ -146,7 +142,6 @@ public class DownloadRequest implements Parcelable { out.writeParcelable(destinationUri, flags); out.writeInt(subscriptionId); out.writeString(serializedResultIntentForApp); - out.writeString(appName); out.writeInt(version); } @@ -178,18 +173,6 @@ public class DownloadRequest implements Parcelable { } } - /** @hide */ - public synchronized void setAppName(String newAppName) { - if (appName != null) { - throw new IllegalStateException("Attempting to reset appName"); - } - appName = newAppName; - } - - public String getAppName() { - return appName; - } - public int getVersion() { return version; } @@ -234,4 +217,29 @@ public class DownloadRequest implements Parcelable { // Add updates for future versions here return Base64.encodeToString(digest.digest(), Base64.URL_SAFE | Base64.NO_WRAP); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) { + return false; + } + if (!(o instanceof DownloadRequest)) { + return false; + } + DownloadRequest request = (DownloadRequest) o; + return downloadId == request.downloadId && + subscriptionId == request.subscriptionId && + version == request.version && + Objects.equals(fileServiceInfo, request.fileServiceInfo) && + Objects.equals(sourceUri, request.sourceUri) && + Objects.equals(destinationUri, request.destinationUri) && + Objects.equals(serializedResultIntentForApp, request.serializedResultIntentForApp); + } + + @Override + public int hashCode() { + return Objects.hash(downloadId, fileServiceInfo, sourceUri, destinationUri, + subscriptionId, serializedResultIntentForApp, version); + } } diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java index 1ce82d94b7e5..5bc53a82a3bb 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java @@ -29,16 +29,16 @@ import android.util.Log; import java.io.File; import java.io.FileFilter; -import java.io.FilenameFilter; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.UUID; -import java.util.function.Predicate; /** * @hide @@ -431,17 +431,16 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } toFile.getParentFile().mkdirs(); - // TODO: This will not work if the two files are on different filesystems. Add manual - // copy later. if (fromFile.renameTo(toFile)) { return Uri.fromFile(toFile); + } else if (manualMove(fromFile, toFile)) { + return Uri.fromFile(toFile); } return null; } private static boolean verifyTempFilePath(Context context, FileServiceInfo serviceInfo, Uri filePath) { - // TODO: modify pursuant to final decision on temp file path scheme if (!ContentResolver.SCHEME_FILE.equals(filePath.getScheme())) { Log.w(LOG_TAG, "Uri " + filePath + " does not have a file scheme"); return false; @@ -493,4 +492,40 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { } return mMiddlewarePackageNameCache; } + + private static boolean manualMove(File src, File dst) { + InputStream in = null; + OutputStream out = null; + try { + if (!dst.exists()) { + dst.createNewFile(); + } + in = new FileInputStream(src); + out = new FileOutputStream(dst); + byte[] buffer = new byte[2048]; + int len; + do { + len = in.read(buffer); + out.write(buffer, 0, len); + } while (len > 0); + } catch (IOException e) { + Log.w(LOG_TAG, "Manual file move failed due to exception " + e); + if (dst.exists()) { + dst.delete(); + } + return false; + } finally { + try { + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + } catch (IOException e) { + Log.w(LOG_TAG, "Error closing streams: " + e); + } + } + return true; + } } diff --git a/telephony/java/android/telephony/mbms/MbmsException.java b/telephony/java/android/telephony/mbms/MbmsException.java index 8260b728a52f..e190623f5529 100644 --- a/telephony/java/android/telephony/mbms/MbmsException.java +++ b/telephony/java/android/telephony/mbms/MbmsException.java @@ -37,6 +37,8 @@ public class MbmsException extends Exception { public static final int ERROR_UNABLE_TO_READ_SIM = 16; public static final int ERROR_CARRIER_CHANGE_NOT_ALLOWED = 17; public static final int ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT = 18; + public static final int ERROR_UNKNOWN_DOWNLOAD_REQUEST = 19; + public static final int ERROR_UNABLE_TO_INITIALIZE = 20; private final int mErrorCode; diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.java b/telephony/java/android/telephony/mbms/ServiceInfo.java index b5675b2576dc..f9ad44c63118 100644 --- a/telephony/java/android/telephony/mbms/ServiceInfo.java +++ b/telephony/java/android/telephony/mbms/ServiceInfo.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -175,4 +176,26 @@ public class ServiceInfo implements Parcelable { return sessionEndTime; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) { + return false; + } + if (!(o instanceof ServiceInfo)) { + return false; + } + ServiceInfo that = (ServiceInfo) o; + return Objects.equals(names, that.names) && + Objects.equals(className, that.className) && + Objects.equals(locales, that.locales) && + Objects.equals(serviceId, that.serviceId) && + Objects.equals(sessionStartTime, that.sessionStartTime) && + Objects.equals(sessionEndTime, that.sessionEndTime); + } + + @Override + public int hashCode() { + return Objects.hash(names, className, locales, serviceId, sessionStartTime, sessionEndTime); + } } diff --git a/telephony/java/android/telephony/mbms/StreamingService.java b/telephony/java/android/telephony/mbms/StreamingService.java index 608d3a921dc7..85731a14277f 100644 --- a/telephony/java/android/telephony/mbms/StreamingService.java +++ b/telephony/java/android/telephony/mbms/StreamingService.java @@ -42,7 +42,6 @@ public class StreamingService { public final static int BROADCAST_METHOD = 1; public final static int UNICAST_METHOD = 2; - private final String mAppName; private final int mSubscriptionId; private final StreamingServiceInfo mServiceInfo; private final IStreamingServiceCallback mCallback; @@ -51,12 +50,10 @@ public class StreamingService { /** * @hide */ - public StreamingService(String appName, - int subscriptionId, + public StreamingService(int subscriptionId, IMbmsStreamingService service, StreamingServiceInfo streamingServiceInfo, IStreamingServiceCallback callback) { - mAppName = appName; mSubscriptionId = subscriptionId; mService = service; mServiceInfo = streamingServiceInfo; @@ -77,7 +74,7 @@ public class StreamingService { } try { - return mService.getPlaybackUri(mAppName, mSubscriptionId, mServiceInfo.getServiceId()); + return mService.getPlaybackUri(mSubscriptionId, mServiceInfo.getServiceId()); } catch (RemoteException e) { Log.w(LOG_TAG, "Remote process died"); mService = null; @@ -103,7 +100,7 @@ public class StreamingService { } try { - mService.stopStreaming(mAppName, mSubscriptionId, mServiceInfo.getServiceId()); + mService.stopStreaming(mSubscriptionId, mServiceInfo.getServiceId()); } catch (RemoteException e) { Log.w(LOG_TAG, "Remote process died"); mService = null; @@ -117,7 +114,7 @@ public class StreamingService { } try { - mService.disposeStream(mAppName, mSubscriptionId, mServiceInfo.getServiceId()); + mService.disposeStream(mSubscriptionId, mServiceInfo.getServiceId()); } catch (RemoteException e) { Log.w(LOG_TAG, "Remote process died"); mService = null; diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl index ff7d233bbf2c..7112e13d5650 100755 --- a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl +++ b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl @@ -29,31 +29,15 @@ import android.telephony.mbms.IDownloadCallback; */ interface IMbmsDownloadService { - /** - * Initialize download service - * Registers this listener, subId with this appName - * - * No return value. Async errors may be reported, but none expected (not doing anything yet). - */ - void initialize(String appName, int subId, IMbmsDownloadManagerCallback listener); + void initialize(int subId, IMbmsDownloadManagerCallback listener); - /** - * - Registers serviceClasses of interest with the uid/appName/subId key. - * - Starts asynch fetching data on download services of matching classes to be reported - * later by callback. - * - * Note that subsequent calls with the same callback, appName, subId and uid will replace - * the service class list. - */ - int getFileServices(String appName, int subId, in List<String> serviceClasses); + int getFileServices(int subId, in List<String> serviceClasses); + + int setTempFileRootDirectory(int subId, String rootDirectoryPath); - int setTempFileRootDirectory(String appName, int subId, String rootDirectoryPath); - /** - * should move the params into a DownloadRequest parcelable - */ int download(in DownloadRequest downloadRequest, IDownloadCallback listener); - List<DownloadRequest> listPendingDownloads(String appName, int subscriptionId); + List<DownloadRequest> listPendingDownloads(int subscriptionId); int cancelDownload(in DownloadRequest downloadRequest); @@ -66,9 +50,5 @@ interface IMbmsDownloadService */ void resetDownloadKnowledge(in DownloadRequest downloadRequest); - /** - * End of life for this MbmsDownloadManager. - * Any pending downloads remain in affect and may start up independently in the future. - */ - void dispose(String appName, int subId); + void dispose(int subId); } diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl index 8ff7fa7c54f3..1370b83857ec 100755 --- a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl +++ b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl @@ -27,28 +27,18 @@ import android.telephony.mbms.StreamingServiceInfo; */ interface IMbmsStreamingService { - int initialize(IMbmsStreamingManagerCallback listener, String appName, int subId); + int initialize(IMbmsStreamingManagerCallback listener, int subId); - int getStreamingServices(String appName, int subId, in List<String> serviceClasses); + int getStreamingServices(int subId, in List<String> serviceClasses); - int startStreaming(String appName, int subId, String serviceId, + int startStreaming(int subId, String serviceId, IStreamingServiceCallback listener); - /** - * Per-stream api. Note each specifies what stream they apply to. - */ + Uri getPlaybackUri(int subId, String serviceId); - Uri getPlaybackUri(String appName, int subId, String serviceId); + void stopStreaming(int subId, String serviceId); - void stopStreaming(String appName, int subId, String serviceId); + void disposeStream(int subId, String serviceId); - void disposeStream(String appName, int subId, String serviceId); - - /** - * End of life for all MbmsStreamingManager's created by this uid/appName/subId. - * Ends any streams run under this uid/appname/subId and calls the disposed methods - * an callbacks registered for this uid/appName/subId and the disposed methods on any - * listeners registered with startStreaming. - */ - void dispose(String appName, int subId); + void dispose(int subId); } diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java index 9577dd2e3d78..58bda6480999 100644 --- a/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java +++ b/telephony/java/android/telephony/mbms/vendor/MbmsDownloadServiceBase.java @@ -21,6 +21,7 @@ import android.telephony.mbms.DownloadRequest; import android.telephony.mbms.DownloadStatus; import android.telephony.mbms.IDownloadCallback; import android.telephony.mbms.IMbmsDownloadManagerCallback; +import android.telephony.mbms.MbmsException; import java.util.List; @@ -31,23 +32,66 @@ import java.util.List; * TODO: future systemapi */ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { + /** + * Initialize the download service for this app and subId, registering the listener. + * + * May throw an {@link IllegalArgumentException} or a {@link SecurityException} + * + * @param listener The callback to use to communicate with the app. + * @param subscriptionId The subscription ID to use. + */ @Override - public void initialize(String appName, int subscriptionId, + public void initialize(int subscriptionId, IMbmsDownloadManagerCallback listener) throws RemoteException { } + /** + * Registers serviceClasses of interest with the appName/subId key. + * Starts async fetching data on streaming services of matching classes to be reported + * later via {@link IMbmsDownloadManagerCallback#fileServicesUpdated(List)} + * + * Note that subsequent calls with the same uid and subId will replace + * the service class list. + * + * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + * @param serviceClasses The service classes that the app wishes to get info on. The strings + * may contain arbitrary data as negotiated between the app and the + * carrier. + * @return One of {@link MbmsException#SUCCESS}, + * {@link MbmsException#ERROR_MIDDLEWARE_NOT_YET_READY}, + * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED} + */ @Override - public int getFileServices(String appName, int subscriptionId, List<String> serviceClasses) + public int getFileServices(int subscriptionId, List<String> serviceClasses) throws RemoteException { return 0; } + /** + * Sets the temp file root directory for this app/subscriptionId combination. The middleware + * should persist {@code rootDirectoryPath} and send it back when sending intents to the + * app's {@link android.telephony.mbms.MbmsDownloadReceiver}. + * @param subscriptionId The subscription id the download is operating under. + * @param rootDirectoryPath The path to the app's temp file root directory. + * @return {@link MbmsException#ERROR_MIDDLEWARE_NOT_YET_READY}, + * {@link MbmsException#ERROR_CANNOT_CHANGE_TEMP_FILE_ROOT}, + * or {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED} + */ @Override - public int setTempFileRootDirectory(String appName, int subscriptionId, + public int setTempFileRootDirectory(int subscriptionId, String rootDirectoryPath) throws RemoteException { return 0; } + /** + * Issues a request to download a set of files. + * @param downloadRequest An object describing the set of files to be downloaded. + * @param listener A listener through which the middleware can provide progress updates to + * the app while both are still running. + * @return TODO: enumerate possible return values + */ @Override public int download(DownloadRequest downloadRequest, IDownloadCallback listener) throws RemoteException { @@ -55,11 +99,23 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { } @Override - public List<DownloadRequest> listPendingDownloads(String appName, int subscriptionId) + public List<DownloadRequest> listPendingDownloads(int subscriptionId) throws RemoteException { return null; } + /** + * Issues a request to cancel the specified download request. + * + * If the middleware is unable to cancel the request for whatever reason, it should return + * synchronously with an error. If this method returns {@link MbmsException#SUCCESS}, the app + * will no longer be expecting any more file-completed intents from the middleware for this + * {@link DownloadRequest}. + * @param downloadRequest The request to cancel + * @return {@link MbmsException#SUCCESS}, + * {@link MbmsException#ERROR_UNKNOWN_DOWNLOAD_REQUEST}, + * {@link MbmsException#ERROR_MIDDLEWARE_NOT_YET_READY} + */ @Override public int cancelDownload(DownloadRequest downloadRequest) throws RemoteException { return 0; @@ -76,7 +132,21 @@ public class MbmsDownloadServiceBase extends IMbmsDownloadService.Stub { throws RemoteException { } + /** + * Signals that the app wishes to dispose of the session identified by the + * {@code subscriptionId} argument and the caller's uid. No notification back to the + * app is required for this operation, and the corresponding callback provided via + * {@link #initialize(int, IMbmsDownloadManagerCallback)} should no longer be used + * after this method has been called by the app. + * + * Any download requests issued by the app should remain in effect until the app calls + * {@link #cancelDownload(DownloadRequest)} on another session. + * + * May throw an {@link IllegalStateException} + * + * @param subscriptionId The subscription id to use. + */ @Override - public void dispose(String appName, int subscriptionId) throws RemoteException { + public void dispose(int subscriptionId) throws RemoteException { } } diff --git a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java index 2c1f085b67d3..b6cf628d4036 100644 --- a/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java +++ b/telephony/java/android/telephony/mbms/vendor/MbmsStreamingServiceBase.java @@ -36,13 +36,12 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { * May throw an {@link IllegalArgumentException} or a {@link SecurityException} * * @param listener The callback to use to communicate with the app. - * @param appName The app name as negotiated with the wireless carrier. * @param subscriptionId The subscription ID to use. * @return {@link MbmsException#SUCCESS} or {@link MbmsException#ERROR_ALREADY_INITIALIZED} */ @Override - public int initialize(IMbmsStreamingManagerCallback listener, String appName, - int subscriptionId) throws RemoteException { + public int initialize(IMbmsStreamingManagerCallback listener, int subscriptionId) + throws RemoteException { return 0; } @@ -51,22 +50,21 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { * Starts async fetching data on streaming services of matching classes to be reported * later via {@link IMbmsStreamingManagerCallback#streamingServicesUpdated(List)} * - * Note that subsequent calls with the same uid, appName and subId will replace + * Note that subsequent calls with the same uid and subId will replace * the service class list. * * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} * - * @param appName The app name as negotiated with the wireless carrier. * @param subscriptionId The subscription id to use. * @param serviceClasses The service classes that the app wishes to get info on. The strings * may contain arbitrary data as negotiated between the app and the * carrier. * @return One of {@link MbmsException#SUCCESS}, - * {@link MbmsException#ERROR_MIDDLEWARE_NOT_BOUND}, + * {@link MbmsException#ERROR_MIDDLEWARE_NOT_YET_READY}, * {@link MbmsException#ERROR_CONCURRENT_SERVICE_LIMIT_REACHED} */ @Override - public int getStreamingServices(String appName, int subscriptionId, + public int getStreamingServices(int subscriptionId, List<String> serviceClasses) throws RemoteException { return 0; } @@ -74,11 +72,10 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { /** * Starts streaming on a particular service. This method may perform asynchronous work. When * the middleware is ready to send bits to the frontend, it should inform the app via - * {@link IStreamingServiceCallback#streamStateChanged(int)}. + * {@link IStreamingServiceCallback#streamStateUpdated(int)}. * * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} * - * @param appName The app name as negotiated with the wireless carrier. * @param subscriptionId The subscription id to use. * @param serviceId The ID of the streaming service that the app has requested. * @param listener The listener object on which the app wishes to receive updates. @@ -86,8 +83,8 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { * or {@link MbmsException#ERROR_UNABLE_TO_START_SERVICE}. */ @Override - public int startStreaming(String appName, int subscriptionId, - String serviceId, IStreamingServiceCallback listener) throws RemoteException { + public int startStreaming(int subscriptionId, String serviceId, + IStreamingServiceCallback listener) throws RemoteException { return 0; } @@ -97,13 +94,12 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { * * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} * - * @param appName The app name as negotiated with the wireless carrier. * @param subscriptionId The subscription id to use. * @param serviceId The ID of the streaming service that the app has requested. * @return An opaque {@link Uri} to be passed to a video player that understands the format. */ @Override - public @Nullable Uri getPlaybackUri(String appName, int subscriptionId, String serviceId) + public @Nullable Uri getPlaybackUri(int subscriptionId, String serviceId) throws RemoteException { return null; } @@ -111,16 +107,15 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { /** * Stop streaming the stream identified by {@code serviceId}. Notification of the resulting * stream state change should be reported to the app via - * {@link IStreamingServiceCallback#streamStateChanged(int)}. + * {@link IStreamingServiceCallback#streamStateUpdated(int)}. * * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} * - * @param appName The app name as negotiated with the wireless carrier. * @param subscriptionId The subscription id to use. * @param serviceId The ID of the streaming service that the app wishes to stop. */ @Override - public void stopStreaming(String appName, int subscriptionId, String serviceId) + public void stopStreaming(int subscriptionId, String serviceId) throws RemoteException { } @@ -128,33 +123,31 @@ public class MbmsStreamingServiceBase extends IMbmsStreamingService.Stub { * Dispose of the stream identified by {@code serviceId} for the app identified by the * {@code appName} and {@code subscriptionId} arguments along with the caller's uid. * No notification back to the app is required for this operation, and the callback provided via - * {@link #startStreaming(String, int, String, IStreamingServiceCallback)} should no longer be + * {@link #startStreaming(int, String, IStreamingServiceCallback)} should no longer be * used after this method has called by the app. * * May throw an {@link IllegalArgumentException} or an {@link IllegalStateException} * - * @param appName The app name as negotiated with the wireless carrier. * @param subscriptionId The subscription id to use. * @param serviceId The ID of the streaming service that the app wishes to dispose of. */ @Override - public void disposeStream(String appName, int subscriptionId, String serviceId) + public void disposeStream(int subscriptionId, String serviceId) throws RemoteException { } /** - * Signals that the app wishes to dispose of the session identified by the {@code appName} and - * {@code subscriptionId} arguments, as well as the caller's uid. No notification back to the + * Signals that the app wishes to dispose of the session identified by the + * {@code subscriptionId} argument and the caller's uid. No notification back to the * app is required for this operation, and the corresponding callback provided via - * {@link #initialize(IMbmsStreamingManagerCallback, String, int)} should no longer be used + * {@link #initialize(IMbmsStreamingManagerCallback, int)} should no longer be used * after this method has been called by the app. * * May throw an {@link IllegalStateException} * - * @param appName The app name as negotiated with the wireless carrier. * @param subscriptionId The subscription id to use. */ @Override - public void dispose(String appName, int subscriptionId) throws RemoteException { + public void dispose(int subscriptionId) throws RemoteException { } } diff --git a/tests/ShowWhenLockedApp/Android.mk b/tests/ShowWhenLockedApp/Android.mk new file mode 100644 index 000000000000..006416791dd9 --- /dev/null +++ b/tests/ShowWhenLockedApp/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := ShowWhenLocked + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_PACKAGE)
\ No newline at end of file diff --git a/tests/ShowWhenLockedApp/AndroidManifest.xml b/tests/ShowWhenLockedApp/AndroidManifest.xml new file mode 100644 index 000000000000..a872e061526f --- /dev/null +++ b/tests/ShowWhenLockedApp/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * Copyright (C) 2017 The Android Open 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.showwhenlocked"> + <application android:label="ShowWhenLocked"> + <activity android:name=".ShowWhenLockedActivity" + android:showWhenLocked="true" + android:turnScreenOn="true" + android:launchMode="singleTask"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/ShowWhenLockedApp/src/com/android/showwhenlocked/ShowWhenLockedActivity.java b/tests/ShowWhenLockedApp/src/com/android/showwhenlocked/ShowWhenLockedActivity.java new file mode 100644 index 000000000000..f71483182676 --- /dev/null +++ b/tests/ShowWhenLockedApp/src/com/android/showwhenlocked/ShowWhenLockedActivity.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2017 The Android Open 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.showwhenlocked; + +import android.app.Activity; +import android.app.KeyguardManager; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +/** + * Sample app to test the manifest attrs {@link android.R.attr#showWhenLocked} + * and {@link android.R.attr#turnScreenOn}. + * + * <p>Run with adb shell am start -n com.android.showwhenlocked/.ShowWhenLockedActivity to test + * when the phone has a keyguard enabled and/or the screen is off. + * + * Use the extra {@link #EXTRA_SHOW_WHEN_LOCKED} and {@link #EXTRA_TURN_SCREEN_ON} to test + * multiple scenarios. + * + * Ex: adb shell am start -n com.android.showwhenlocked/.ShowWhenLockedActivity --ez \ + * showWhenLocked false \ + * setTurnScreenOnAtStop false + * + * Note: Behavior may change if values are set to true after the Activity is already created + * and only brought to the front. For example, turnScreenOn only takes effect on the next launch + * if set using the extra value. + */ +public class ShowWhenLockedActivity extends Activity { + private static final String TAG = ShowWhenLockedActivity.class.getSimpleName(); + + /** + * The value set for this extra sets {@link #setShowWhenLocked(boolean)} as soon as the app + * is launched. This may cause delays in when the value set takes affect. + */ + private static final String EXTRA_SHOW_WHEN_LOCKED = "showWhenLocked"; + + /** + * The value set for this extra sets {@link #setTurnScreenOn(boolean)} as soon as the app + * is launched. This may cause delays in when the value set takes affect. + */ + private static final String EXTRA_TURN_SCREEN_ON = "turnScreenOn"; + + /** + * The value set for this extra will call {@link #setShowWhenLocked(boolean)} at onStop so + * it take effect on the next launch. + */ + private static final String EXTRA_SHOW_WHEN_LOCKED_STOP = "setShowWhenLockedAtStop"; + + /** + * The value set for this extra will call {@link #setTurnScreenOn(boolean)} at onStop so + * it take effect on the next launch. + */ + private static final String EXTRA_TURN_SCREEN_ON_STOP = "setTurnScreenOnAtStop"; + + /** + * The value set for this extra will call + * {@link KeyguardManager#requestDismissKeyguard(Activity, KeyguardManager.KeyguardDismissCallback)} + * as soon as the app is launched. + */ + private static final String EXTRA_DISMISS_KEYGUARD = "dismissKeyguard"; + + private boolean showWhenLockedAtStop = true; + private boolean turnScreenOnAtStop = true; + + private KeyguardManager mKeyguardManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.v(TAG, "onCreate"); + mKeyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); + handleExtras(getIntent().getExtras()); + } + + @Override + protected void onStart() { + super.onStart(); + Log.v(TAG, "onStart"); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleExtras(intent.getExtras()); + } + + @Override + protected void onResume() { + super.onResume(); + Log.v(TAG, "onResume"); + } + + @Override + protected void onPause() { + super.onPause(); + Log.v(TAG, "onPause"); + } + + @Override + protected void onStop() { + super.onStop(); + Log.v(TAG, "onStop"); + + setShowWhenLocked(showWhenLockedAtStop); + setTurnScreenOn(turnScreenOnAtStop); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + Log.v(TAG, "onDestroy"); + } + + private void handleExtras(Bundle extras) { + if (extras == null) { + return; + } + + if (extras.containsKey(EXTRA_SHOW_WHEN_LOCKED)) { + boolean showWhenLocked = extras.getBoolean(EXTRA_SHOW_WHEN_LOCKED, true); + Log.v(TAG, "Setting showWhenLocked to " + showWhenLocked); + setShowWhenLocked(showWhenLocked); + } + + if (extras.containsKey(EXTRA_TURN_SCREEN_ON)) { + boolean turnScreenOn = extras.getBoolean(EXTRA_TURN_SCREEN_ON, true); + Log.v(TAG, "Setting turnScreenOn to " + turnScreenOn); + setTurnScreenOn(turnScreenOn); + } + + if (extras.containsKey(EXTRA_SHOW_WHEN_LOCKED_STOP)) { + showWhenLockedAtStop = extras.getBoolean(EXTRA_SHOW_WHEN_LOCKED_STOP, true); + Log.v(TAG, "Setting showWhenLockedAtStop to " + showWhenLockedAtStop); + } + + if (extras.containsKey(EXTRA_TURN_SCREEN_ON_STOP)) { + turnScreenOnAtStop = extras.getBoolean(EXTRA_TURN_SCREEN_ON_STOP, true); + Log.v(TAG, "Setting turnScreenOnAtStop to " + turnScreenOnAtStop); + } + + if (extras.containsKey(EXTRA_DISMISS_KEYGUARD)) { + if (extras.getBoolean(EXTRA_DISMISS_KEYGUARD, false)) { + Log.v(TAG, "Requesting dismiss keyguard"); + mKeyguardManager.requestDismissKeyguard(this, null); + } + } + } +} + diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index 7ff0c7227c9c..f16d806fe5c7 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -987,4 +987,8 @@ bool ConfigDescription::IsCompatibleWith(const ConfigDescription& o) const { return !ConflictsWith(o) && !Dominates(o) && !o.Dominates(*this); } +::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o) { + return out << o.toString().string(); +} + } // namespace aapt diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h index 65c96175091c..8c519a1a913c 100644 --- a/tools/aapt2/ConfigDescription.h +++ b/tools/aapt2/ConfigDescription.h @@ -24,30 +24,22 @@ namespace aapt { -/* - * Subclass of ResTable_config that adds convenient - * initialization and comparison methods. - */ +// Subclass of ResTable_config that adds convenient +// initialization and comparison methods. struct ConfigDescription : public android::ResTable_config { - /** - * Returns an immutable default config. - */ + // 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. - */ + // 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 android::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. - */ + // 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(); @@ -61,38 +53,30 @@ struct ConfigDescription : public android::ResTable_config { 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'. - */ + // 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". - */ + // 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. - */ + // 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). - */ + // 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; @@ -105,6 +89,8 @@ struct ConfigDescription : public android::ResTable_config { bool operator>(const ConfigDescription& o) const; }; +::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o); + inline ConfigDescription::ConfigDescription() { memset(this, 0, sizeof(*this)); size = sizeof(android::ResTable_config); @@ -123,15 +109,13 @@ inline ConfigDescription::ConfigDescription(ConfigDescription&& o) { *this = o; } -inline ConfigDescription& ConfigDescription::operator=( - const android::ResTable_config& 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 ConfigDescription& o) { +inline ConfigDescription& ConfigDescription::operator=(const ConfigDescription& o) { *static_cast<android::ResTable_config*>(this) = o; return *this; } @@ -141,8 +125,7 @@ inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) { return *this; } -inline bool ConfigDescription::MatchWithDensity( - const ConfigDescription& o) const { +inline bool ConfigDescription::MatchWithDensity(const ConfigDescription& o) const { return match(o) && (density == 0 || density == o.density); } @@ -170,11 +153,6 @@ inline bool ConfigDescription::operator>(const ConfigDescription& o) const { return compare(o) > 0; } -inline ::std::ostream& operator<<(::std::ostream& out, - const ConfigDescription& o) { - return out << o.toString().string(); -} - } // namespace aapt #endif // AAPT_CONFIG_DESCRIPTION_H diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index e6bf3a6f9f56..353d734b69c6 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -60,6 +60,7 @@ #include "unflatten/BinaryResourceParser.h" #include "util/Files.h" #include "xml/XmlDom.h" +#include "xml/XmlUtil.h" using android::StringPiece; using android::base::StringPrintf; @@ -342,16 +343,18 @@ class ResourceFileFlattener { ConfigDescription config; // The entry this file came from. - ResourceEntry* entry; + ResourceEntry* entry = nullptr; // The file to copy as-is. - io::IFile* file_to_copy; + io::IFile* file_to_copy = nullptr; // The XML to process and flatten. std::unique_ptr<xml::XmlResource> xml_to_flatten; // The destination to write this file to. std::string dst_path; + + bool skip_versioning = false; }; uint32_t GetCompressionFlags(const StringPiece& str); @@ -431,19 +434,6 @@ uint32_t ResourceFileFlattener::GetCompressionFlags(const StringPiece& str) { return ArchiveEntry::kCompress; } -static bool IsTransitionElement(const std::string& name) { - return name == "fade" || name == "changeBounds" || name == "slide" || name == "explode" || - name == "changeImageTransform" || name == "changeTransform" || - name == "changeClipBounds" || name == "autoTransition" || name == "recolor" || - name == "changeScroll" || name == "transitionSet" || name == "transition" || - name == "transitionManager"; -} - -static bool IsVectorElement(const std::string& name) { - return name == "vector" || name == "animated-vector" || name == "pathInterpolator" || - name == "objectAnimator"; -} - template <typename T> std::vector<T> make_singleton_vec(T&& val) { std::vector<T> vec; @@ -476,21 +466,10 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer } } - if (options_.no_auto_version) { + if (options_.no_auto_version || file_op->skip_versioning) { return make_singleton_vec(std::move(file_op->xml_to_flatten)); } - if (options_.no_version_vectors || options_.no_version_transitions) { - // Skip this if it is a vector or animated-vector. - xml::Element* el = xml::FindRootElement(doc); - if (el && el->namespace_uri.empty()) { - if ((options_.no_version_vectors && IsVectorElement(el->name)) || - (options_.no_version_transitions && IsTransitionElement(el->name))) { - return make_singleton_vec(std::move(file_op->xml_to_flatten)); - } - } - } - const ConfigDescription& config = file_op->config; ResourceEntry* entry = file_op->entry; @@ -504,15 +483,26 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv bool error = false; std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> config_sorted_files; + int tag_version_options = 0; + if (options_.no_version_vectors) { + tag_version_options |= xml::kNoVersionVector; + } + + if (options_.no_version_transitions) { + tag_version_options |= xml::kNoVersionTransition; + } + 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& config_value : entry->values) { + const auto values_end = entry->values.end(); + for (auto values_iter = entry->values.begin(); values_iter != values_end; ++values_iter) { + ResourceConfigValue* config_value = values_iter->get(); + // WARNING! Do not insert or remove any resources while executing in this scope. It will // corrupt the iteration order. @@ -554,6 +544,44 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv 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); + + // Check if this file needs to be versioned based on tag rules. + xml::Element* root_el = xml::FindRootElement(file_op.xml_to_flatten.get()); + if (root_el == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + << "failed to find the root XML element"); + return false; + } + + if (root_el->namespace_uri.empty()) { + if (Maybe<xml::TagApiVersionResult> result = + xml::GetXmlTagApiVersion(root_el->name, tag_version_options)) { + file_op.skip_versioning = result.value().skip_version; + if (result.value().api_version && config_value->config.sdkVersion == 0u) { + const ApiVersion min_tag_version = result.value().api_version.value(); + // Only version it if it doesn't specify its own version and the version is + // greater than the minSdk. + const util::Range<ApiVersion> valid_range{ + context_->GetMinSdkVersion() + 1, + FindNextApiVersionForConfigInSortedVector(values_iter, values_end)}; + if (valid_range.Contains(min_tag_version)) { + // Update the configurations. The iteration order will not be affected + // since sdkVersions in ConfigDescriptions are the last property compared + // in the sort function. + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note(DiagMessage(config_value->value->GetSource()) + << "auto-versioning XML resource to API " + << min_tag_version); + } + const_cast<ConfigDescription&>(config_value->config).sdkVersion = + static_cast<uint16_t>(min_tag_version); + file_op.config.sdkVersion = static_cast<uint16_t>(min_tag_version); + file_op.xml_to_flatten->file.config.sdkVersion = + static_cast<uint16_t>(min_tag_version); + } + } + } + } } // NOTE(adamlesinski): Explicitly construct a StringPiece here, or diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/adaptive-icon.xml b/tools/aapt2/integration-tests/AppOne/res/drawable/adaptive-icon.xml new file mode 100644 index 000000000000..e3cda7e1e979 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/res/drawable/adaptive-icon.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open 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. +--> + +<adaptive-icon xmlns:android="http://schema.android.com/apk/res/android"> + <background android:drawable="@android:color/white" /> + <foreground android:drawable="@drawable/image" /> +</adaptive-icon> diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp index f80c6e9b34d3..4ac70d91bcc9 100644 --- a/tools/aapt2/link/AutoVersioner.cpp +++ b/tools/aapt2/link/AutoVersioner.cpp @@ -34,6 +34,19 @@ bool ShouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDes return sdk_version_to_generate < FindNextApiVersionForConfig(entry, config); } +ApiVersion FindNextApiVersionForConfigInSortedVector( + std::vector<std::unique_ptr<ResourceConfigValue>>::const_iterator start, + std::vector<std::unique_ptr<ResourceConfigValue>>::const_iterator end) { + const ConfigDescription start_config = (*start)->config.CopyWithoutSdkVersion(); + ++start; + if (start != end) { + if ((*start)->config.CopyWithoutSdkVersion() == start_config) { + return static_cast<ApiVersion>((*start)->config.sdkVersion); + } + } + return std::numeric_limits<ApiVersion>::max(); +} + ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry, const ConfigDescription& config) { const auto end_iter = entry->values.end(); @@ -46,25 +59,7 @@ ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry, // 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. - - // 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, return the sdkVersion. - return (*iter)->config.sdkVersion; - } - } - - // Didn't find another config with a different sdk version, so return the highest possible value. - return std::numeric_limits<ApiVersion>::max(); + return FindNextApiVersionForConfigInSortedVector(iter, end_iter); } bool AutoVersioner::Consume(IAaptContext* context, ResourceTable* table) { diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp index 49639f8ad549..88a831b9e801 100644 --- a/tools/aapt2/link/AutoVersioner_test.cpp +++ b/tools/aapt2/link/AutoVersioner_test.cpp @@ -42,8 +42,8 @@ TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) { 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, "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dp_v13_config, "")); EXPECT_TRUE(ShouldGenerateVersionedResource(&entry, ConfigDescription::DefaultConfig(), 17)); EXPECT_FALSE(ShouldGenerateVersionedResource(&entry, ConfigDescription::DefaultConfig(), 22)); diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h index 5527f9092c87..493b6b1181b9 100644 --- a/tools/aapt2/link/Linkers.h +++ b/tools/aapt2/link/Linkers.h @@ -23,6 +23,7 @@ #include "android-base/macros.h" #include "Resource.h" +#include "ResourceTable.h" #include "SdkConstants.h" #include "process/IResourceTableConsumer.h" #include "xml/XmlDom.h" @@ -41,17 +42,19 @@ struct CallSite { 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 ApiVersion sdk_version_to_generate); -// Finds the next largest ApiVersion of the config which is identical to the given config except -// for sdkVersion. +// Finds the next largest ApiVersion of `config` for values defined for `entry`. ApiVersion FindNextApiVersionForConfig(const ResourceEntry* entry, const ConfigDescription& config); +// Finds the next largest ApiVersion of the config pointed to by the iterator `start`. +ApiVersion FindNextApiVersionForConfigInSortedVector( + std::vector<std::unique_ptr<ResourceConfigValue>>::const_iterator start, + std::vector<std::unique_ptr<ResourceConfigValue>>::const_iterator end); + class AutoVersioner : public IResourceTableConsumer { public: AutoVersioner() = default; diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md index ebcd4698d8d5..8c476fac3b89 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -9,6 +9,7 @@ (bug 62839863) - Fixed issue where Java classes referenced from fragments and menus were not added to the set of Proguard keep rules. (bug 62216174) +- Automatically version XML `<adaptive-icon>` resources to v26. (bug 62316340) ## Version 2.17 ### `aapt2 ...` diff --git a/tools/aapt2/util/TypeTraits.h b/tools/aapt2/util/TypeTraits.h index b6539edce6d9..6fcb4bdb4145 100644 --- a/tools/aapt2/util/TypeTraits.h +++ b/tools/aapt2/util/TypeTraits.h @@ -38,6 +38,7 @@ namespace aapt { DEFINE_HAS_BINARY_OP_TRAIT(has_eq_op, ==); DEFINE_HAS_BINARY_OP_TRAIT(has_lt_op, <); +DEFINE_HAS_BINARY_OP_TRAIT(has_lte_op, <=); /** * Type trait that checks if two types can be equated (==) and compared (<). diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 386f74b0301a..8bca9dd739bf 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -48,6 +48,11 @@ template <typename T> struct Range { T start; T end; + + typename std::enable_if<has_lte_op<const T, const T>::value, bool>::type Contains( + const T& t) const { + return start <= t && t < end; + } }; std::vector<std::string> Split(const android::StringPiece& str, char sep); diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp index fb8cee8b5634..fa1b0f049678 100644 --- a/tools/aapt2/xml/XmlUtil.cpp +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -80,5 +80,65 @@ void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack, } } +namespace { + +struct TagCompat { + ApiVersion api_version; + + enum class XmlType { + kVector, + kTransition, + kAdaptiveIcon, + }; + + XmlType type; +}; + +std::unordered_map<StringPiece, TagCompat> sTagVersions = { + {"fade", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + {"changeBounds", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + {"slide", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + {"explode", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + {"changeImageTransform", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + {"changeTransform", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + {"changeClipBounds", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + {"autoTransition", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + {"recolor", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + {"changeScroll", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + {"transitionSet", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + {"transition", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + {"transitionManager", {SDK_LOLLIPOP, TagCompat::XmlType::kTransition}}, + + {"vector", {SDK_LOLLIPOP, TagCompat::XmlType::kVector}}, + {"animated-vector", {SDK_LOLLIPOP, TagCompat::XmlType::kVector}}, + {"pathInterpolator", {SDK_LOLLIPOP, TagCompat::XmlType::kVector}}, + {"objectAnimator", {SDK_LOLLIPOP, TagCompat::XmlType::kVector}}, + + {"adaptive-icon", {SDK_O, TagCompat::XmlType::kAdaptiveIcon}}, +}; + +} // namespace + +Maybe<TagApiVersionResult> GetXmlTagApiVersion(const StringPiece& tag_name, int options) { + auto iter = sTagVersions.find(tag_name); + if (iter == sTagVersions.end()) { + return {}; + } + + const TagCompat& tag_compat = iter->second; + if (options & kNoVersionVector) { + if (tag_compat.type == TagCompat::XmlType::kVector) { + return TagApiVersionResult{{}, true /*skip_version*/}; + } + } + + if (options & kNoVersionTransition) { + if (tag_compat.type == TagCompat::XmlType::kTransition) { + return TagApiVersionResult{{}, true /*skip_version*/}; + } + } + return TagApiVersionResult{tag_compat.api_version, false /*skip_version*/}; +} + } // namespace xml } // namespace aapt diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h index 1650ac2124ac..552f42adc464 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -20,18 +20,16 @@ #include <string> #include "ResourceValues.h" +#include "SdkConstants.h" #include "util/Maybe.h" 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"; @@ -102,6 +100,24 @@ struct IPackageDeclStack { void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack, const android::StringPiece& local_package, Reference* in_ref); +struct TagApiVersionResult { + // If set, the API version to apply. + Maybe<ApiVersion> api_version; + + // Whether to skip any auto-versioning. + bool skip_version; +}; + +enum TagVersionOptions : int { + // Skip versioning XML resources that deal with vector drawables. + kNoVersionVector, + + // Skip versioning XML resources that deal with transitions. + kNoVersionTransition, +}; + +Maybe<TagApiVersionResult> GetXmlTagApiVersion(const android::StringPiece& tag_name, int options); + } // namespace xml } // namespace aapt |