From 72b5616890c1b309f5a5115b90ee7e333f322930 Mon Sep 17 00:00:00 2001 From: Eugene Susla Date: Wed, 6 Dec 2017 13:08:39 -0800 Subject: [PATCH 001/416] Minor optimization for CollectionUtils.mapNotNull Test: presubmit Change-Id: Ibdafa1d37e09a4e326e0c5c4a4696fc954437ba0 --- core/java/com/android/internal/util/CollectionUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java index f0b47de8be9..462c0540313 100644 --- a/core/java/com/android/internal/util/CollectionUtils.java +++ b/core/java/com/android/internal/util/CollectionUtils.java @@ -140,14 +140,14 @@ public class CollectionUtils { public static @NonNull List mapNotNull(@Nullable List cur, Function f) { if (isEmpty(cur)) return Collections.emptyList(); - final ArrayList result = new ArrayList<>(); + List result = null; for (int i = 0; i < cur.size(); i++) { O transformed = f.apply(cur.get(i)); if (transformed != null) { - result.add(transformed); + result = add(result, transformed); } } - return result; + return emptyIfNull(result); } /** -- GitLab From abdd37c8c6ff02371ba071037378a6ef5e43f514 Mon Sep 17 00:00:00 2001 From: Alex Khlivnuik Date: Wed, 29 Nov 2017 12:18:05 +0900 Subject: [PATCH 002/416] Add CarrierConfig to displaying HD audio indicator for GSM/CDMA calls This causes UI to display HD audio indicator even in GSM/CDMA calls. This feature is necessary to fulfill some carriers requirement. Test: manual - Checked that HD audio indicator for GSM/CDMA calls can be controlled by carrier config. Bug: 30207043 Change-Id: I57298d8813a5cc53110af02a369af144136f7fc9 --- .../java/android/telephony/CarrierConfigManager.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index d80ad365438..24303555c08 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1339,6 +1339,14 @@ public class CarrierConfigManager { */ public static final String KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO = "video_calls_can_be_hd_audio"; + /** + * When true, indicates that the HD audio icon in the in-call screen should be shown for + * GSM/CDMA calls. + * @hide + */ + public static final String KEY_GSM_CDMA_CALLS_CAN_BE_HD_AUDIO = + "gsm_cdma_calls_can_be_hd_audio"; + /** * Whether system apps are allowed to use fallback if carrier video call is not available. * Defaults to {@code true}. @@ -1959,6 +1967,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL, true); sDefaults.putBoolean(KEY_WIFI_CALLS_CAN_BE_HD_AUDIO, true); sDefaults.putBoolean(KEY_VIDEO_CALLS_CAN_BE_HD_AUDIO, true); + sDefaults.putBoolean(KEY_GSM_CDMA_CALLS_CAN_BE_HD_AUDIO, false); sDefaults.putBoolean(KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL, true); sDefaults.putStringArray(KEY_IMS_REASONINFO_MAPPING_STRING_ARRAY, null); -- GitLab From 4304a02ac990c1af5fb8f479bdd2b04c8af4fddb Mon Sep 17 00:00:00 2001 From: Shuzhen Wang Date: Fri, 8 Dec 2017 12:16:49 -0800 Subject: [PATCH 003/416] OutputConfiguration: Fix missing mIsShared in parcel read Test: Camera CTS Bug: 69683251 Merged-In: I7ea4aa8ed4baa5a5e7d25a0073361d827ba86c13 Change-Id: I7ea4aa8ed4baa5a5e7d25a0073361d827ba86c13 --- .../android/hardware/camera2/params/OutputConfiguration.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 2b317d679d1..05c4dc37889 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -409,6 +409,7 @@ public final class OutputConfiguration implements Parcelable { this.mConfiguredSize = other.mConfiguredSize; this.mConfiguredGenerationId = other.mConfiguredGenerationId; this.mIsDeferredConfig = other.mIsDeferredConfig; + this.mIsShared = other.mIsShared; } /** @@ -421,6 +422,7 @@ public final class OutputConfiguration implements Parcelable { int width = source.readInt(); int height = source.readInt(); boolean isDeferred = source.readInt() == 1; + boolean isShared = source.readInt() == 1; ArrayList surfaces = new ArrayList(); source.readTypedList(surfaces, Surface.CREATOR); @@ -431,6 +433,7 @@ public final class OutputConfiguration implements Parcelable { mSurfaces = surfaces; mConfiguredSize = new Size(width, height); mIsDeferredConfig = isDeferred; + mIsShared = isShared; mSurfaces = surfaces; if (mSurfaces.size() > 0) { mSurfaceType = SURFACE_TYPE_UNKNOWN; -- GitLab From 493cebd02538adf347413450238bb1f3f7a0d541 Mon Sep 17 00:00:00 2001 From: Yohann Roussel Date: Mon, 15 Jan 2018 14:27:00 +0100 Subject: [PATCH 004/416] Add tests about MultiDex corruption recovering Those are testing extracted zip corruption and also corruption of their corresponding odex file and the capacity of MultiDex.install to restore a runnable state. Bug: 28832787 Test: This is the test Change-Id: I8dd99172d545e700b12c2a2b1391ef1aeb5560ce --- .../MultiDexLegacyTestServices/Android.mk | 2 - .../AndroidManifest.xml | 4 +- .../AbstractService.java | 52 +-- .../Android.mk | 33 ++ .../AndroidManifest.xml | 18 + .../test2/ServicesTests.java | 381 ++++++++++++++++++ 6 files changed, 464 insertions(+), 26 deletions(-) create mode 100644 core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk create mode 100644 core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml create mode 100644 core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/src/com/android/framework/multidexlegacytestservices/test2/ServicesTests.java diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk index 99bcd6c62b5..a6c537373f2 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/Android.mk @@ -36,10 +36,8 @@ LOCAL_DEX_PREOPT := false include $(BUILD_PACKAGE) -ifndef LOCAL_JACK_ENABLED $(mainDexList): $(full_classes_proguard_jar) | $(MAINDEXCLASSES) $(hide) mkdir -p $(dir $@) $(MAINDEXCLASSES) $< 1>$@ $(built_dex_intermediate): $(mainDexList) -endif diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/AndroidManifest.xml index e30689203c1..7cd01e54a64 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/AndroidManifest.xml +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/AndroidManifest.xml @@ -7,6 +7,8 @@ + + @@ -124,6 +126,6 @@ - + diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java index 7b83999d0ca..cb0a591559d 100644 --- a/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServices/src/com/android/framework/multidexlegacytestservices/AbstractService.java @@ -60,35 +60,40 @@ public abstract class AbstractService extends Service implements Runnable { // of the result file will be too big. RandomAccessFile raf = new RandomAccessFile(resultFile, "rw"); raf.seek(raf.length()); - Log.i(TAG, "Writing 0x42434445 at " + raf.length() + " in " + resultFile.getPath()); - raf.writeInt(0x42434445); + if (raf.length() == 0) { + Log.i(TAG, "Writing 0x42434445 at " + raf.length() + " in " + resultFile.getPath()); + raf.writeInt(0x42434445); + } else { + Log.w(TAG, "Service was restarted appending 0x42434445 twice at " + raf.length() + + " in " + resultFile.getPath()); + raf.writeInt(0x42434445); + raf.writeInt(0x42434445); + } raf.close(); - } catch (IOException e) { - e.printStackTrace(); - } - MultiDex.install(applicationContext); - Log.i(TAG, "Multi dex installation done."); + MultiDex.install(applicationContext); + Log.i(TAG, "Multi dex installation done."); - int value = getValue(); - Log.i(TAG, "Saving the result (" + value + ") to " + resultFile.getPath()); - try { + int value = getValue(); + Log.i(TAG, "Saving the result (" + value + ") to " + resultFile.getPath()); // Append the check value in result file, keeping the constant values already written. - RandomAccessFile raf = new RandomAccessFile(resultFile, "rw"); + raf = new RandomAccessFile(resultFile, "rw"); raf.seek(raf.length()); Log.i(TAG, "Writing result at " + raf.length() + " in " + resultFile.getPath()); raf.writeInt(value); raf.close(); } catch (IOException e) { - e.printStackTrace(); - } - try { - // Writing end of processing flags, the existence of the file is the criteria - RandomAccessFile raf = new RandomAccessFile(new File(applicationContext.getFilesDir(), getId() + ".complete"), "rw"); - Log.i(TAG, "creating complete file " + resultFile.getPath()); - raf.writeInt(0x32333435); - raf.close(); - } catch (IOException e) { - e.printStackTrace(); + throw new AssertionError(e); + } finally { + try { + // Writing end of processing flags, the existence of the file is the criteria + RandomAccessFile raf = new RandomAccessFile( + new File(applicationContext.getFilesDir(), getId() + ".complete"), "rw"); + Log.i(TAG, "creating complete file " + resultFile.getPath()); + raf.writeInt(0x32333435); + raf.close(); + } catch (IOException e) { + e.printStackTrace(); + } } } @@ -119,9 +124,10 @@ public abstract class AbstractService extends Service implements Runnable { intermediate = ReflectIntermediateClass.get(45, 80, 20 /* 5 seems enough on a nakasi, using 20 to get some margin */); } catch (Exception e) { - e.printStackTrace(); + throw new AssertionError(e); } - int value = new com.android.framework.multidexlegacytestservices.manymethods.Big001().get1() + + int value = + new com.android.framework.multidexlegacytestservices.manymethods.Big001().get1() + new com.android.framework.multidexlegacytestservices.manymethods.Big002().get2() + new com.android.framework.multidexlegacytestservices.manymethods.Big003().get3() + new com.android.framework.multidexlegacytestservices.manymethods.Big004().get4() + diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk new file mode 100644 index 00000000000..f3d98a88d48 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/Android.mk @@ -0,0 +1,33 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_PACKAGE_NAME := MultiDexLegacyTestServicesTests2 + +LOCAL_JAVA_LIBRARIES := android-support-multidex +LOCAL_STATIC_JAVA_LIBRARIES := android-support-test + +LOCAL_SDK_VERSION := 9 + +LOCAL_DEX_PREOPT := false + +include $(BUILD_PACKAGE) + diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml new file mode 100644 index 00000000000..0ab29591be1 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/src/com/android/framework/multidexlegacytestservices/test2/ServicesTests.java b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/src/com/android/framework/multidexlegacytestservices/test2/ServicesTests.java new file mode 100644 index 00000000000..900f20387c4 --- /dev/null +++ b/core/tests/hosttests/test-apps/MultiDexLegacyTestServicesTests2/src/com/android/framework/multidexlegacytestservices/test2/ServicesTests.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.framework.multidexlegacytestservices.test2; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.concurrent.TimeoutException; +import junit.framework.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Run the tests with: adb shell am instrument -w + * com.android.framework.multidexlegacytestservices.test2/android.support.test.runner.AndroidJUnitRunner + * + */ +@RunWith(AndroidJUnit4.class) +public class ServicesTests { + private static final String TAG = "ServicesTests"; + + static { + Log.i(TAG, "Initializing"); + } + + private class ExtensionFilter implements FileFilter { + private final String ext; + + public ExtensionFilter(String ext) { + this.ext = ext; + } + + @Override + public boolean accept(File file) { + return file.getName().endsWith(ext); + } + } + + private class ExtractedZipFilter extends ExtensionFilter { + public ExtractedZipFilter() { + super(".zip"); + } + + @Override + public boolean accept(File file) { + return super.accept(file) && !file.getName().startsWith("tmp-"); + } + } + + private static final int ENDHDR = 22; + + private static final String SERVICE_BASE_ACTION = + "com.android.framework.multidexlegacytestservices.action.Service"; + private static final int MIN_SERVICE = 1; + private static final int MAX_SERVICE = 19; + private static final String COMPLETION_SUCCESS = "Success"; + + private File targetFilesDir; + + @Before + public void setup() throws Exception { + Log.i(TAG, "setup"); + killServices(); + + File applicationDataDir = + new File(InstrumentationRegistry.getTargetContext().getApplicationInfo().dataDir); + clearDirContent(applicationDataDir); + targetFilesDir = InstrumentationRegistry.getTargetContext().getFilesDir(); + + Log.i(TAG, "setup done"); + } + + @Test + public void testStressConcurentLaunch() throws Exception { + startServices(); + waitServicesCompletion(); + String completionStatus = getServicesCompletionStatus(); + if (completionStatus != COMPLETION_SUCCESS) { + Assert.fail(completionStatus); + } + } + + @Test + public void testRecoverFromZipCorruption() throws Exception { + int serviceId = 1; + // Ensure extraction. + initServicesWorkFiles(); + startService(serviceId); + waitServicesCompletion(serviceId); + + // Corruption of the extracted zips. + tamperAllExtractedZips(); + + killServices(); + checkRecover(); + } + + @Test + public void testRecoverFromDexCorruption() throws Exception { + int serviceId = 1; + // Ensure extraction. + initServicesWorkFiles(); + startService(serviceId); + waitServicesCompletion(serviceId); + + // Corruption of the odex files. + tamperAllOdex(); + + killServices(); + checkRecover(); + } + + @Test + public void testRecoverFromZipCorruptionStressTest() throws Exception { + Thread startServices = + new Thread() { + @Override + public void run() { + startServices(); + } + }; + + startServices.start(); + + // Start services lasts more than 80s, lets cause a few corruptions during this interval. + for (int i = 0; i < 80; i++) { + Thread.sleep(1000); + tamperAllExtractedZips(); + } + startServices.join(); + try { + waitServicesCompletion(); + } catch (TimeoutException e) { + // Can happen. + } + + killServices(); + checkRecover(); + } + + @Test + public void testRecoverFromDexCorruptionStressTest() throws Exception { + Thread startServices = + new Thread() { + @Override + public void run() { + startServices(); + } + }; + + startServices.start(); + + // Start services lasts more than 80s, lets cause a few corruptions during this interval. + for (int i = 0; i < 80; i++) { + Thread.sleep(1000); + tamperAllOdex(); + } + startServices.join(); + try { + waitServicesCompletion(); + } catch (TimeoutException e) { + // Will probably happen most of the time considering what we're doing... + } + + killServices(); + checkRecover(); + } + + private static void clearDirContent(File dir) { + for (File subElement : dir.listFiles()) { + if (subElement.isDirectory()) { + clearDirContent(subElement); + } + if (!subElement.delete()) { + throw new AssertionError("Failed to clear '" + subElement.getAbsolutePath() + "'"); + } + } + } + + private void startServices() { + Log.i(TAG, "start services"); + initServicesWorkFiles(); + for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) { + startService(i); + try { + Thread.sleep((i - 1) * (1 << (i / 5))); + } catch (InterruptedException e) { + } + } + } + + private void startService(int serviceId) { + Log.i(TAG, "start service " + serviceId); + InstrumentationRegistry.getContext().startService(new Intent(SERVICE_BASE_ACTION + serviceId)); + } + + private void initServicesWorkFiles() { + for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) { + File resultFile = new File(targetFilesDir, "Service" + i); + resultFile.delete(); + Assert.assertFalse( + "Failed to delete result file '" + resultFile.getAbsolutePath() + "'.", + resultFile.exists()); + File completeFile = new File(targetFilesDir, "Service" + i + ".complete"); + completeFile.delete(); + Assert.assertFalse( + "Failed to delete completion file '" + completeFile.getAbsolutePath() + "'.", + completeFile.exists()); + } + } + + private void waitServicesCompletion() throws TimeoutException { + Log.i(TAG, "start sleeping"); + int attempt = 0; + int maxAttempt = 50; // 10 is enough for a nexus S + do { + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + } + attempt++; + if (attempt >= maxAttempt) { + throw new TimeoutException(); + } + } while (!areAllServicesCompleted()); + } + + private void waitServicesCompletion(int serviceId) throws TimeoutException { + Log.i(TAG, "start sleeping"); + int attempt = 0; + int maxAttempt = 50; // 10 is enough for a nexus S + do { + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + } + attempt++; + if (attempt >= maxAttempt) { + throw new TimeoutException(); + } + } while (isServiceRunning(serviceId)); + } + + private String getServicesCompletionStatus() { + String status = COMPLETION_SUCCESS; + for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) { + File resultFile = new File(targetFilesDir, "Service" + i); + if (!resultFile.isFile()) { + status = "Service" + i + " never completed."; + break; + } + if (resultFile.length() != 8) { + status = "Service" + i + " was restarted."; + break; + } + } + Log.i(TAG, "Services completion status: " + status); + return status; + } + + private String getServiceCompletionStatus(int serviceId) { + String status = COMPLETION_SUCCESS; + File resultFile = new File(targetFilesDir, "Service" + serviceId); + if (!resultFile.isFile()) { + status = "Service" + serviceId + " never completed."; + } else if (resultFile.length() != 8) { + status = "Service" + serviceId + " was restarted."; + } + Log.i(TAG, "Service " + serviceId + " completion status: " + status); + return status; + } + + private boolean areAllServicesCompleted() { + for (int i = MIN_SERVICE; i <= MAX_SERVICE; i++) { + if (isServiceRunning(i)) { + return false; + } + } + return true; + } + + private boolean isServiceRunning(int i) { + File completeFile = new File(targetFilesDir, "Service" + i + ".complete"); + return !completeFile.exists(); + } + + private File getSecondaryFolder() { + File dir = + new File( + new File( + InstrumentationRegistry.getTargetContext().getApplicationInfo().dataDir, + "code_cache"), + "secondary-dexes"); + Assert.assertTrue(dir.getAbsolutePath(), dir.isDirectory()); + return dir; + } + + private void tamperAllExtractedZips() throws IOException { + // First attempt was to just overwrite zip entries but keep central directory, this was no + // trouble for Dalvik that was just ignoring those zip and using the odex files. + Log.i(TAG, "Tamper extracted zip files by overwriting all content by '\\0's."); + byte[] zeros = new byte[4 * 1024]; + // Do not tamper tmp zip during their extraction. + for (File zip : getSecondaryFolder().listFiles(new ExtractedZipFilter())) { + long fileLength = zip.length(); + Assert.assertTrue(fileLength > ENDHDR); + zip.setWritable(true); + RandomAccessFile raf = new RandomAccessFile(zip, "rw"); + try { + int index = 0; + while (index < fileLength) { + int length = (int) Math.min(zeros.length, fileLength - index); + raf.write(zeros, 0, length); + index += length; + } + } finally { + raf.close(); + } + } + } + + private void tamperAllOdex() throws IOException { + Log.i(TAG, "Tamper odex files by overwriting some content by '\\0's."); + byte[] zeros = new byte[4 * 1024]; + // I think max size would be 40 (u1[8] + 8 u4) but it's a test so lets take big margins. + int savedSizeForOdexHeader = 80; + for (File odex : getSecondaryFolder().listFiles(new ExtensionFilter(".dex"))) { + long fileLength = odex.length(); + Assert.assertTrue(fileLength > zeros.length + savedSizeForOdexHeader); + odex.setWritable(true); + RandomAccessFile raf = new RandomAccessFile(odex, "rw"); + try { + raf.seek(savedSizeForOdexHeader); + raf.write(zeros, 0, zeros.length); + } finally { + raf.close(); + } + } + } + + private void checkRecover() throws TimeoutException { + Log.i(TAG, "Check recover capability"); + int serviceId = 1; + // Start one service and check it was able to run correctly even if a previous run failed. + initServicesWorkFiles(); + startService(serviceId); + waitServicesCompletion(serviceId); + String completionStatus = getServiceCompletionStatus(serviceId); + if (completionStatus != COMPLETION_SUCCESS) { + Assert.fail(completionStatus); + } + } + + private void killServices() { + ((ActivityManager) + InstrumentationRegistry.getContext().getSystemService(Context.ACTIVITY_SERVICE)) + .killBackgroundProcesses("com.android.framework.multidexlegacytestservices"); + } +} -- GitLab From 5ec65ae909a85d13d03c030be357c8c14a50d306 Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Thu, 9 Nov 2017 17:12:17 -0800 Subject: [PATCH 005/416] Check for null-terminator in ResStringPool::string8At All other stringAt methods check for null termination. Be consistent so that upper levels don't end up with huge corrupt strings. Bug: 62537081 Test: none Change-Id: I17bdfb0c1e34507b66c6cad651bbdb12c5d4c417 (cherry picked from commit 3d35a0ea307693a97583a61973e729a5e7db2687) (cherry picked from commit 97f8cb01149b35b1832c7f9efe85ff19edf1083e) --- libs/androidfw/ResourceTypes.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index bdb53c36d99..de2bf6ace12 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -785,7 +785,13 @@ const char* ResStringPool::string8At(size_t idx, size_t* outLen) const *outLen = decodeLength(&str); size_t encLen = decodeLength(&str); if ((uint32_t)(str+encLen-strings) < mStringPoolSize) { - return (const char*)str; + // Reject malformed (non null-terminated) strings + if (str[encLen] != 0x00) { + ALOGW("Bad string block: string #%d is not null-terminated", + (int)idx); + return NULL; + } + return (const char*)str; } else { ALOGW("Bad string block: string #%d extends to %d, past end at %d\n", (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize); -- GitLab From 70e4d199358377f559c5232e1785d76c97b96ff6 Mon Sep 17 00:00:00 2001 From: Bowgo Tsai Date: Thu, 4 Jan 2018 16:35:10 +0800 Subject: [PATCH 006/416] Add odm sepolicy support to SELinuxMMAC.java Currently there are two mac permission files: - /system/etc/selinux/plat_mac_permissions.xml - /vendor/etc/selinux/nonplat_mac_permissions.xml The change renames nonplat_mac_permissions.xml to vendor_mac_permissions.xml. It also adds odm_mac_permissions.xml but allows it to be optional: - /system/etc/selinux/plat_mac_permissions.xml - /vendor/etc/selinux/vendor_mac_permissions.xml - /odm/etc/selinux/odm_mac_permissions.xml (optional) Also cleans up comments to reflect the change. Bug: 64240127 Test: boot sailfish normally without odm Test: boot another device having odm Change-Id: I87d01215a65e75bf33659ed03797ffda5393d5a4 --- .../com/android/server/pm/SELinuxMMAC.java | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index f0ce3c9d230..12684642c1d 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -60,10 +60,8 @@ public final class SELinuxMMAC { // to synchronize access during policy load and access attempts. private static List sPolicies = new ArrayList<>(); - /** Path to MAC permissions on system image */ - private static final File[] MAC_PERMISSIONS = - { new File(Environment.getRootDirectory(), "/etc/selinux/plat_mac_permissions.xml"), - new File(Environment.getVendorDirectory(), "/etc/selinux/nonplat_mac_permissions.xml") }; + /** Required MAC permissions files */ + private static List sMacPermissions = new ArrayList<>(); // Append privapp to existing seinfo label private static final String PRIVILEGED_APP_STR = ":privapp"; @@ -74,13 +72,40 @@ public final class SELinuxMMAC { // Append targetSdkVersion=n to existing seinfo label where n is the app's targetSdkVersion private static final String TARGETSDKVERSION_STR = ":targetSdkVersion="; + // Only initialize sMacPermissions once. + static { + // Platform mac permissions. + sMacPermissions.add(new File( + Environment.getRootDirectory(), "/etc/selinux/plat_mac_permissions.xml")); + + // Vendor mac permissions. + // The filename has been renamed from nonplat_mac_permissions to + // vendor_mac_permissions. Either of them should exist. + final File vendorMacPermission = new File( + Environment.getVendorDirectory(), "/etc/selinux/vendor_mac_permissions.xml"); + if (vendorMacPermission.exists()) { + sMacPermissions.add(vendorMacPermission); + } else { + // For backward compatibility. + sMacPermissions.add(new File(Environment.getVendorDirectory(), + "/etc/selinux/nonplat_mac_permissions.xml")); + } + + // ODM mac permissions (optional). + final File odmMacPermission = new File( + Environment.getOdmDirectory(), "/etc/selinux/odm_mac_permissions.xml"); + if (odmMacPermission.exists()) { + sMacPermissions.add(odmMacPermission); + } + } + /** * Load the mac_permissions.xml file containing all seinfo assignments used to - * label apps. The loaded mac_permissions.xml file is determined by the - * MAC_PERMISSIONS class variable which is set at class load time which itself - * is based on the USE_OVERRIDE_POLICY class variable. For further guidance on + * label apps. The loaded mac_permissions.xml files are plat_mac_permissions.xml and + * vendor_mac_permissions.xml, on /system and /vendor partitions, respectively. + * odm_mac_permissions.xml on /odm partition is optional. For further guidance on * the proper structure of a mac_permissions.xml file consult the source code - * located at system/sepolicy/mac_permissions.xml. + * located at system/sepolicy/private/mac_permissions.xml. * * @return boolean indicating if policy was correctly loaded. A value of false * typically indicates a structural problem with the xml or incorrectly @@ -93,10 +118,13 @@ public final class SELinuxMMAC { FileReader policyFile = null; XmlPullParser parser = Xml.newPullParser(); - for (int i = 0; i < MAC_PERMISSIONS.length; i++) { + + final int count = sMacPermissions.size(); + for (int i = 0; i < count; ++i) { + final File macPermission = sMacPermissions.get(i); try { - policyFile = new FileReader(MAC_PERMISSIONS[i]); - Slog.d(TAG, "Using policy file " + MAC_PERMISSIONS[i]); + policyFile = new FileReader(macPermission); + Slog.d(TAG, "Using policy file " + macPermission); parser.setInput(policyFile); parser.nextTag(); @@ -120,13 +148,13 @@ public final class SELinuxMMAC { StringBuilder sb = new StringBuilder("Exception @"); sb.append(parser.getPositionDescription()); sb.append(" while parsing "); - sb.append(MAC_PERMISSIONS[i]); + sb.append(macPermission); sb.append(":"); sb.append(ex); Slog.w(TAG, sb.toString()); return false; } catch (IOException ioe) { - Slog.w(TAG, "Exception parsing " + MAC_PERMISSIONS[i], ioe); + Slog.w(TAG, "Exception parsing " + macPermission, ioe); return false; } finally { IoUtils.closeQuietly(policyFile); -- GitLab From 242b93b7605b46fc025bc51165bef4a9e9c1d175 Mon Sep 17 00:00:00 2001 From: Hans Boehm Date: Thu, 18 Jan 2018 20:21:29 -0800 Subject: [PATCH 007/416] Add reachabilityFence, remove ExemptionMechanism.finalize ReachabilityFence is taken from Java 9. ExemptionMechanism.finalize was never useful, and is also being removed upstream. It is unsafe in the presence of dead reference elimination. Bug: 63934467 Bug: 28342794 Test: Build and boot AOSP Change-Id: I91dc4d33b91175248f44783e3c868a4f1b1d2d0d --- api/current.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/current.txt b/api/current.txt index 40452b76366..0a86fa9a22a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -56393,6 +56393,7 @@ package java.lang.ref { method public boolean enqueue(); method public T get(); method public boolean isEnqueued(); + method public static void reachabilityFence(java.lang.Object); } public class ReferenceQueue { @@ -69415,7 +69416,6 @@ package javax.crypto { public class ExemptionMechanism { ctor protected ExemptionMechanism(javax.crypto.ExemptionMechanismSpi, java.security.Provider, java.lang.String); - method protected void finalize(); method public final byte[] genExemptionBlob() throws javax.crypto.ExemptionMechanismException, java.lang.IllegalStateException; method public final int genExemptionBlob(byte[]) throws javax.crypto.ExemptionMechanismException, java.lang.IllegalStateException, javax.crypto.ShortBufferException; method public final int genExemptionBlob(byte[], int) throws javax.crypto.ExemptionMechanismException, java.lang.IllegalStateException, javax.crypto.ShortBufferException; -- GitLab From 8cbdf04eb83ec8ca62701fe5de9c7809b7a43f0c Mon Sep 17 00:00:00 2001 From: Aaron Whyte Date: Tue, 23 Jan 2018 12:06:02 -0800 Subject: [PATCH 008/416] Fix race condition when evaluating screen timeout. When deciding whether or not to honor a window's timeout override, do not require that the window has already been drawn this frame. BUG=70225679 Change-Id: I4070f465b5c57d4de08807bfe3d4e67034b56827 --- .../server/wm/RootWindowContainer.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2a77c92b5c8..c3b2c24e89e 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -897,10 +897,26 @@ class RootWindowContainer extends WindowContainer { boolean handleNotObscuredLocked(WindowState w, boolean obscured, boolean syswin) { final WindowManager.LayoutParams attrs = w.mAttrs; final int attrFlags = attrs.flags; + final boolean onScreen = w.isOnScreen(); final boolean canBeSeen = w.isDisplayedLw(); final int privateflags = attrs.privateFlags; boolean displayHasContent = false; + if (DEBUG_KEEP_SCREEN_ON) { + Slog.d(TAG_KEEP_SCREEN_ON, "handleNotObscuredLocked w: " + w + + ", w.mHasSurface: " + w.mHasSurface + + ", w.isOnScreen(): " + onScreen + + ", w.isDisplayedLw(): " + w.isDisplayedLw() + + ", w.mAttrs.userActivityTimeout: " + w.mAttrs.userActivityTimeout); + } + if (w.mHasSurface && onScreen) { + if (!syswin && w.mAttrs.userActivityTimeout >= 0 && mUserActivityTimeout < 0) { + mUserActivityTimeout = w.mAttrs.userActivityTimeout; + if (DEBUG_KEEP_SCREEN_ON) { + Slog.d(TAG, "mUserActivityTimeout set to " + mUserActivityTimeout); + } + } + } if (w.mHasSurface && canBeSeen) { if ((attrFlags & FLAG_KEEP_SCREEN_ON) != 0) { mHoldScreen = w.mSession; @@ -913,9 +929,6 @@ class RootWindowContainer extends WindowContainer { if (!syswin && w.mAttrs.screenBrightness >= 0 && mScreenBrightness < 0) { mScreenBrightness = w.mAttrs.screenBrightness; } - if (!syswin && w.mAttrs.userActivityTimeout >= 0 && mUserActivityTimeout < 0) { - mUserActivityTimeout = w.mAttrs.userActivityTimeout; - } final int type = attrs.type; // This function assumes that the contents of the default display are processed first -- GitLab From f9890f4e44198e1051672612f9542e2c2dad88fc Mon Sep 17 00:00:00 2001 From: Ricky Wai Date: Wed, 24 Jan 2018 15:10:26 +0000 Subject: [PATCH 009/416] Add Network Watchlist adb test commands - Add shell command to set temp watchlist config for (CTS/GTS) tests - Device will no longer use temp watchlist config after reboot - Temp watchlist config will be removed after reboot - Temp watchlist config will be applied on testOnly app only Bug: 63908748 Test: Able to boot Change-Id: I7dcc6637676c1871ef8fdb637d3d98d0ce78ce36 --- .../watchlist/NetworkWatchlistService.java | 23 +++++ .../NetworkWatchlistShellCommand.java | 94 +++++++++++++++++++ .../server/net/watchlist/WatchlistConfig.java | 37 +++++++- .../watchlist/WatchlistLoggingHandler.java | 30 +++++- 4 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java index 7165e600ca2..5f4e4714710 100644 --- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java +++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java @@ -23,8 +23,10 @@ import android.net.INetdEventCallback; import android.net.metrics.IpConnectivityLog; import android.os.Binder; import android.os.Process; +import android.os.ResultReceiver; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ShellCallback; import android.os.SystemProperties; import android.provider.Settings; import android.text.TextUtils; @@ -80,6 +82,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { return; } try { + mService.init(); mService.initIpConnectivityMetrics(); mService.startWatchlistLogging(); } catch (RemoteException e) { @@ -127,6 +130,10 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { mIpConnectivityMetrics = ipConnectivityMetrics; } + private void init() { + mConfig.removeTestModeConfig(); + } + private void initIpConnectivityMetrics() { mIpConnectivityMetrics = (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface( ServiceManager.getService(IpConnectivityLog.SERVICE_NAME)); @@ -151,6 +158,22 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { } }; + private boolean isCallerShell() { + final int callingUid = Binder.getCallingUid(); + return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID; + } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + if (!isCallerShell()) { + Slog.w(TAG, "Only shell is allowed to call network watchlist shell commands"); + return; + } + (new NetworkWatchlistShellCommand(mContext)).exec(this, in, out, err, args, callback, + resultReceiver); + } + @VisibleForTesting protected boolean startWatchlistLoggingImpl() throws RemoteException { if (DEBUG) { diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java new file mode 100644 index 00000000000..9533823df80 --- /dev/null +++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.net.watchlist; + +import android.content.Context; +import android.content.Intent; +import android.net.NetworkWatchlistManager; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ShellCommand; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; + +/** + * Network watchlist shell commands class, to provide a way to set temporary watchlist config for + * testing in shell, so CTS / GTS can use it to verify if watchlist feature is working properly. + */ +class NetworkWatchlistShellCommand extends ShellCommand { + + final NetworkWatchlistManager mNetworkWatchlistManager; + + NetworkWatchlistShellCommand(Context context) { + mNetworkWatchlistManager = new NetworkWatchlistManager(context); + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + + final PrintWriter pw = getOutPrintWriter(); + try { + switch(cmd) { + case "set-test-config": + return runSetTestConfig(); + default: + return handleDefaultCommands(cmd); + } + } catch (RemoteException e) { + pw.println("Remote exception: " + e); + } + return -1; + } + + /** + * Method to get fd from input xml path, and set it as temporary watchlist config. + */ + private int runSetTestConfig() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + try { + final String configXmlPath = getNextArgRequired(); + final ParcelFileDescriptor pfd = openFileForSystem(configXmlPath, "r"); + if (pfd != null) { + final InputStream fileStream = new FileInputStream(pfd.getFileDescriptor()); + WatchlistConfig.getInstance().setTestMode(fileStream); + } + pw.println("Success!"); + } catch (RuntimeException | IOException ex) { + pw.println("Error: " + ex.toString()); + return -1; + } + return 0; + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("Network watchlist manager commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(""); + pw.println(" set-test-config your_watchlist_config.xml"); + pw.println(); + Intent.printIntentArgsHelp(pw , ""); + } +} diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java b/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java index 7387ad4f90f..2714d5e5f60 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java @@ -16,6 +16,7 @@ package com.android.server.net.watchlist; +import android.os.FileUtils; import android.util.AtomicFile; import android.util.Log; import android.util.Slog; @@ -32,6 +33,7 @@ import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -50,6 +52,8 @@ class WatchlistConfig { // Watchlist config that pushed by ConfigUpdater. private static final String NETWORK_WATCHLIST_DB_PATH = "/data/misc/network_watchlist/network_watchlist.xml"; + private static final String NETWORK_WATCHLIST_DB_FOR_TEST_PATH = + "/data/misc/network_watchlist/network_watchlist_for_test.xml"; // Hash for null / unknown config, a 32 byte array filled with content 0x00 private static final byte[] UNKNOWN_CONFIG_HASH = new byte[32]; @@ -80,7 +84,7 @@ class WatchlistConfig { private boolean mIsSecureConfig = true; private final static WatchlistConfig sInstance = new WatchlistConfig(); - private final File mXmlFile; + private File mXmlFile; private volatile CrcShaDigests mDomainDigests; private volatile CrcShaDigests mIpDigests; @@ -232,7 +236,38 @@ class WatchlistConfig { return UNKNOWN_CONFIG_HASH; } + /** + * This method will copy temporary test config and temporary override network watchlist config + * in memory. When device is rebooted, temporary test config will be removed, and system will + * use back the original watchlist config. + * Also, as temporary network watchlist config is not secure, we will mark it as insecure + * config and will be applied to testOnly applications only. + */ + public void setTestMode(InputStream testConfigInputStream) throws IOException { + Log.i(TAG, "Setting watchlist testing config"); + // Copy test config + FileUtils.copyToFileOrThrow(testConfigInputStream, + new File(NETWORK_WATCHLIST_DB_FOR_TEST_PATH)); + // Mark config as insecure, so it will be applied to testOnly applications only + mIsSecureConfig = false; + // Reload watchlist config using test config file + mXmlFile = new File(NETWORK_WATCHLIST_DB_FOR_TEST_PATH); + reloadConfig(); + } + + public void removeTestModeConfig() { + try { + final File f = new File(NETWORK_WATCHLIST_DB_FOR_TEST_PATH); + if (f.exists()) { + f.delete(); + } + } catch (Exception e) { + Log.e(TAG, "Unable to delete test config"); + } + } + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("Watchlist config hash: " + HexDump.toHexString(getWatchlistConfigHash())); pw.println("Domain CRC32 digest list:"); if (mDomainDigests != null) { mDomainDigests.crc32Digests.dump(fd, pw, args); diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java index 3b6d59e178f..c4de4ac1b7d 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java @@ -118,6 +118,25 @@ class WatchlistLoggingHandler extends Handler { } /** + * Return if a given package has testOnly is true. + */ + private boolean isPackageTestOnly(int uid) { + final ApplicationInfo ai; + try { + final String[] packageNames = mPm.getPackagesForUid(uid); + if (packageNames == null || packageNames.length == 0) { + Slog.e(TAG, "Couldn't find package: " + packageNames); + return false; + } + ai = mPm.getApplicationInfo(packageNames[0],0); + } catch (NameNotFoundException e) { + // Should not happen. + return false; + } + return (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0; + } + + /** * Report network watchlist records if we collected enough data. */ public void reportWatchlistIfNecessary() { @@ -146,16 +165,21 @@ class WatchlistLoggingHandler extends Handler { } final String cncDomain = searchAllSubDomainsInWatchlist(hostname); if (cncDomain != null) { - insertRecord(getDigestFromUid(uid), cncDomain, timestamp); + insertRecord(uid, cncDomain, timestamp); } else { final String cncIp = searchIpInWatchlist(ipAddresses); if (cncIp != null) { - insertRecord(getDigestFromUid(uid), cncIp, timestamp); + insertRecord(uid, cncIp, timestamp); } } } - private boolean insertRecord(byte[] digest, String cncHost, long timestamp) { + private boolean insertRecord(int uid, String cncHost, long timestamp) { + if (!mConfig.isConfigSecure() && !isPackageTestOnly(uid)) { + // Skip package if config is not secure and package is not TestOnly app. + return true; + } + final byte[] digest = getDigestFromUid(uid); final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp); tryAggregateRecords(); return result; -- GitLab From 9b78f8254d31b03aaeb83d92be830dd2ba36787e Mon Sep 17 00:00:00 2001 From: Etan Cohen Date: Fri, 19 Jan 2018 17:21:06 -0800 Subject: [PATCH 010/416] [AWARE] Remove ability to accept connections from ANYONE Wi-Fi Aware data-path (NDP) setup requires an Initiator and a Responder. Both Initiator and a Responder are set up with the MAC address of the peer to which to connect to as well as a security configuration (Open, PMK, Passphrase). The original API (27) allowed a Responder to be configured to accept a connection from anyone (e.g. using a null MAC address). This creates ambiguous semantics when an NDP already exists. The second Responder request could refer to the previously setup NDP or to a new one to be created. We cannot tell the difference up-front. Remove the "Accept request from ANYONE" API for newer APIs. Bug: 72175022 Test: unit tests and integration tests Change-Id: I194cc15402c33c2f1c62834d64646f2489274c35 --- .../net/wifi/aware/DiscoverySession.java | 45 ++++++++++++---- .../net/wifi/aware/WifiAwareManager.java | 21 +++----- .../net/wifi/aware/WifiAwareSession.java | 31 +++++++---- .../net/wifi/aware/WifiAwareManagerTest.java | 52 +++++++++++++------ 4 files changed, 102 insertions(+), 47 deletions(-) diff --git a/wifi/java/android/net/wifi/aware/DiscoverySession.java b/wifi/java/android/net/wifi/aware/DiscoverySession.java index 9f7362234ad..699f54cf13f 100644 --- a/wifi/java/android/net/wifi/aware/DiscoverySession.java +++ b/wifi/java/android/net/wifi/aware/DiscoverySession.java @@ -22,6 +22,8 @@ import android.annotation.SystemApi; import android.net.NetworkSpecifier; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + import dalvik.system.CloseGuard; import java.lang.ref.WeakReference; @@ -141,6 +143,34 @@ public class DiscoverySession implements AutoCloseable { } } + /** + * Access the client ID of the Aware session. + * + * Note: internal visibility for testing. + * + * @return The internal client ID. + * + * @hide + */ + @VisibleForTesting + public int getClientId() { + return mClientId; + } + + /** + * Access the discovery session ID of the Aware session. + * + * Note: internal visibility for testing. + * + * @return The internal discovery session ID. + * + * @hide + */ + @VisibleForTesting + public int getSessionId() { + return mSessionId; + } + /** * Sends a message to the specified destination. Aware messages are transmitted in the context * of a discovery session - executed subsequent to a publish/subscribe @@ -246,8 +276,7 @@ public class DiscoverySession implements AutoCloseable { * or * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, byte[])}. * On a RESPONDER this value is used to gate the acceptance of a connection - * request from only that peer. A RESPONDER may specify a {@code null} - - * indicating that it will accept connection requests from any device. + * request from only that peer. * * @return A {@link NetworkSpecifier} to be used to construct * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to @@ -255,7 +284,7 @@ public class DiscoverySession implements AutoCloseable { * android.net.ConnectivityManager.NetworkCallback)} * [or other varieties of that API]. */ - public NetworkSpecifier createNetworkSpecifierOpen(@Nullable PeerHandle peerHandle) { + public NetworkSpecifier createNetworkSpecifierOpen(@NonNull PeerHandle peerHandle) { if (mTerminated) { Log.w(TAG, "createNetworkSpecifierOpen: called on terminated session"); return null; @@ -295,8 +324,7 @@ public class DiscoverySession implements AutoCloseable { * byte[], java.util.List)} or * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, * byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request - * from only that peer. A RESPONDER may specify a {@code null} - indicating - * that it will accept connection requests from any device. + * from only that peer. * @param passphrase The passphrase to be used to encrypt the link. The PMK is generated from * the passphrase. Use the * {@link #createNetworkSpecifierOpen(PeerHandle)} API to @@ -309,7 +337,7 @@ public class DiscoverySession implements AutoCloseable { * [or other varieties of that API]. */ public NetworkSpecifier createNetworkSpecifierPassphrase( - @Nullable PeerHandle peerHandle, @NonNull String passphrase) { + @NonNull PeerHandle peerHandle, @NonNull String passphrase) { if (!WifiAwareUtils.validatePassphrase(passphrase)) { throw new IllegalArgumentException("Passphrase must meet length requirements"); } @@ -354,8 +382,7 @@ public class DiscoverySession implements AutoCloseable { * byte[], java.util.List)} or * {@link DiscoverySessionCallback#onMessageReceived(PeerHandle, * byte[])}. On a RESPONDER this value is used to gate the acceptance of a connection request - * from only that peer. A RESPONDER may specify a null - indicating that - * it will accept connection requests from any device. + * from only that peer. * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for * encrypting the data-path. Use the * {@link #createNetworkSpecifierPassphrase(PeerHandle, String)} to specify a @@ -371,7 +398,7 @@ public class DiscoverySession implements AutoCloseable { * @hide */ @SystemApi - public NetworkSpecifier createNetworkSpecifierPmk(@Nullable PeerHandle peerHandle, + public NetworkSpecifier createNetworkSpecifierPmk(@NonNull PeerHandle peerHandle, @NonNull byte[] pmk) { if (!WifiAwareUtils.validatePmk(pmk)) { throw new IllegalArgumentException("PMK must 32 bytes"); diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java index 2f0c3163aa0..06a5c2e2710 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java @@ -406,7 +406,7 @@ public class WifiAwareManager { /** @hide */ public NetworkSpecifier createNetworkSpecifier(int clientId, int role, int sessionId, - PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) { + @NonNull PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) { if (VDBG) { Log.v(TAG, "createNetworkSpecifier: role=" + role + ", sessionId=" + sessionId + ", peerHandle=" + ((peerHandle == null) ? peerHandle : peerHandle.peerId) @@ -420,12 +420,9 @@ public class WifiAwareManager { "createNetworkSpecifier: Invalid 'role' argument when creating a network " + "specifier"); } - if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) { - if (peerHandle == null) { - throw new IllegalArgumentException( - "createNetworkSpecifier: Invalid peer handle (value of null) - not " - + "permitted on INITIATOR"); - } + if (peerHandle == null) { + throw new IllegalArgumentException( + "createNetworkSpecifier: Invalid peer handle - cannot be null"); } return new WifiAwareNetworkSpecifier( @@ -443,7 +440,7 @@ public class WifiAwareManager { /** @hide */ public NetworkSpecifier createNetworkSpecifier(int clientId, @DataPathRole int role, - @Nullable byte[] peer, @Nullable byte[] pmk, @Nullable String passphrase) { + @NonNull byte[] peer, @Nullable byte[] pmk, @Nullable String passphrase) { if (VDBG) { Log.v(TAG, "createNetworkSpecifier: role=" + role + ", pmk=" + ((pmk == null) ? "null" : "non-null") @@ -456,11 +453,9 @@ public class WifiAwareManager { "createNetworkSpecifier: Invalid 'role' argument when creating a network " + "specifier"); } - if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR) { - if (peer == null) { - throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC " - + "address - null not permitted on INITIATOR"); - } + if (peer == null) { + throw new IllegalArgumentException( + "createNetworkSpecifier: Invalid peer MAC - cannot be null"); } if (peer != null && peer.length != 6) { throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC address"); diff --git a/wifi/java/android/net/wifi/aware/WifiAwareSession.java b/wifi/java/android/net/wifi/aware/WifiAwareSession.java index f26b9f5aacf..321965330e0 100644 --- a/wifi/java/android/net/wifi/aware/WifiAwareSession.java +++ b/wifi/java/android/net/wifi/aware/WifiAwareSession.java @@ -25,6 +25,8 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + import dalvik.system.CloseGuard; import java.lang.ref.WeakReference; @@ -96,6 +98,20 @@ public class WifiAwareSession implements AutoCloseable { } } + /** + * Access the client ID of the Aware session. + * + * Note: internal visibility for testing. + * + * @return The internal client ID. + * + * @hide + */ + @VisibleForTesting + public int getClientId() { + return mClientId; + } + /** * Issue a request to the Aware service to create a new Aware publish discovery session, using * the specified {@code publishConfig} configuration. The results of the publish operation @@ -207,8 +223,7 @@ public class WifiAwareSession implements AutoCloseable { * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER} * @param peer The MAC address of the peer's Aware discovery interface. On a RESPONDER this * value is used to gate the acceptance of a connection request from only that - * peer. A RESPONDER may specify a {@code null} - indicating that it will accept - * connection requests from any device. + * peer. * * @return A {@link NetworkSpecifier} to be used to construct * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)} to pass to @@ -217,7 +232,7 @@ public class WifiAwareSession implements AutoCloseable { * [or other varieties of that API]. */ public NetworkSpecifier createNetworkSpecifierOpen( - @WifiAwareManager.DataPathRole int role, @Nullable byte[] peer) { + @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer) { WifiAwareManager mgr = mMgr.get(); if (mgr == null) { Log.e(TAG, "createNetworkSpecifierOpen: called post GC on WifiAwareManager"); @@ -246,8 +261,7 @@ public class WifiAwareSession implements AutoCloseable { * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER} * @param peer The MAC address of the peer's Aware discovery interface. On a RESPONDER this * value is used to gate the acceptance of a connection request from only that - * peer. A RESPONDER may specify a {@code null} - indicating that it will accept - * connection requests from any device. + * peer. * @param passphrase The passphrase to be used to encrypt the link. The PMK is generated from * the passphrase. Use {@link #createNetworkSpecifierOpen(int, byte[])} to * specify an open (unencrypted) link. @@ -259,7 +273,7 @@ public class WifiAwareSession implements AutoCloseable { * [or other varieties of that API]. */ public NetworkSpecifier createNetworkSpecifierPassphrase( - @WifiAwareManager.DataPathRole int role, @Nullable byte[] peer, + @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer, @NonNull String passphrase) { WifiAwareManager mgr = mMgr.get(); if (mgr == null) { @@ -293,8 +307,7 @@ public class WifiAwareSession implements AutoCloseable { * {@link WifiAwareManager#WIFI_AWARE_DATA_PATH_ROLE_RESPONDER} * @param peer The MAC address of the peer's Aware discovery interface. On a RESPONDER this * value is used to gate the acceptance of a connection request from only that - * peer. A RESPONDER may specify a null - indicating that it will accept - * connection requests from any device. + * peer. * @param pmk A PMK (pairwise master key, see IEEE 802.11i) specifying the key to use for * encrypting the data-path. Use the * {@link #createNetworkSpecifierPassphrase(int, byte[], String)} to specify a @@ -311,7 +324,7 @@ public class WifiAwareSession implements AutoCloseable { */ @SystemApi public NetworkSpecifier createNetworkSpecifierPmk( - @WifiAwareManager.DataPathRole int role, @Nullable byte[] peer, @NonNull byte[] pmk) { + @WifiAwareManager.DataPathRole int role, @NonNull byte[] peer, @NonNull byte[] pmk) { WifiAwareManager mgr = mMgr.get(); if (mgr == null) { Log.e(TAG, "createNetworkSpecifierPmk: called post GC on WifiAwareManager"); diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java index 9cab66a240a..dbca0229526 100644 --- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java +++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java @@ -1022,7 +1022,7 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientNullPmk() throws Exception { - executeNetworkSpecifierWithClient(true, null, null); + executeNetworkSpecifierWithClient(new PeerHandle(123412), true, null, null); } /** @@ -1030,7 +1030,7 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientIncorrectLengthPmk() throws Exception { - executeNetworkSpecifierWithClient(true, "012".getBytes(), null); + executeNetworkSpecifierWithClient(new PeerHandle(123412), true, "012".getBytes(), null); } /** @@ -1038,7 +1038,7 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientNullPassphrase() throws Exception { - executeNetworkSpecifierWithClient(false, null, null); + executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, null); } /** @@ -1046,7 +1046,7 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientShortPassphrase() throws Exception { - executeNetworkSpecifierWithClient(false, null, "012"); + executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, "012"); } /** @@ -1054,15 +1054,23 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierWithClientLongPassphrase() throws Exception { - executeNetworkSpecifierWithClient(false, null, + executeNetworkSpecifierWithClient(new PeerHandle(123412), false, null, "0123456789012345678901234567890123456789012345678901234567890123456789"); } - private void executeNetworkSpecifierWithClient(boolean doPmk, byte[] pmk, String passphrase) - throws Exception { + /** + * Validate that a null PeerHandle triggers an exception. + */ + @Test(expected = IllegalArgumentException.class) + public void testNetworkSpecifierWithClientNullPeer() throws Exception { + executeNetworkSpecifierWithClient(null, false, null, + "0123456789012345678901234567890123456789012345678901234567890123456789"); + } + + private void executeNetworkSpecifierWithClient(PeerHandle peerHandle, boolean doPmk, byte[] pmk, + String passphrase) throws Exception { final int clientId = 4565; final int sessionId = 123; - final PeerHandle peerHandle = new PeerHandle(123412); final ConfigRequest configRequest = new ConfigRequest.Builder().build(); final PublishConfig publishConfig = new PublishConfig.Builder().build(); @@ -1108,7 +1116,8 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierDirectNullPmk() throws Exception { - executeNetworkSpecifierDirect(true, null, null); + executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false), true, + null, null); } /** @@ -1116,7 +1125,8 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierDirectIncorrectLengthPmk() throws Exception { - executeNetworkSpecifierDirect(true, "012".getBytes(), null); + executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false), true, + "012".getBytes(), null); } /** @@ -1124,7 +1134,8 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierDirectNullPassphrase() throws Exception { - executeNetworkSpecifierDirect(false, null, null); + executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false), + false, null, null); } /** @@ -1132,7 +1143,8 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierDirectShortPassphrase() throws Exception { - executeNetworkSpecifierDirect(false, null, "012"); + executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false), + false, null, "012"); } /** @@ -1140,14 +1152,22 @@ public class WifiAwareManagerTest { */ @Test(expected = IllegalArgumentException.class) public void testNetworkSpecifierDirectLongPassphrase() throws Exception { - executeNetworkSpecifierDirect(false, null, + executeNetworkSpecifierDirect(HexEncoding.decode("000102030405".toCharArray(), false), + false, null, "0123456789012345678901234567890123456789012345678901234567890123456789"); } - private void executeNetworkSpecifierDirect(boolean doPmk, byte[] pmk, String passphrase) - throws Exception { + /** + * Validate that a null peer MAC triggers an exception. + */ + @Test(expected = IllegalArgumentException.class) + public void testNetworkSpecifierDirectNullPeer() throws Exception { + executeNetworkSpecifierDirect(null, false, null, null); + } + + private void executeNetworkSpecifierDirect(byte[] someMac, boolean doPmk, byte[] pmk, + String passphrase) throws Exception { final int clientId = 134; - final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false); final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR; final ConfigRequest configRequest = new ConfigRequest.Builder().build(); -- GitLab From c1ef447548239a26de1d6fa124a180e27b0db44c Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Mon, 22 Jan 2018 21:36:07 -0800 Subject: [PATCH 011/416] OMS: Migrate device tests to atest Test: atest OverlayDeviceTests Change-Id: Ie1040ee45e4ffef060a4e21257b0a61fd4f4c660 --- core/tests/overlaytests/device/Android.mk | 31 + .../overlaytests/device/AndroidManifest.xml | 8 +- .../tests/overlaytests/device/AndroidTest.xml | 47 ++ .../device/OverlayAppFiltered/Android.mk | 10 - .../OverlayAppFiltered/AndroidManifest.xml | 9 - .../res/raw/lorem_ipsum.txt | 1 - .../OverlayAppFiltered/res/values/config.xml | 4 - .../OverlayAppFiltered/res/xml/integer.xml | 2 - .../device/OverlayAppFirst/Android.mk | 10 - .../OverlayAppFirst/AndroidManifest.xml | 6 - .../device/OverlayAppSecond/Android.mk | 10 - .../OverlayAppSecond/AndroidManifest.xml | 6 - .../device/OverlayTest/Android.mk | 16 - .../device/OverlayTest/AndroidManifest.xml | 10 - .../overlaytest/WithMultipleOverlaysTest.java | 7 - .../android/overlaytest/WithOverlayTest.java | 7 - .../overlaytest/WithoutOverlayTest.java | 7 - .../device/OverlayTestOverlay/Android.mk | 10 - .../OverlayTestOverlay/AndroidManifest.xml | 6 - .../res/drawable-nodpi/drawable.jpg | Bin .../{OverlayTest => }/res/raw/lorem_ipsum.txt | 0 .../res/values-sv/config.xml | 0 .../{OverlayTest => }/res/values/config.xml | 0 .../{OverlayTest => }/res/xml/integer.xml | 0 .../android/overlaytest/OverlayBaseTest.java | 171 +++- .../overlaytest/WithMultipleOverlaysTest.java | 36 + .../android/overlaytest/WithOverlayTest.java | 37 + .../overlaytest/WithoutOverlayTest.java | 36 + .../overlaytests/device/test-apps/Android.mk | 15 + .../device/test-apps/AppOverlayOne/Android.mk | 22 + .../AppOverlayOne/AndroidManifest.xml | 22 + .../res/drawable-nodpi/drawable.jpg | Bin .../AppOverlayOne}/res/raw/lorem_ipsum.txt | 0 .../AppOverlayOne}/res/values-sv/config.xml | 0 .../AppOverlayOne}/res/values/config.xml | 0 .../AppOverlayOne}/res/xml/integer.xml | 0 .../device/test-apps/AppOverlayTwo/Android.mk | 22 + .../AppOverlayTwo/AndroidManifest.xml | 22 + .../AppOverlayTwo}/res/raw/lorem_ipsum.txt | 0 .../AppOverlayTwo}/res/values-sv/config.xml | 0 .../AppOverlayTwo}/res/values/config.xml | 0 .../AppOverlayTwo}/res/xml/integer.xml | 0 .../test-apps/FrameworkOverlay/Android.mk | 22 + .../FrameworkOverlay/AndroidManifest.xml | 22 + .../FrameworkOverlay}/res/values/config.xml | 0 .../SignatureOverlay/AndroidManifest.xml | 2 +- core/tests/overlaytests/testrunner.py | 732 ------------------ 47 files changed, 491 insertions(+), 877 deletions(-) create mode 100644 core/tests/overlaytests/device/Android.mk create mode 100644 core/tests/overlaytests/device/AndroidTest.xml delete mode 100644 core/tests/overlaytests/device/OverlayAppFiltered/Android.mk delete mode 100644 core/tests/overlaytests/device/OverlayAppFiltered/AndroidManifest.xml delete mode 100644 core/tests/overlaytests/device/OverlayAppFiltered/res/raw/lorem_ipsum.txt delete mode 100644 core/tests/overlaytests/device/OverlayAppFiltered/res/values/config.xml delete mode 100644 core/tests/overlaytests/device/OverlayAppFiltered/res/xml/integer.xml delete mode 100644 core/tests/overlaytests/device/OverlayAppFirst/Android.mk delete mode 100644 core/tests/overlaytests/device/OverlayAppFirst/AndroidManifest.xml delete mode 100644 core/tests/overlaytests/device/OverlayAppSecond/Android.mk delete mode 100644 core/tests/overlaytests/device/OverlayAppSecond/AndroidManifest.xml delete mode 100644 core/tests/overlaytests/device/OverlayTest/Android.mk delete mode 100644 core/tests/overlaytests/device/OverlayTest/AndroidManifest.xml delete mode 100644 core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java delete mode 100644 core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java delete mode 100644 core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java delete mode 100644 core/tests/overlaytests/device/OverlayTestOverlay/Android.mk delete mode 100644 core/tests/overlaytests/device/OverlayTestOverlay/AndroidManifest.xml rename core/tests/overlaytests/device/{OverlayTest => }/res/drawable-nodpi/drawable.jpg (100%) rename core/tests/overlaytests/device/{OverlayTest => }/res/raw/lorem_ipsum.txt (100%) rename core/tests/overlaytests/device/{OverlayTest => }/res/values-sv/config.xml (100%) rename core/tests/overlaytests/device/{OverlayTest => }/res/values/config.xml (100%) rename core/tests/overlaytests/device/{OverlayTest => }/res/xml/integer.xml (100%) rename core/tests/overlaytests/device/{OverlayTest => }/src/com/android/overlaytest/OverlayBaseTest.java (76%) create mode 100644 core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java create mode 100644 core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java create mode 100644 core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java create mode 100644 core/tests/overlaytests/device/test-apps/Android.mk create mode 100644 core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.mk create mode 100644 core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml rename core/tests/overlaytests/device/{OverlayAppFirst => test-apps/AppOverlayOne}/res/drawable-nodpi/drawable.jpg (100%) rename core/tests/overlaytests/device/{OverlayAppFirst => test-apps/AppOverlayOne}/res/raw/lorem_ipsum.txt (100%) rename core/tests/overlaytests/device/{OverlayAppFirst => test-apps/AppOverlayOne}/res/values-sv/config.xml (100%) rename core/tests/overlaytests/device/{OverlayAppFirst => test-apps/AppOverlayOne}/res/values/config.xml (100%) rename core/tests/overlaytests/device/{OverlayAppFirst => test-apps/AppOverlayOne}/res/xml/integer.xml (100%) create mode 100644 core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.mk create mode 100644 core/tests/overlaytests/device/test-apps/AppOverlayTwo/AndroidManifest.xml rename core/tests/overlaytests/device/{OverlayAppSecond => test-apps/AppOverlayTwo}/res/raw/lorem_ipsum.txt (100%) rename core/tests/overlaytests/device/{OverlayAppSecond => test-apps/AppOverlayTwo}/res/values-sv/config.xml (100%) rename core/tests/overlaytests/device/{OverlayAppSecond => test-apps/AppOverlayTwo}/res/values/config.xml (100%) rename core/tests/overlaytests/device/{OverlayAppSecond => test-apps/AppOverlayTwo}/res/xml/integer.xml (100%) create mode 100644 core/tests/overlaytests/device/test-apps/FrameworkOverlay/Android.mk create mode 100644 core/tests/overlaytests/device/test-apps/FrameworkOverlay/AndroidManifest.xml rename core/tests/overlaytests/device/{OverlayTestOverlay => test-apps/FrameworkOverlay}/res/values/config.xml (100%) delete mode 100755 core/tests/overlaytests/testrunner.py diff --git a/core/tests/overlaytests/device/Android.mk b/core/tests/overlaytests/device/Android.mk new file mode 100644 index 00000000000..4ca3e4ce238 --- /dev/null +++ b/core/tests/overlaytests/device/Android.mk @@ -0,0 +1,31 @@ +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := OverlayDeviceTests +LOCAL_STATIC_JAVA_LIBRARIES := android-support-test +LOCAL_COMPATIBILITY_SUITE := device-tests +LOCAL_TARGET_REQUIRED_MODULES := \ + OverlayDeviceTests_AppOverlayOne \ + OverlayDeviceTests_AppOverlayTwo \ + OverlayDeviceTests_FrameworkOverlay +include $(BUILD_PACKAGE) + +# Include to build test-apps. +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/core/tests/overlaytests/device/AndroidManifest.xml b/core/tests/overlaytests/device/AndroidManifest.xml index e01caeedd86..d14fdf5ee81 100644 --- a/core/tests/overlaytests/device/AndroidManifest.xml +++ b/core/tests/overlaytests/device/AndroidManifest.xml @@ -15,15 +15,15 @@ --> + package="com.android.overlaytest"> - + - + android:targetPackage="com.android.overlaytest" + android:label="Runtime resource overlay tests" /> diff --git a/core/tests/overlaytests/device/AndroidTest.xml b/core/tests/overlaytests/device/AndroidTest.xml new file mode 100644 index 00000000000..f0698355983 --- /dev/null +++ b/core/tests/overlaytests/device/AndroidTest.xml @@ -0,0 +1,47 @@ + + + + + diff --git a/core/tests/overlaytests/device/OverlayAppFiltered/Android.mk b/core/tests/overlaytests/device/OverlayAppFiltered/Android.mk deleted file mode 100644 index f76de7a93b2..00000000000 --- a/core/tests/overlaytests/device/OverlayAppFiltered/Android.mk +++ /dev/null @@ -1,10 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_SDK_VERSION := system_current - -LOCAL_PACKAGE_NAME := com.android.overlaytest.filtered_app_overlay - -include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/device/OverlayAppFiltered/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayAppFiltered/AndroidManifest.xml deleted file mode 100644 index 5b7950a25fb..00000000000 --- a/core/tests/overlaytests/device/OverlayAppFiltered/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/core/tests/overlaytests/device/OverlayAppFiltered/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayAppFiltered/res/raw/lorem_ipsum.txt deleted file mode 100644 index 0954cedeb5d..00000000000 --- a/core/tests/overlaytests/device/OverlayAppFiltered/res/raw/lorem_ipsum.txt +++ /dev/null @@ -1 +0,0 @@ -Lorem ipsum: filtered overlays. diff --git a/core/tests/overlaytests/device/OverlayAppFiltered/res/values/config.xml b/core/tests/overlaytests/device/OverlayAppFiltered/res/values/config.xml deleted file mode 100644 index 60b94eec599..00000000000 --- a/core/tests/overlaytests/device/OverlayAppFiltered/res/values/config.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - filtered - diff --git a/core/tests/overlaytests/device/OverlayAppFiltered/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayAppFiltered/res/xml/integer.xml deleted file mode 100644 index e2652b7e291..00000000000 --- a/core/tests/overlaytests/device/OverlayAppFiltered/res/xml/integer.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/core/tests/overlaytests/device/OverlayAppFirst/Android.mk b/core/tests/overlaytests/device/OverlayAppFirst/Android.mk deleted file mode 100644 index bf9416c279b..00000000000 --- a/core/tests/overlaytests/device/OverlayAppFirst/Android.mk +++ /dev/null @@ -1,10 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_SDK_VERSION := current - -LOCAL_PACKAGE_NAME := com.android.overlaytest.first_app_overlay - -include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/device/OverlayAppFirst/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayAppFirst/AndroidManifest.xml deleted file mode 100644 index ec10bbcf752..00000000000 --- a/core/tests/overlaytests/device/OverlayAppFirst/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/core/tests/overlaytests/device/OverlayAppSecond/Android.mk b/core/tests/overlaytests/device/OverlayAppSecond/Android.mk deleted file mode 100644 index bb7d142d680..00000000000 --- a/core/tests/overlaytests/device/OverlayAppSecond/Android.mk +++ /dev/null @@ -1,10 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_SDK_VERSION := current - -LOCAL_PACKAGE_NAME := com.android.overlaytest.second_app_overlay - -include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/device/OverlayAppSecond/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayAppSecond/AndroidManifest.xml deleted file mode 100644 index ed498637b45..00000000000 --- a/core/tests/overlaytests/device/OverlayAppSecond/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/core/tests/overlaytests/device/OverlayTest/Android.mk b/core/tests/overlaytests/device/OverlayTest/Android.mk deleted file mode 100644 index 5fe7b917102..00000000000 --- a/core/tests/overlaytests/device/OverlayTest/Android.mk +++ /dev/null @@ -1,16 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_PACKAGE_NAME := OverlayTest - -LOCAL_DEX_PREOPT := false - -LOCAL_JAVA_LIBRARIES += android.test.base - -LOCAL_MODULE_PATH := $(TARGET_OUT)/app - -LOCAL_SRC_FILES := $(call all-java-files-under, src) - -include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/device/OverlayTest/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayTest/AndroidManifest.xml deleted file mode 100644 index 9edba12ffa8..00000000000 --- a/core/tests/overlaytests/device/OverlayTest/AndroidManifest.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java deleted file mode 100644 index e104f5a670c..00000000000 --- a/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.android.overlaytest; - -public class WithMultipleOverlaysTest extends OverlayBaseTest { - public WithMultipleOverlaysTest() { - mMode = MODE_MULTIPLE_OVERLAYS; - } -} diff --git a/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java deleted file mode 100644 index 816a476e28c..00000000000 --- a/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.android.overlaytest; - -public class WithOverlayTest extends OverlayBaseTest { - public WithOverlayTest() { - mMode = MODE_SINGLE_OVERLAY; - } -} diff --git a/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java deleted file mode 100644 index 318cccc8546..00000000000 --- a/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.android.overlaytest; - -public class WithoutOverlayTest extends OverlayBaseTest { - public WithoutOverlayTest() { - mMode = MODE_NO_OVERLAY; - } -} diff --git a/core/tests/overlaytests/device/OverlayTestOverlay/Android.mk b/core/tests/overlaytests/device/OverlayTestOverlay/Android.mk deleted file mode 100644 index ed330467f68..00000000000 --- a/core/tests/overlaytests/device/OverlayTestOverlay/Android.mk +++ /dev/null @@ -1,10 +0,0 @@ -LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE_TAGS := tests - -LOCAL_SDK_VERSION := current - -LOCAL_PACKAGE_NAME := com.android.overlaytest.overlay - -include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/device/OverlayTestOverlay/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayTestOverlay/AndroidManifest.xml deleted file mode 100644 index f8b6c7b888b..00000000000 --- a/core/tests/overlaytests/device/OverlayTestOverlay/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/core/tests/overlaytests/device/OverlayTest/res/drawable-nodpi/drawable.jpg b/core/tests/overlaytests/device/res/drawable-nodpi/drawable.jpg similarity index 100% rename from core/tests/overlaytests/device/OverlayTest/res/drawable-nodpi/drawable.jpg rename to core/tests/overlaytests/device/res/drawable-nodpi/drawable.jpg diff --git a/core/tests/overlaytests/device/OverlayTest/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/res/raw/lorem_ipsum.txt similarity index 100% rename from core/tests/overlaytests/device/OverlayTest/res/raw/lorem_ipsum.txt rename to core/tests/overlaytests/device/res/raw/lorem_ipsum.txt diff --git a/core/tests/overlaytests/device/OverlayTest/res/values-sv/config.xml b/core/tests/overlaytests/device/res/values-sv/config.xml similarity index 100% rename from core/tests/overlaytests/device/OverlayTest/res/values-sv/config.xml rename to core/tests/overlaytests/device/res/values-sv/config.xml diff --git a/core/tests/overlaytests/device/OverlayTest/res/values/config.xml b/core/tests/overlaytests/device/res/values/config.xml similarity index 100% rename from core/tests/overlaytests/device/OverlayTest/res/values/config.xml rename to core/tests/overlaytests/device/res/values/config.xml diff --git a/core/tests/overlaytests/device/OverlayTest/res/xml/integer.xml b/core/tests/overlaytests/device/res/xml/integer.xml similarity index 100% rename from core/tests/overlaytests/device/OverlayTest/res/xml/integer.xml rename to core/tests/overlaytests/device/res/xml/integer.xml diff --git a/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java similarity index 76% rename from core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java rename to core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java index e57c55ced04..96ab977b799 100644 --- a/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java @@ -1,45 +1,80 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.overlaytest; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import android.app.UiAutomation; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.XmlResourceParser; -import android.test.AndroidTestCase; +import android.os.LocaleList; +import android.os.ParcelFileDescriptor; +import android.support.test.InstrumentationRegistry; import android.util.AttributeSet; import android.util.Xml; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.Locale; -public abstract class OverlayBaseTest extends AndroidTestCase { +@Ignore +public abstract class OverlayBaseTest { private Resources mResources; - protected int mMode; // will be set by subclasses - static final protected int MODE_NO_OVERLAY = 0; - static final protected int MODE_SINGLE_OVERLAY = 1; - static final protected int MODE_MULTIPLE_OVERLAYS = 2; + private final int mMode; + static final int MODE_NO_OVERLAY = 0; + static final int MODE_SINGLE_OVERLAY = 1; + static final int MODE_MULTIPLE_OVERLAYS = 2; + + static final String APP_OVERLAY_ONE_PKG = "com.android.overlaytest.app_overlay_one"; + static final String APP_OVERLAY_TWO_PKG = "com.android.overlaytest.app_overlay_two"; + static final String FRAMEWORK_OVERLAY_PKG = "com.android.overlaytest.framework"; + + protected OverlayBaseTest(int mode) { + mMode = mode; + } - protected void setUp() { - mResources = getContext().getResources(); + @Before + public void setUp() { + mResources = InstrumentationRegistry.getContext().getResources(); } private int calculateRawResourceChecksum(int resId) throws Throwable { - InputStream input = null; - try { - input = mResources.openRawResource(resId); + try (InputStream input = mResources.openRawResource(resId)) { int ch, checksum = 0; while ((ch = input.read()) != -1) { checksum = (checksum + ch) % 0xffddbb00; } return checksum; - } finally { - input.close(); } } private void setLocale(Locale locale) { - Locale.setDefault(locale); + final LocaleList locales = new LocaleList(locale); + LocaleList.setDefault(locales); Configuration config = new Configuration(); - config.locale = locale; + config.setLocales(locales); mResources.updateConfiguration(config, mResources.getDisplayMetrics()); } @@ -126,6 +161,7 @@ public abstract class OverlayBaseTest extends AndroidTestCase { } } + @Test public void testFrameworkBooleanOverlay() throws Throwable { // config_annoy_dianne has the value: // - true when no overlay exists (MODE_NO_OVERLAY) @@ -135,6 +171,7 @@ public abstract class OverlayBaseTest extends AndroidTestCase { assertResource(resId, true, false, false); } + @Test public void testBooleanOverlay() throws Throwable { // usually_false has the value: // - false when no overlay exists (MODE_NO_OVERLAY) @@ -144,12 +181,14 @@ public abstract class OverlayBaseTest extends AndroidTestCase { assertResource(resId, false, true, false); } + @Test public void testBoolean() throws Throwable { // always_true has no overlay final int resId = R.bool.always_true; assertResource(resId, true, true, true); } + @Test public void testIntegerArrayOverlay() throws Throwable { // fibonacci has values: // - eight first values of Fibonacci sequence, when no overlay exists (MODE_NO_OVERLAY) @@ -162,6 +201,7 @@ public abstract class OverlayBaseTest extends AndroidTestCase { new int[]{21, 13, 8, 5, 3, 2, 1, 1}); } + @Test public void testIntegerArray() throws Throwable { // prime_numbers has no overlay final int resId = R.array.prime_numbers; @@ -169,6 +209,7 @@ public abstract class OverlayBaseTest extends AndroidTestCase { assertResource(resId, expected, expected, expected); } + @Test public void testDrawable() throws Throwable { // drawable-nodpi/drawable has overlay (default config) final int resId = R.drawable.drawable; @@ -188,16 +229,19 @@ public abstract class OverlayBaseTest extends AndroidTestCase { assertEquals(expected, actual); } + @Test public void testAppString() throws Throwable { final int resId = R.string.str; assertResource(resId, "none", "single", "multiple"); } + @Test public void testApp2() throws Throwable { final int resId = R.string.str2; // only in base package and first app overlay assertResource(resId, "none", "single", "single"); } + @Test public void testAppXml() throws Throwable { int expected = getExpected(0, 1, 2); int actual = -1; @@ -214,6 +258,7 @@ public abstract class OverlayBaseTest extends AndroidTestCase { assertEquals(expected, actual); } + @Test public void testAppRaw() throws Throwable { final int resId = R.raw.lorem_ipsum; @@ -256,10 +301,10 @@ public abstract class OverlayBaseTest extends AndroidTestCase { * SLOT PACKAGE CONFIGURATION VALUE * A target package (default) 100 * B target package -sv 200 - * C OverlayAppFirst (default) 300 - * D OverlayAppFirst -sv 400 - * E OverlayAppSecond (default) 500 - * F OverlayAppSecond -sv 600 + * C AppOverlayOne (default) 300 + * D AppOverlayOne -sv 400 + * E AppOverlayTwo (default) 500 + * F AppOverlayTwo -sv 600 * * Example: in testMatrix101110, the base package defines the * R.integer.matrix101110 resource for the default configuration (value @@ -269,195 +314,283 @@ public abstract class OverlayBaseTest extends AndroidTestCase { * are loaded, the expected value after setting the language to Swedish is * 400. */ + @Test public void testMatrix100000() throws Throwable { final int resId = R.integer.matrix_100000; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 100, 100); } + @Test public void testMatrix100001() throws Throwable { final int resId = R.integer.matrix_100001; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 100, 600); } + @Test public void testMatrix100010() throws Throwable { final int resId = R.integer.matrix_100010; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 100, 500); } + @Test public void testMatrix100011() throws Throwable { final int resId = R.integer.matrix_100011; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 100, 600); } + @Test public void testMatrix100100() throws Throwable { final int resId = R.integer.matrix_100100; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 400); } + @Test public void testMatrix100101() throws Throwable { final int resId = R.integer.matrix_100101; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 600); } + @Test public void testMatrix100110() throws Throwable { final int resId = R.integer.matrix_100110; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 400); } + @Test public void testMatrix100111() throws Throwable { final int resId = R.integer.matrix_100111; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 600); } + @Test public void testMatrix101000() throws Throwable { final int resId = R.integer.matrix_101000; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 300, 300); } + @Test public void testMatrix101001() throws Throwable { final int resId = R.integer.matrix_101001; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 300, 600); } + @Test public void testMatrix101010() throws Throwable { final int resId = R.integer.matrix_101010; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 300, 500); } + @Test public void testMatrix101011() throws Throwable { final int resId = R.integer.matrix_101011; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 300, 600); } + @Test public void testMatrix101100() throws Throwable { final int resId = R.integer.matrix_101100; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 400); } + @Test public void testMatrix101101() throws Throwable { final int resId = R.integer.matrix_101101; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 600); } + @Test public void testMatrix101110() throws Throwable { final int resId = R.integer.matrix_101110; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 400); } + @Test public void testMatrix101111() throws Throwable { final int resId = R.integer.matrix_101111; setLocale(new Locale("sv", "SE")); assertResource(resId, 100, 400, 600); } + @Test public void testMatrix110000() throws Throwable { final int resId = R.integer.matrix_110000; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 200); } + @Test public void testMatrix110001() throws Throwable { final int resId = R.integer.matrix_110001; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 600); } + @Test public void testMatrix110010() throws Throwable { final int resId = R.integer.matrix_110010; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 200); } + @Test public void testMatrix110011() throws Throwable { final int resId = R.integer.matrix_110011; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 600); } + @Test public void testMatrix110100() throws Throwable { final int resId = R.integer.matrix_110100; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 400); } + @Test public void testMatrix110101() throws Throwable { final int resId = R.integer.matrix_110101; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 600); } + @Test public void testMatrix110110() throws Throwable { final int resId = R.integer.matrix_110110; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 400); } + @Test public void testMatrix110111() throws Throwable { final int resId = R.integer.matrix_110111; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 600); } + @Test public void testMatrix111000() throws Throwable { final int resId = R.integer.matrix_111000; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 200); } + @Test public void testMatrix111001() throws Throwable { final int resId = R.integer.matrix_111001; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 600); } + @Test public void testMatrix111010() throws Throwable { final int resId = R.integer.matrix_111010; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 200); } + @Test public void testMatrix111011() throws Throwable { final int resId = R.integer.matrix_111011; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 200, 600); } + @Test public void testMatrix111100() throws Throwable { final int resId = R.integer.matrix_111100; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 400); } + @Test public void testMatrix111101() throws Throwable { final int resId = R.integer.matrix_111101; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 600); } + @Test public void testMatrix111110() throws Throwable { final int resId = R.integer.matrix_111110; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 400); } + @Test public void testMatrix111111() throws Throwable { final int resId = R.integer.matrix_111111; setLocale(new Locale("sv", "SE")); assertResource(resId, 200, 400, 600); } + + /** + * Executes the shell command and reads all the output to ensure the command ran and didn't + * get stuck buffering on output. + */ + protected static String executeShellCommand(UiAutomation automation, String command) + throws Exception { + final ParcelFileDescriptor pfd = automation.executeShellCommand(command); + try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { + final BufferedReader reader = new BufferedReader( + new InputStreamReader(in, StandardCharsets.UTF_8)); + StringBuilder str = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + str.append(line); + } + return str.toString(); + } + } + + /** + * Enables overlay packages and waits for a configuration change event before + * returning, to guarantee that Resources are up-to-date. + * @param packages the list of package names to enable. + */ + protected static void enableOverlayPackages(String... packages) throws Exception { + enableOverlayPackages(true, packages); + } + + /** + * Disables overlay packages and waits for a configuration change event before + * returning, to guarantee that Resources are up-to-date. + * @param packages the list of package names to disable. + */ + protected static void disableOverlayPackages(String... packages) throws Exception { + enableOverlayPackages(false, packages); + } + + /** + * Enables/disables overlay packages and waits for a configuration change event before + * returning, to guarantee that Resources are up-to-date. + * @param enable enables the overlays when true, disables when false. + * @param packages the list of package names to enable/disable. + */ + private static void enableOverlayPackages(boolean enable, String[] packages) + throws Exception { + final UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation() + .getUiAutomation(); + for (final String pkg : packages) { + executeShellCommand(uiAutomation, + "cmd overlay " + (enable ? "enable " : "disable ") + pkg); + } + + // Wait for the overlay change to propagate. + Thread.sleep(1000); + } } diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java new file mode 100644 index 00000000000..f35e511cdcf --- /dev/null +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.overlaytest; + +import android.support.test.filters.MediumTest; + +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@MediumTest +public class WithMultipleOverlaysTest extends OverlayBaseTest { + public WithMultipleOverlaysTest() { + super(MODE_MULTIPLE_OVERLAYS); + } + + @BeforeClass + public static void enableOverlay() throws Exception { + enableOverlayPackages(APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG, FRAMEWORK_OVERLAY_PKG); + } +} diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java new file mode 100644 index 00000000000..037449fc2c0 --- /dev/null +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.overlaytest; + +import android.support.test.filters.MediumTest; + +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@MediumTest +public class WithOverlayTest extends OverlayBaseTest { + public WithOverlayTest() { + super(MODE_SINGLE_OVERLAY); + } + + @BeforeClass + public static void enableOverlay() throws Exception { + disableOverlayPackages(APP_OVERLAY_TWO_PKG); + enableOverlayPackages(APP_OVERLAY_ONE_PKG, FRAMEWORK_OVERLAY_PKG); + } +} diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java new file mode 100644 index 00000000000..f657b5cef0e --- /dev/null +++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.overlaytest; + +import android.support.test.filters.MediumTest; + +import org.junit.BeforeClass; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +@MediumTest +public class WithoutOverlayTest extends OverlayBaseTest { + public WithoutOverlayTest() { + super(MODE_NO_OVERLAY); + } + + @BeforeClass + public static void disableOverlays() throws Exception { + disableOverlayPackages(APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG, FRAMEWORK_OVERLAY_PKG); + } +} diff --git a/core/tests/overlaytests/device/test-apps/Android.mk b/core/tests/overlaytests/device/test-apps/Android.mk new file mode 100644 index 00000000000..9af9f444ca5 --- /dev/null +++ b/core/tests/overlaytests/device/test-apps/Android.mk @@ -0,0 +1,15 @@ +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include $(call all-subdir-makefiles) diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.mk b/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.mk new file mode 100644 index 00000000000..17e20eeeda8 --- /dev/null +++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.mk @@ -0,0 +1,22 @@ +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := OverlayDeviceTests_AppOverlayOne +LOCAL_COMPATIBILITY_SUITE := device-tests +LOCAL_CERTIFICATE := platform +include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml new file mode 100644 index 00000000000..17191589e3f --- /dev/null +++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/core/tests/overlaytests/device/OverlayAppFirst/res/drawable-nodpi/drawable.jpg b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/drawable-nodpi/drawable.jpg similarity index 100% rename from core/tests/overlaytests/device/OverlayAppFirst/res/drawable-nodpi/drawable.jpg rename to core/tests/overlaytests/device/test-apps/AppOverlayOne/res/drawable-nodpi/drawable.jpg diff --git a/core/tests/overlaytests/device/OverlayAppFirst/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/raw/lorem_ipsum.txt similarity index 100% rename from core/tests/overlaytests/device/OverlayAppFirst/res/raw/lorem_ipsum.txt rename to core/tests/overlaytests/device/test-apps/AppOverlayOne/res/raw/lorem_ipsum.txt diff --git a/core/tests/overlaytests/device/OverlayAppFirst/res/values-sv/config.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/values-sv/config.xml similarity index 100% rename from core/tests/overlaytests/device/OverlayAppFirst/res/values-sv/config.xml rename to core/tests/overlaytests/device/test-apps/AppOverlayOne/res/values-sv/config.xml diff --git a/core/tests/overlaytests/device/OverlayAppFirst/res/values/config.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/values/config.xml similarity index 100% rename from core/tests/overlaytests/device/OverlayAppFirst/res/values/config.xml rename to core/tests/overlaytests/device/test-apps/AppOverlayOne/res/values/config.xml diff --git a/core/tests/overlaytests/device/OverlayAppFirst/res/xml/integer.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/integer.xml similarity index 100% rename from core/tests/overlaytests/device/OverlayAppFirst/res/xml/integer.xml rename to core/tests/overlaytests/device/test-apps/AppOverlayOne/res/xml/integer.xml diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.mk b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.mk new file mode 100644 index 00000000000..c24bea9e06e --- /dev/null +++ b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.mk @@ -0,0 +1,22 @@ +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := OverlayDeviceTests_AppOverlayTwo +LOCAL_COMPATIBILITY_SUITE := device-tests +LOCAL_CERTIFICATE := platform +include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/AndroidManifest.xml b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/AndroidManifest.xml new file mode 100644 index 00000000000..ae8307c446c --- /dev/null +++ b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/core/tests/overlaytests/device/OverlayAppSecond/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/res/raw/lorem_ipsum.txt similarity index 100% rename from core/tests/overlaytests/device/OverlayAppSecond/res/raw/lorem_ipsum.txt rename to core/tests/overlaytests/device/test-apps/AppOverlayTwo/res/raw/lorem_ipsum.txt diff --git a/core/tests/overlaytests/device/OverlayAppSecond/res/values-sv/config.xml b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/res/values-sv/config.xml similarity index 100% rename from core/tests/overlaytests/device/OverlayAppSecond/res/values-sv/config.xml rename to core/tests/overlaytests/device/test-apps/AppOverlayTwo/res/values-sv/config.xml diff --git a/core/tests/overlaytests/device/OverlayAppSecond/res/values/config.xml b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/res/values/config.xml similarity index 100% rename from core/tests/overlaytests/device/OverlayAppSecond/res/values/config.xml rename to core/tests/overlaytests/device/test-apps/AppOverlayTwo/res/values/config.xml diff --git a/core/tests/overlaytests/device/OverlayAppSecond/res/xml/integer.xml b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/res/xml/integer.xml similarity index 100% rename from core/tests/overlaytests/device/OverlayAppSecond/res/xml/integer.xml rename to core/tests/overlaytests/device/test-apps/AppOverlayTwo/res/xml/integer.xml diff --git a/core/tests/overlaytests/device/test-apps/FrameworkOverlay/Android.mk b/core/tests/overlaytests/device/test-apps/FrameworkOverlay/Android.mk new file mode 100644 index 00000000000..dc811c51e92 --- /dev/null +++ b/core/tests/overlaytests/device/test-apps/FrameworkOverlay/Android.mk @@ -0,0 +1,22 @@ +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE_TAGS := tests +LOCAL_PACKAGE_NAME := OverlayDeviceTests_FrameworkOverlay +LOCAL_COMPATIBILITY_SUITE := device-tests +LOCAL_CERTIFICATE := platform +include $(BUILD_PACKAGE) diff --git a/core/tests/overlaytests/device/test-apps/FrameworkOverlay/AndroidManifest.xml b/core/tests/overlaytests/device/test-apps/FrameworkOverlay/AndroidManifest.xml new file mode 100644 index 00000000000..77ea16afff8 --- /dev/null +++ b/core/tests/overlaytests/device/test-apps/FrameworkOverlay/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/core/tests/overlaytests/device/OverlayTestOverlay/res/values/config.xml b/core/tests/overlaytests/device/test-apps/FrameworkOverlay/res/values/config.xml similarity index 100% rename from core/tests/overlaytests/device/OverlayTestOverlay/res/values/config.xml rename to core/tests/overlaytests/device/test-apps/FrameworkOverlay/res/values/config.xml diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml index 2d6843948f2..b08ac96aca6 100644 --- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml +++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml @@ -15,6 +15,6 @@ --> + package="com.android.server.om.hosttest.signature_overlay"> diff --git a/core/tests/overlaytests/testrunner.py b/core/tests/overlaytests/testrunner.py deleted file mode 100755 index e88805e8cbf..00000000000 --- a/core/tests/overlaytests/testrunner.py +++ /dev/null @@ -1,732 +0,0 @@ -#!/usr/bin/python -import hashlib -import optparse -import os -import re -import shlex -import subprocess -import sys -import threading -import time - -TASK_COMPILATION = 'compile' -TASK_DISABLE_OVERLAYS = 'disable overlays' -TASK_ENABLE_MULTIPLE_OVERLAYS = 'enable multiple overlays' -TASK_ENABLE_SINGLE_OVERLAY = 'enable single overlay' -TASK_ENABLE_FILTERED_OVERLAYS = 'enable filtered overlays' -TASK_FILE_EXISTS_TEST = 'test (file exists)' -TASK_GREP_IDMAP_TEST = 'test (grep idmap)' -TASK_MD5_TEST = 'test (md5)' -TASK_IDMAP_PATH = 'idmap --path' -TASK_IDMAP_SCAN = 'idmap --scan' -TASK_INSTRUMENTATION = 'instrumentation' -TASK_INSTRUMENTATION_TEST = 'test (instrumentation)' -TASK_MKDIR = 'mkdir' -TASK_PUSH = 'push' -TASK_ROOT = 'root' -TASK_REMOUNT = 'remount' -TASK_RM = 'rm' -TASK_SETPROP = 'setprop' -TASK_SETUP_IDMAP_PATH = 'setup idmap --path' -TASK_SETUP_IDMAP_SCAN = 'setup idmap --scan' -TASK_START = 'start' -TASK_STOP = 'stop' - -adb = 'adb' - -def _adb_shell(cmd): - argv = shlex.split(adb + " shell '" + cmd + "; echo $?'") - proc = subprocess.Popen(argv, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdout, stderr) = proc.communicate() - (stdout, stderr) = (stdout.replace('\r', ''), stderr.replace('\r', '')) - tmp = stdout.rsplit('\n', 2) - if len(tmp) == 2: - stdout == '' - returncode = int(tmp[0]) - else: - stdout = tmp[0] + '\n' - returncode = int(tmp[1]) - return returncode, stdout, stderr - -class VerbosePrinter: - class Ticker(threading.Thread): - def _print(self): - s = '\r' + self.text + '[' + '.' * self.i + ' ' * (4 - self.i) + ']' - sys.stdout.write(s) - sys.stdout.flush() - self.i = (self.i + 1) % 5 - - def __init__(self, cond_var, text): - threading.Thread.__init__(self) - self.text = text - self.setDaemon(True) - self.cond_var = cond_var - self.running = False - self.i = 0 - self._print() - self.running = True - - def run(self): - self.cond_var.acquire() - while True: - self.cond_var.wait(0.25) - running = self.running - if not running: - break - self._print() - self.cond_var.release() - - def stop(self): - self.cond_var.acquire() - self.running = False - self.cond_var.notify_all() - self.cond_var.release() - - def _start_ticker(self): - self.ticker = VerbosePrinter.Ticker(self.cond_var, self.text) - self.ticker.start() - - def _stop_ticker(self): - self.ticker.stop() - self.ticker.join() - self.ticker = None - - def _format_begin(self, type, name): - N = self.width - len(type) - len(' [ ] ') - fmt = '%%s %%-%ds ' % N - return fmt % (type, name) - - def __init__(self, use_color): - self.cond_var = threading.Condition() - self.ticker = None - if use_color: - self.color_RED = '\033[1;31m' - self.color_red = '\033[0;31m' - self.color_reset = '\033[0;37m' - else: - self.color_RED = '' - self.color_red = '' - self.color_reset = '' - - argv = shlex.split('stty size') # get terminal width - proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdout, stderr) = proc.communicate() - if proc.returncode == 0: - (h, w) = stdout.split() - self.width = int(w) - else: - self.width = 72 # conservative guesstimate - - def begin(self, type, name): - self.text = self._format_begin(type, name) - sys.stdout.write(self.text + '[ ]') - sys.stdout.flush() - self._start_ticker() - - def end_pass(self, type, name): - self._stop_ticker() - sys.stdout.write('\r' + self.text + '[ OK ]\n') - sys.stdout.flush() - - def end_fail(self, type, name, msg): - self._stop_ticker() - sys.stdout.write('\r' + self.color_RED + self.text + '[FAIL]\n') - sys.stdout.write(self.color_red) - sys.stdout.write(msg) - sys.stdout.write(self.color_reset) - sys.stdout.flush() - -class QuietPrinter: - def begin(self, type, name): - pass - - def end_pass(self, type, name): - sys.stdout.write('PASS ' + type + ' ' + name + '\n') - sys.stdout.flush() - - def end_fail(self, type, name, msg): - sys.stdout.write('FAIL ' + type + ' ' + name + '\n') - sys.stdout.flush() - -class CompilationTask: - def __init__(self, makefile): - self.makefile = makefile - - def get_type(self): - return TASK_COMPILATION - - def get_name(self): - return self.makefile - - def execute(self): - os.putenv('ONE_SHOT_MAKEFILE', os.getcwd() + "/" + self.makefile) - argv = shlex.split('make -C "../../../../../" files') - proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdout, stderr) = proc.communicate() - return proc.returncode, stdout, stderr - -class InstrumentationTask: - def __init__(self, instrumentation_class): - self.instrumentation_class = instrumentation_class - - def get_type(self): - return TASK_INSTRUMENTATION - - def get_name(self): - return self.instrumentation_class - - def execute(self): - return _adb_shell('am instrument -r -w -e class %s com.android.overlaytest/android.test.InstrumentationTestRunner' % self.instrumentation_class) - -class PushTask: - def __init__(self, src, dest): - self.src = src - self.dest = dest - - def get_type(self): - return TASK_PUSH - - def get_name(self): - return "%s -> %s" % (self.src, self.dest) - - def execute(self): - src = os.getenv('OUT') - if (src is None): - return 1, "", "Unable to proceed - $OUT environment var not set\n" - src += "/" + self.src - argv = shlex.split(adb + ' push %s %s' % (src, self.dest)) - proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdout, stderr) = proc.communicate() - return proc.returncode, stdout, stderr - -class MkdirTask: - def __init__(self, path): - self.path = path - - def get_type(self): - return TASK_MKDIR - - def get_name(self): - return self.path - - def execute(self): - return _adb_shell('mkdir -p %s' % self.path) - -class RmTask: - def __init__(self, path): - self.path = path - - def get_type(self): - return TASK_RM - - def get_name(self): - return self.path - - def execute(self): - returncode, stdout, stderr = _adb_shell('ls %s' % self.path) - if returncode != 0 and stderr.endswith(': No such file or directory\n'): - return 0, "", "" - return _adb_shell('rm -r %s' % self.path) - -class SetPropTask: - def __init__(self, prop, value): - self.prop = prop - self.value = value - - def get_type(self): - return TASK_SETPROP - - def get_name(self): - return self.prop - - def execute(self): - return _adb_shell('setprop %s %s' % (self.prop, self.value)) - -class IdmapPathTask: - def __init__(self, path_target_apk, path_overlay_apk, path_idmap): - self.path_target_apk = path_target_apk - self.path_overlay_apk = path_overlay_apk - self.path_idmap = path_idmap - - def get_type(self): - return TASK_IDMAP_PATH - - def get_name(self): - return self.path_idmap - - def execute(self): - return _adb_shell('su system idmap --scan "%s" "%s" "%s" "%s"' % (self.target_pkg_name, self.target_pkg, self.idmap_dir, self.overlay_dir)) - -class IdmapScanTask: - def __init__(self, overlay_dir, target_pkg_name, target_pkg, idmap_dir, symlink_dir): - self.overlay_dir = overlay_dir - self.target_pkg_name = target_pkg_name - self.target_pkg = target_pkg - self.idmap_dir = idmap_dir - self.symlink_dir = symlink_dir - - def get_type(self): - return TASK_IDMAP_SCAN - - def get_name(self): - return self.target_pkg_name - - def execute(self): - return _adb_shell('su system idmap --scan "%s" "%s" "%s" "%s"' % (self.overlay_dir, self.target_pkg_name, self.target_pkg, self.idmap_dir)) - -class FileExistsTest: - def __init__(self, path): - self.path = path - - def get_type(self): - return TASK_FILE_EXISTS_TEST - - def get_name(self): - return self.path - - def execute(self): - return _adb_shell('ls %s' % self.path) - -class GrepIdmapTest: - def __init__(self, path_idmap, pattern, expected_n): - self.path_idmap = path_idmap - self.pattern = pattern - self.expected_n = expected_n - - def get_type(self): - return TASK_GREP_IDMAP_TEST - - def get_name(self): - return self.pattern - - def execute(self): - returncode, stdout, stderr = _adb_shell('idmap --inspect %s' % self.path_idmap) - if returncode != 0: - return returncode, stdout, stderr - all_matches = re.findall('\s' + self.pattern + '$', stdout, flags=re.MULTILINE) - if len(all_matches) != self.expected_n: - return 1, 'pattern=%s idmap=%s expected=%d found=%d\n' % (self.pattern, self.path_idmap, self.expected_n, len(all_matches)), '' - return 0, "", "" - -class Md5Test: - def __init__(self, path, expected_content): - self.path = path - self.expected_md5 = hashlib.md5(expected_content).hexdigest() - - def get_type(self): - return TASK_MD5_TEST - - def get_name(self): - return self.path - - def execute(self): - returncode, stdout, stderr = _adb_shell('md5sum %s' % self.path) - if returncode != 0: - return returncode, stdout, stderr - actual_md5 = stdout.split()[0] - if actual_md5 != self.expected_md5: - return 1, 'expected %s, got %s\n' % (self.expected_md5, actual_md5), '' - return 0, "", "" - -class StartTask: - def get_type(self): - return TASK_START - - def get_name(self): - return "" - - def execute(self): - (returncode, stdout, stderr) = _adb_shell('start') - if returncode != 0: - return returncode, stdout, stderr - - while True: - (returncode, stdout, stderr) = _adb_shell('getprop dev.bootcomplete') - if returncode != 0: - return returncode, stdout, stderr - if stdout.strip() == "1": - break - time.sleep(0.5) - - return 0, "", "" - -class StopTask: - def get_type(self): - return TASK_STOP - - def get_name(self): - return "" - - def execute(self): - (returncode, stdout, stderr) = _adb_shell('stop') - if returncode != 0: - return returncode, stdout, stderr - return _adb_shell('setprop dev.bootcomplete 0') - -class RootTask: - def get_type(self): - return TASK_ROOT - - def get_name(self): - return "" - - def execute(self): - (returncode, stdout, stderr) = _adb_shell('getprop service.adb.root 0') - if returncode != 0: - return returncode, stdout, stderr - if stdout.strip() == '1': # already root - return 0, "", "" - - argv = shlex.split(adb + ' root') - proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdout, stderr) = proc.communicate() - if proc.returncode != 0: - return proc.returncode, stdout, stderr - - argv = shlex.split(adb + ' wait-for-device') - proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdout, stderr) = proc.communicate() - return proc.returncode, stdout, stderr - -class RemountTask: - def get_type(self): - return TASK_REMOUNT - - def get_name(self): - return "" - - def execute(self): - argv = shlex.split(adb + ' remount') - proc = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdout, stderr) = proc.communicate() - # adb remount returns 0 even if the operation failed, so check stdout - if stdout.startswith('remount failed:'): - return 1, stdout, stderr - return proc.returncode, stdout, stderr - -class CompoundTask: - def __init__(self, type, tasks): - self.type = type - self.tasks = tasks - - def get_type(self): - return self.type - - def get_name(self): - return "" - - def execute(self): - for t in self.tasks: - (returncode, stdout, stderr) = t.execute() - if returncode != 0: - return returncode, stdout, stderr - return 0, "", "" - -def _create_disable_overlays_task(): - tasks = [ - RmTask("/vendor/overlay/framework_a.apk"), - RmTask("/vendor/overlay/framework_b.apk"), - RmTask("/data/resource-cache/vendor@overlay@framework_a.apk@idmap"), - RmTask("/data/resource-cache/vendor@overlay@framework_b.apk@idmap"), - RmTask("/vendor/overlay/app_a.apk"), - RmTask("/vendor/overlay/app_b.apk"), - RmTask("/vendor/overlay/app_c.apk"), - RmTask("/data/resource-cache/vendor@overlay@app_a.apk@idmap"), - RmTask("/data/resource-cache/vendor@overlay@app_b.apk@idmap"), - RmTask("/data/resource-cache/vendor@overlay@app_c.apk@idmap"), - SetPropTask('persist.oem.overlay.test', '""'), - RmTask("/data/property/persist.oem.overlay.test"), - ] - return CompoundTask(TASK_DISABLE_OVERLAYS, tasks) - -def _create_enable_single_overlay_task(): - tasks = [ - _create_disable_overlays_task(), - MkdirTask('/system/vendor'), - MkdirTask('/vendor/overlay'), - PushTask('/data/app/com.android.overlaytest.overlay/com.android.overlaytest.overlay.apk', '/vendor/overlay/framework_a.apk'), - PushTask('/data/app/com.android.overlaytest.first_app_overlay/com.android.overlaytest.first_app_overlay.apk', '/vendor/overlay/app_a.apk'), - ] - return CompoundTask(TASK_ENABLE_SINGLE_OVERLAY, tasks) - -def _create_enable_multiple_overlays_task(): - tasks = [ - _create_disable_overlays_task(), - MkdirTask('/system/vendor'), - MkdirTask('/vendor/overlay'), - - PushTask('/data/app/com.android.overlaytest.overlay/com.android.overlaytest.overlay.apk', '/vendor/overlay/framework_b.apk'), - PushTask('/data/app/com.android.overlaytest.first_app_overlay/com.android.overlaytest.first_app_overlay.apk', '/vendor/overlay/app_a.apk'), - PushTask('/data/app/com.android.overlaytest.second_app_overlay/com.android.overlaytest.second_app_overlay.apk', '/vendor/overlay/app_b.apk'), - PushTask('/data/app/com.android.overlaytest.filtered_app_overlay/com.android.overlaytest.filtered_app_overlay.apk', '/vendor/overlay/app_c.apk'), - ] - return CompoundTask(TASK_ENABLE_MULTIPLE_OVERLAYS, tasks) - -def _create_enable_filtered_overlays_task(): - tasks = [ - _create_disable_overlays_task(), - SetPropTask('persist.oem.overlay.test', 'foo'), - MkdirTask('/system/vendor'), - MkdirTask('/vendor/overlay'), - PushTask('/data/app/com.android.overlaytest.overlay/com.android.overlaytest.overlay.apk', '/vendor/overlay/framework_b.apk'), - PushTask('/data/app/com.android.overlaytest.first_app_overlay/com.android.overlaytest.first_app_overlay.apk', '/vendor/overlay/app_a.apk'), - PushTask('/data/app/com.android.overlaytest.second_app_overlay/com.android.overlaytest.second_app_overlay.apk', '/vendor/overlay/app_b.apk'), - PushTask('/data/app/com.android.overlaytest.filtered_app_overlay/com.android.overlaytest.filtered_app_overlay.apk', '/vendor/overlay/app_c.apk'), - ] - return CompoundTask(TASK_ENABLE_FILTERED_OVERLAYS, tasks) - -def _create_setup_idmap_path_task(idmaps, symlinks): - tasks = [ - _create_enable_single_overlay_task(), - RmTask(symlinks), - RmTask(idmaps), - MkdirTask(idmaps), - MkdirTask(symlinks), - ] - return CompoundTask(TASK_SETUP_IDMAP_PATH, tasks) - -def _create_setup_idmap_scan_task(idmaps, symlinks): - tasks = [ - _create_enable_filtered_overlays_task(), - RmTask(symlinks), - RmTask(idmaps), - MkdirTask(idmaps), - MkdirTask(symlinks), - ] - return CompoundTask(TASK_SETUP_IDMAP_SCAN, tasks) - -def _handle_instrumentation_task_output(stdout, printer): - regex_status_code = re.compile(r'^INSTRUMENTATION_STATUS_CODE: -?(\d+)') - regex_name = re.compile(r'^INSTRUMENTATION_STATUS: test=(.*)') - regex_begin_stack = re.compile(r'^INSTRUMENTATION_STATUS: stack=(.*)') - regex_end_stack = re.compile(r'^$') - - failed_tests = 0 - current_test = None - current_stack = [] - mode_stack = False - for line in stdout.split("\n"): - line = line.rstrip() # strip \r from adb output - m = regex_status_code.match(line) - if m: - c = int(m.group(1)) - if c == 1: - printer.begin(TASK_INSTRUMENTATION_TEST, current_test) - elif c == 0: - printer.end_pass(TASK_INSTRUMENTATION_TEST, current_test) - else: - failed_tests += 1 - current_stack.append("\n") - msg = "\n".join(current_stack) - printer.end_fail(TASK_INSTRUMENTATION_TEST, current_test, msg.rstrip() + '\n') - continue - - m = regex_name.match(line) - if m: - current_test = m.group(1) - continue - - m = regex_begin_stack.match(line) - if m: - mode_stack = True - current_stack = [] - current_stack.append(" " + m.group(1)) - continue - - m = regex_end_stack.match(line) - if m: - mode_stack = False - continue - - if mode_stack: - current_stack.append(" " + line.strip()) - - return failed_tests - -def _set_adb_device(option, opt, value, parser): - global adb - if opt == '-d' or opt == '--device': - adb = 'adb -d' - if opt == '-e' or opt == '--emulator': - adb = 'adb -e' - if opt == '-s' or opt == '--serial': - adb = 'adb -s ' + value - -def _create_opt_parser(): - parser = optparse.OptionParser() - parser.add_option('-d', '--device', action='callback', callback=_set_adb_device, - help='pass -d to adb') - parser.add_option('-e', '--emulator', action='callback', callback=_set_adb_device, - help='pass -e to adb') - parser.add_option('-s', '--serial', type="str", action='callback', callback=_set_adb_device, - help='pass -s to adb') - parser.add_option('-C', '--no-color', action='store_false', - dest='use_color', default=True, - help='disable color escape sequences in output') - parser.add_option('-q', '--quiet', action='store_true', - dest='quiet_mode', default=False, - help='quiet mode, output only results') - parser.add_option('-b', '--no-build', action='store_false', - dest='do_build', default=True, - help='do not rebuild test projects') - parser.add_option('-k', '--continue', action='store_true', - dest='do_continue', default=False, - help='do not rebuild test projects') - parser.add_option('-i', '--test-idmap', action='store_true', - dest='test_idmap', default=False, - help='run tests for idmap') - parser.add_option('-0', '--test-no-overlay', action='store_true', - dest='test_no_overlay', default=False, - help='run tests without any overlay') - parser.add_option('-1', '--test-single-overlay', action='store_true', - dest='test_single_overlay', default=False, - help='run tests for single overlay') - parser.add_option('-2', '--test-multiple-overlays', action='store_true', - dest='test_multiple_overlays', default=False, - help='run tests for multiple overlays') - parser.add_option('-3', '--test-filtered-overlays', action='store_true', - dest='test_filtered_overlays', default=False, - help='run tests for filtered (sys prop) overlays') - return parser - -if __name__ == '__main__': - opt_parser = _create_opt_parser() - opts, args = opt_parser.parse_args(sys.argv[1:]) - if not opts.test_idmap and not opts.test_no_overlay and not opts.test_single_overlay and not opts.test_multiple_overlays and not opts.test_filtered_overlays: - opts.test_idmap = True - opts.test_no_overlay = True - opts.test_single_overlay = True - opts.test_multiple_overlays = True - opts.test_filtered_overlays = True - - if len(args) > 0: - opt_parser.error("unexpected arguments: %s" % " ".join(args)) - # will never reach this: opt_parser.error will call sys.exit - - if opts.quiet_mode: - printer = QuietPrinter() - else: - printer = VerbosePrinter(opts.use_color) - tasks = [] - - # must be in the same directory as this script for compilation tasks to work - script = sys.argv[0] - dirname = os.path.dirname(script) - wd = os.path.realpath(dirname) - os.chdir(wd) - - # build test cases - if opts.do_build: - tasks.append(CompilationTask('OverlayTest/Android.mk')) - tasks.append(CompilationTask('OverlayTestOverlay/Android.mk')) - tasks.append(CompilationTask('OverlayAppFirst/Android.mk')) - tasks.append(CompilationTask('OverlayAppSecond/Android.mk')) - tasks.append(CompilationTask('OverlayAppFiltered/Android.mk')) - - # remount filesystem, install test project - tasks.append(RootTask()) - tasks.append(RemountTask()) - tasks.append(PushTask('/system/app/OverlayTest/OverlayTest.apk', '/system/app/OverlayTest.apk')) - - # test idmap - if opts.test_idmap: - idmaps='/data/local/tmp/idmaps' - symlinks='/data/local/tmp/symlinks' - - # idmap --path - tasks.append(StopTask()) - tasks.append(_create_setup_idmap_path_task(idmaps, symlinks)) - tasks.append(StartTask()) - tasks.append(IdmapPathTask('/vendor/overlay/framework_a.apk', '/system/framework/framework-res.apk', idmaps + '/a.idmap')) - tasks.append(FileExistsTest(idmaps + '/a.idmap')) - tasks.append(GrepIdmapTest(idmaps + '/a.idmap', 'bool/config_annoy_dianne', 1)) - - # idmap --scan - tasks.append(StopTask()) - tasks.append(_create_setup_idmap_scan_task(idmaps, symlinks)) - tasks.append(StartTask()) - tasks.append(IdmapScanTask('/vendor/overlay', 'android', '/system/framework/framework-res.apk', idmaps, symlinks)) - tasks.append(FileExistsTest(idmaps + '/vendor@overlay@framework_b.apk@idmap')) - tasks.append(GrepIdmapTest(idmaps + '/vendor@overlay@framework_b.apk@idmap', 'bool/config_annoy_dianne', 1)) - - - # overlays.list - overlays_list_path = idmaps + '/overlays.list' - expected_content = '''\ -/vendor/overlay/framework_b.apk /data/local/tmp/idmaps/vendor@overlay@framework_b.apk@idmap -''' - tasks.append(FileExistsTest(overlays_list_path)) - tasks.append(Md5Test(overlays_list_path, expected_content)) - - # idmap cleanup - tasks.append(RmTask(symlinks)) - tasks.append(RmTask(idmaps)) - - # test no overlay: all overlays cleared - if opts.test_no_overlay: - tasks.append(StopTask()) - tasks.append(_create_disable_overlays_task()) - tasks.append(StartTask()) - tasks.append(InstrumentationTask('com.android.overlaytest.WithoutOverlayTest')) - - # test single overlay: one overlay (a) - if opts.test_single_overlay: - tasks.append(StopTask()) - tasks.append(_create_enable_single_overlay_task()) - tasks.append(StartTask()) - tasks.append(InstrumentationTask('com.android.overlaytest.WithOverlayTest')) - - # test multiple overlays: all overlays - including 'disabled' filtered - # overlay (system property unset) so expect 'b[p=2]' overrides 'a[p=1]' but - # 'c[p=3]' should be ignored - if opts.test_multiple_overlays: - tasks.append(StopTask()) - tasks.append(_create_enable_multiple_overlays_task()) - tasks.append(StartTask()) - tasks.append(InstrumentationTask('com.android.overlaytest.WithMultipleOverlaysTest')) - - # test filtered overlays: all overlays - including 'enabled' filtered - # overlay (system property set/matched) so expect c[p=3] to override both a - # & b where applicable - if opts.test_filtered_overlays: - tasks.append(StopTask()) - tasks.append(_create_enable_filtered_overlays_task()) - tasks.append(StartTask()) - tasks.append(InstrumentationTask('com.android.overlaytest.WithFilteredOverlaysTest')) - - ignored_errors = 0 - for t in tasks: - type = t.get_type() - name = t.get_name() - if type == TASK_INSTRUMENTATION: - # InstrumentationTask will run several tests, but we want it - # to appear as if each test was run individually. Calling - # "am instrument" with a single test method is prohibitively - # expensive, so let's instead post-process the output to - # emulate individual calls. - retcode, stdout, stderr = t.execute() - if retcode != 0: - printer.begin(TASK_INSTRUMENTATION, name) - printer.end_fail(TASK_INSTRUMENTATION, name, stderr) - sys.exit(retcode) - retcode = _handle_instrumentation_task_output(stdout, printer) - if retcode != 0: - if not opts.do_continue: - sys.exit(retcode) - else: - ignored_errors += retcode - else: - printer.begin(type, name) - retcode, stdout, stderr = t.execute() - if retcode == 0: - printer.end_pass(type, name) - if retcode != 0: - if len(stderr) == 0: - # hope for output from stdout instead (true for eg adb shell rm) - stderr = stdout - printer.end_fail(type, name, stderr) - if not opts.do_continue: - sys.exit(retcode) - else: - ignored_errors += retcode - sys.exit(ignored_errors) -- GitLab From 43a8ebb899adcd2bd2bc06c72fa1fc77dd2100d9 Mon Sep 17 00:00:00 2001 From: Mikhail Naganov Date: Wed, 24 Jan 2018 09:21:38 -0800 Subject: [PATCH 012/416] Add AudioFormat.ENCODING_E_AC3_JOC Added to Java API only. Conversion from/to native depends on having the constant on the native side. Bug: 63901775 Test: make Change-Id: I816562d40e98684d6ae1d4c27460324c99926525 --- api/current.txt | 1 + core/jni/android_media_AudioFormat.h | 5 +++++ media/java/android/media/AudioFormat.java | 12 ++++++++++++ 3 files changed, 18 insertions(+) diff --git a/api/current.txt b/api/current.txt index fa896eb5cc1..0d592b9a05d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -21870,6 +21870,7 @@ package android.media { field public static final int ENCODING_DTS = 7; // 0x7 field public static final int ENCODING_DTS_HD = 8; // 0x8 field public static final int ENCODING_E_AC3 = 6; // 0x6 + field public static final int ENCODING_E_AC3_JOC = 18; // 0x12 field public static final int ENCODING_IEC61937 = 13; // 0xd field public static final int ENCODING_INVALID = 0; // 0x0 field public static final int ENCODING_MP3 = 9; // 0x9 diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h index c79f5bd96e9..12da27307ad 100644 --- a/core/jni/android_media_AudioFormat.h +++ b/core/jni/android_media_AudioFormat.h @@ -36,6 +36,7 @@ #define ENCODING_AAC_ELD 15 #define ENCODING_AAC_XHE 16 #define ENCODING_AC4 17 +#define ENCODING_E_AC3_JOC 18 #define ENCODING_INVALID 0 #define ENCODING_DEFAULT 1 @@ -80,6 +81,8 @@ static inline audio_format_t audioFormatToNative(int audioFormat) return AUDIO_FORMAT_AAC; // FIXME temporary value, needs addition of xHE-AAC case ENCODING_AC4: return AUDIO_FORMAT_AC4; + // case ENCODING_E_AC3_JOC: // FIXME Not defined on the native side yet + // return AUDIO_FORMAT_E_AC3_JOC; case ENCODING_DEFAULT: return AUDIO_FORMAT_DEFAULT; default: @@ -130,6 +133,8 @@ static inline int audioFormatFromNative(audio_format_t nativeFormat) // return ENCODING_AAC_XHE; case AUDIO_FORMAT_AC4: return ENCODING_AC4; + // case AUDIO_FORMAT_E_AC3_JOC: // FIXME Not defined on the native side yet + // return ENCODING_E_AC3_JOC; case AUDIO_FORMAT_DEFAULT: return ENCODING_DEFAULT; default: diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java index b07d0422004..f98480b2800 100644 --- a/media/java/android/media/AudioFormat.java +++ b/media/java/android/media/AudioFormat.java @@ -265,6 +265,12 @@ public final class AudioFormat implements Parcelable { public static final int ENCODING_AAC_XHE = 16; /** Audio data format: AC-4 sync frame transport format */ public static final int ENCODING_AC4 = 17; + /** Audio data format: E-AC-3-JOC compressed + * E-AC-3-JOC streams can be decoded by downstream devices supporting {@link #ENCODING_E_AC3}. + * Use {@link #ENCODING_E_AC3} as the AudioTrack encoding when the downstream device + * supports {@link #ENCODING_E_AC3} but not {@link #ENCODING_E_AC3_JOC}. + **/ + public static final int ENCODING_E_AC3_JOC = 18; /** @hide */ public static String toLogFriendlyEncoding(int enc) { @@ -512,6 +518,7 @@ public final class AudioFormat implements Parcelable { case ENCODING_PCM_FLOAT: case ENCODING_AC3: case ENCODING_E_AC3: + case ENCODING_E_AC3_JOC: case ENCODING_DTS: case ENCODING_DTS_HD: case ENCODING_MP3: @@ -537,6 +544,7 @@ public final class AudioFormat implements Parcelable { case ENCODING_PCM_FLOAT: case ENCODING_AC3: case ENCODING_E_AC3: + case ENCODING_E_AC3_JOC: case ENCODING_DTS: case ENCODING_DTS_HD: case ENCODING_IEC61937: @@ -564,6 +572,7 @@ public final class AudioFormat implements Parcelable { return true; case ENCODING_AC3: case ENCODING_E_AC3: + case ENCODING_E_AC3_JOC: case ENCODING_DTS: case ENCODING_DTS_HD: case ENCODING_MP3: @@ -593,6 +602,7 @@ public final class AudioFormat implements Parcelable { return true; case ENCODING_AC3: case ENCODING_E_AC3: + case ENCODING_E_AC3_JOC: case ENCODING_DTS: case ENCODING_DTS_HD: case ENCODING_MP3: @@ -829,6 +839,7 @@ public final class AudioFormat implements Parcelable { case ENCODING_PCM_FLOAT: case ENCODING_AC3: case ENCODING_E_AC3: + case ENCODING_E_AC3_JOC: case ENCODING_DTS: case ENCODING_DTS_HD: case ENCODING_IEC61937: @@ -1044,6 +1055,7 @@ public final class AudioFormat implements Parcelable { ENCODING_PCM_FLOAT, ENCODING_AC3, ENCODING_E_AC3, + ENCODING_E_AC3_JOC, ENCODING_DTS, ENCODING_DTS_HD, ENCODING_IEC61937, -- GitLab From 58f34065c53067a22bba6b8af7f9cb759701e79e Mon Sep 17 00:00:00 2001 From: Tomasz Wasilczyk Date: Sat, 13 Jan 2018 08:25:17 -0800 Subject: [PATCH 013/416] Implement front-end API to retrieve DAB frequency table. Bug: 69958423 Test: instrumentation (none added) Change-Id: I7e648f988baf3b14d814588d44b18fd57c108906 --- api/system-current.txt | 1 + .../android/hardware/radio/RadioManager.java | 98 ++++++++----------- core/java/android/hardware/radio/Utils.java | 23 +++++ .../server/broadcastradio/hal1/Tuner.java | 2 +- .../server/broadcastradio/hal2/Convert.java | 10 +- .../broadcastradio/hal2/RadioModule.java | 12 ++- services/core/jni/BroadcastRadio/convert.cpp | 5 +- 7 files changed, 89 insertions(+), 62 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index fd25e1bcd1e..c0320aa26a0 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1986,6 +1986,7 @@ package android.hardware.radio { method public int describeContents(); method public android.hardware.radio.RadioManager.BandDescriptor[] getBands(); method public int getClassId(); + method public java.util.Map getDabFrequencyTable(); method public int getId(); method public java.lang.String getImplementor(); method public int getNumAudioSources(); diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index b00f6033976..e1d7edfa7d9 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -213,6 +213,7 @@ public class RadioManager { private final boolean mIsBgScanSupported; private final Set mSupportedProgramTypes; private final Set mSupportedIdentifierTypes; + @Nullable private final Map mDabFrequencyTable; @NonNull private final Map mVendorInfo; /** @hide */ @@ -221,6 +222,7 @@ public class RadioManager { boolean isCaptureSupported, BandDescriptor[] bands, boolean isBgScanSupported, @ProgramSelector.ProgramType int[] supportedProgramTypes, @ProgramSelector.IdentifierType int[] supportedIdentifierTypes, + @Nullable Map dabFrequencyTable, Map vendorInfo) { mId = id; mServiceName = TextUtils.isEmpty(serviceName) ? "default" : serviceName; @@ -236,6 +238,13 @@ public class RadioManager { mIsBgScanSupported = isBgScanSupported; mSupportedProgramTypes = arrayToSet(supportedProgramTypes); mSupportedIdentifierTypes = arrayToSet(supportedIdentifierTypes); + if (dabFrequencyTable != null) { + for (Map.Entry entry : dabFrequencyTable.entrySet()) { + Objects.requireNonNull(entry.getKey()); + Objects.requireNonNull(entry.getValue()); + } + } + mDabFrequencyTable = dabFrequencyTable; mVendorInfo = (vendorInfo == null) ? new HashMap<>() : vendorInfo; } @@ -362,6 +371,19 @@ public class RadioManager { return mSupportedIdentifierTypes.contains(type); } + /** + * A frequency table for Digital Audio Broadcasting (DAB). + * + * The key is a channel name, i.e. 5A, 7B. + * + * The value is a frequency, in kHz. + * + * @return a frequency table, or {@code null} if the module doesn't support DAB + */ + public @Nullable Map getDabFrequencyTable() { + return mDabFrequencyTable; + } + /** * A map of vendor-specific opaque strings, passed from HAL without changes. * Format of these strings can vary across vendors. @@ -403,6 +425,7 @@ public class RadioManager { mIsBgScanSupported = in.readInt() == 1; mSupportedProgramTypes = arrayToSet(in.createIntArray()); mSupportedIdentifierTypes = arrayToSet(in.createIntArray()); + mDabFrequencyTable = Utils.readStringIntMap(in); mVendorInfo = Utils.readStringMap(in); } @@ -433,6 +456,7 @@ public class RadioManager { dest.writeInt(mIsBgScanSupported ? 1 : 0); dest.writeIntArray(setToArray(mSupportedProgramTypes)); dest.writeIntArray(setToArray(mSupportedIdentifierTypes)); + Utils.writeStringIntMap(dest, mDabFrequencyTable); Utils.writeStringMap(dest, mVendorInfo); } @@ -456,67 +480,31 @@ public class RadioManager { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + mId; - result = prime * result + mServiceName.hashCode(); - result = prime * result + mClassId; - result = prime * result + ((mImplementor == null) ? 0 : mImplementor.hashCode()); - result = prime * result + ((mProduct == null) ? 0 : mProduct.hashCode()); - result = prime * result + ((mVersion == null) ? 0 : mVersion.hashCode()); - result = prime * result + ((mSerial == null) ? 0 : mSerial.hashCode()); - result = prime * result + mNumTuners; - result = prime * result + mNumAudioSources; - result = prime * result + (mIsCaptureSupported ? 1 : 0); - result = prime * result + Arrays.hashCode(mBands); - result = prime * result + (mIsBgScanSupported ? 1 : 0); - result = prime * result + mVendorInfo.hashCode(); - return result; + return Objects.hash(mId, mServiceName, mClassId, mImplementor, mProduct, mVersion, + mSerial, mNumTuners, mNumAudioSources, mIsCaptureSupported, mBands, + mIsBgScanSupported, mDabFrequencyTable, mVendorInfo); } @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (!(obj instanceof ModuleProperties)) - return false; + if (this == obj) return true; + if (!(obj instanceof ModuleProperties)) return false; ModuleProperties other = (ModuleProperties) obj; - if (mId != other.getId()) - return false; + + if (mId != other.getId()) return false; if (!TextUtils.equals(mServiceName, other.mServiceName)) return false; - if (mClassId != other.getClassId()) - return false; - if (mImplementor == null) { - if (other.getImplementor() != null) - return false; - } else if (!mImplementor.equals(other.getImplementor())) - return false; - if (mProduct == null) { - if (other.getProduct() != null) - return false; - } else if (!mProduct.equals(other.getProduct())) - return false; - if (mVersion == null) { - if (other.getVersion() != null) - return false; - } else if (!mVersion.equals(other.getVersion())) - return false; - if (mSerial == null) { - if (other.getSerial() != null) - return false; - } else if (!mSerial.equals(other.getSerial())) - return false; - if (mNumTuners != other.getNumTuners()) - return false; - if (mNumAudioSources != other.getNumAudioSources()) - return false; - if (mIsCaptureSupported != other.isCaptureSupported()) - return false; - if (!Arrays.equals(mBands, other.getBands())) - return false; - if (mIsBgScanSupported != other.isBackgroundScanningSupported()) - return false; - if (!mVendorInfo.equals(other.mVendorInfo)) return false; + if (mClassId != other.mClassId) return false; + if (!Objects.equals(mImplementor, other.mImplementor)) return false; + if (!Objects.equals(mProduct, other.mProduct)) return false; + if (!Objects.equals(mVersion, other.mVersion)) return false; + if (!Objects.equals(mSerial, other.mSerial)) return false; + if (mNumTuners != other.mNumTuners) return false; + if (mNumAudioSources != other.mNumAudioSources) return false; + if (mIsCaptureSupported != other.mIsCaptureSupported) return false; + if (!Objects.equals(mBands, other.mBands)) return false; + if (mIsBgScanSupported != other.mIsBgScanSupported) return false; + if (!Objects.equals(mDabFrequencyTable, other.mDabFrequencyTable)) return false; + if (!Objects.equals(mVendorInfo, other.mVendorInfo)) return false; return true; } } diff --git a/core/java/android/hardware/radio/Utils.java b/core/java/android/hardware/radio/Utils.java index f1b589746a1..9887f782326 100644 --- a/core/java/android/hardware/radio/Utils.java +++ b/core/java/android/hardware/radio/Utils.java @@ -56,6 +56,29 @@ final class Utils { return map; } + static void writeStringIntMap(@NonNull Parcel dest, @Nullable Map map) { + if (map == null) { + dest.writeInt(0); + return; + } + dest.writeInt(map.size()); + for (Map.Entry entry : map.entrySet()) { + dest.writeString(entry.getKey()); + dest.writeInt(entry.getValue()); + } + } + + static @NonNull Map readStringIntMap(@NonNull Parcel in) { + int size = in.readInt(); + Map map = new HashMap<>(); + while (size-- > 0) { + String key = in.readString(); + int value = in.readInt(); + map.put(key, value); + } + return map; + } + static void writeSet(@NonNull Parcel dest, @Nullable Set set) { if (set == null) { dest.writeInt(0); diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java index f9b35f53d4d..51e8ecfb019 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java @@ -48,7 +48,7 @@ class Tuner extends ITuner.Stub { private boolean mIsClosed = false; private boolean mIsMuted = false; - private int mRegion; // TODO(b/62710330): find better solution to handle regions + private int mRegion; private final boolean mWithAudio; Tuner(@NonNull ITunerCallback clientCallback, int halRev, diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java index 7a95971ca51..8e8169e2448 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.hardware.broadcastradio.V2_0.AmFmBandRange; import android.hardware.broadcastradio.V2_0.AmFmRegionConfig; import android.hardware.broadcastradio.V2_0.Announcement; +import android.hardware.broadcastradio.V2_0.DabTableEntry; import android.hardware.broadcastradio.V2_0.IdentifierType; import android.hardware.broadcastradio.V2_0.ProgramFilter; import android.hardware.broadcastradio.V2_0.ProgramIdentifier; @@ -177,9 +178,15 @@ class Convert { return bands.toArray(new RadioManager.BandDescriptor[bands.size()]); } + private static @Nullable Map dabConfigFromHal( + @Nullable List config) { + if (config == null) return null; + return config.stream().collect(Collectors.toMap(e -> e.label, e -> e.frequency)); + } + static @NonNull RadioManager.ModuleProperties propertiesFromHal(int id, @NonNull String serviceName, @NonNull Properties prop, - @Nullable AmFmRegionConfig amfmConfig) { + @Nullable AmFmRegionConfig amfmConfig, @Nullable List dabConfig) { Objects.requireNonNull(serviceName); Objects.requireNonNull(prop); @@ -209,6 +216,7 @@ class Convert { false, // isBgScanSupported is deprecated supportedProgramTypes, supportedIdentifierTypes, + dabConfigFromHal(dabConfig), vendorInfoFromHal(prop.vendorInfo) ); } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java index 4dff9e06000..72f5bce8def 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java @@ -22,6 +22,7 @@ import android.hardware.radio.ITuner; import android.hardware.radio.RadioManager; import android.hardware.broadcastradio.V2_0.AmFmRegionConfig; import android.hardware.broadcastradio.V2_0.Announcement; +import android.hardware.broadcastradio.V2_0.DabTableEntry; import android.hardware.broadcastradio.V2_0.IAnnouncementListener; import android.hardware.broadcastradio.V2_0.IBroadcastRadio; import android.hardware.broadcastradio.V2_0.ICloseHandle; @@ -56,12 +57,17 @@ class RadioModule { if (service == null) return null; Mutable amfmConfig = new Mutable<>(); - service.getAmFmRegionConfig(false, (int result, AmFmRegionConfig config) -> { + service.getAmFmRegionConfig(false, (result, config) -> { if (result == Result.OK) amfmConfig.value = config; }); - RadioManager.ModuleProperties prop = - Convert.propertiesFromHal(idx, fqName, service.getProperties(), amfmConfig.value); + Mutable> dabConfig = new Mutable<>(); + service.getDabRegionConfig((result, config) -> { + if (result == Result.OK) dabConfig.value = config; + }); + + RadioManager.ModuleProperties prop = Convert.propertiesFromHal(idx, fqName, + service.getProperties(), amfmConfig.value, dabConfig.value); return new RadioModule(service, prop); } catch (RemoteException ex) { diff --git a/services/core/jni/BroadcastRadio/convert.cpp b/services/core/jni/BroadcastRadio/convert.cpp index 8c38e0a9e36..847222ae4ba 100644 --- a/services/core/jni/BroadcastRadio/convert.cpp +++ b/services/core/jni/BroadcastRadio/convert.cpp @@ -395,7 +395,8 @@ static JavaRef ModulePropertiesFromHal(JNIEnv *env, const V1_0::Propert gjni.ModuleProperties.cstor, moduleId, jServiceName.get(), prop10.classId, jImplementor.get(), jProduct.get(), jVersion.get(), jSerial.get(), prop10.numTuners, prop10.numAudioSources, prop10.supportsCapture, jBands.get(), isBgScanSupported, - jSupportedProgramTypes.get(), jSupportedIdentifierTypes.get(), jVendorInfo.get())); + jSupportedProgramTypes.get(), jSupportedIdentifierTypes.get(), nullptr, + jVendorInfo.get())); } JavaRef ModulePropertiesFromHal(JNIEnv *env, const V1_0::Properties &properties, @@ -712,7 +713,7 @@ void register_android_server_broadcastradio_convert(JNIEnv *env) { gjni.ModuleProperties.cstor = GetMethodIDOrDie(env, modulePropertiesClass, "", "(ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;" "Ljava/lang/String;IIZ[Landroid/hardware/radio/RadioManager$BandDescriptor;Z" - "[I[ILjava/util/Map;)V"); + "[I[ILjava/util/Map;Ljava/util/Map;)V"); auto programInfoClass = FindClassOrDie(env, "android/hardware/radio/RadioManager$ProgramInfo"); gjni.ProgramInfo.clazz = MakeGlobalRefOrDie(env, programInfoClass); -- GitLab From 264625486becc65fac29d7094cfba6d591daeae4 Mon Sep 17 00:00:00 2001 From: Brad Ebinger Date: Thu, 11 Jan 2018 10:27:43 -0800 Subject: [PATCH 014/416] Integrate ImsCallSessionListener API changes Integrates the ImsCallSessionListener API changes. This involves: 1) Moving the ImsCallSessionListener to the android.telephony.ims namespace. 2) Creating a compat layer between the old IImsCallSessionListener AIDL and the new one for vendors using the old implementation. 3) Modify ImsCallSession to only use setListener to set ImsCallSessionListener (other method was never used in our code). Test: Telephony Unit Tests, Manual IMS Tests Bug: 63987047 Change-Id: I4378c0b1d68ff4f5f21815c81af52c03a66f81c5 --- Android.bp | 7 +- .../ImsCallSessionListener.java | 54 +++- .../aidl/IImsCallSessionListener.aidl | 2 +- .../ims/compat/feature/MMTelFeature.java | 48 +++ .../compat/stub/ImsCallSessionImplBase.java | 276 ++++++++++++++++ .../telephony/ims/feature/MMTelFeature.java | 11 +- .../ims/internal/aidl/IImsMmTelFeature.aidl | 12 +- .../ims/internal/feature/MmTelFeature.java | 82 ++++- .../ims/stub/ImsCallSessionImplBase.java | 82 ++--- .../stub/ImsCallSessionListenerImplBase.java | 298 ------------------ .../android/ims/internal/IImsCallSession.aidl | 3 +- .../ims/internal/IImsMMTelFeature.aidl | 4 +- .../android/ims/internal/ImsCallSession.java | 109 +++---- 13 files changed, 549 insertions(+), 439 deletions(-) rename telephony/java/android/telephony/ims/{internal => }/ImsCallSessionListener.java (87%) rename telephony/java/android/telephony/ims/{internal => }/aidl/IImsCallSessionListener.aidl (99%) create mode 100644 telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java create mode 100644 telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java delete mode 100644 telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java diff --git a/Android.bp b/Android.bp index ee2281fe27e..3b87f44b300 100644 --- a/Android.bp +++ b/Android.bp @@ -476,7 +476,7 @@ java_library { "telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl", "telephony/java/android/telephony/data/IDataService.aidl", "telephony/java/android/telephony/data/IDataServiceCallback.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl", + "telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsConfigCallback.aidl", @@ -485,7 +485,8 @@ java_library { "telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl", - "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", + "telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl", + "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl", "telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl", "telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl", @@ -509,7 +510,7 @@ java_library { "telephony/java/com/android/ims/internal/IImsService.aidl", "telephony/java/com/android/ims/internal/IImsServiceController.aidl", "telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl", - "telephony/java/com/android/ims/internal/IImsSmsListener.aidl", + "telephony/java/com/android/ims/internal/IImsSmsListener.aidl", "telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl", "telephony/java/com/android/ims/internal/IImsUt.aidl", "telephony/java/com/android/ims/internal/IImsUtListener.aidl", diff --git a/telephony/java/android/telephony/ims/internal/ImsCallSessionListener.java b/telephony/java/android/telephony/ims/ImsCallSessionListener.java similarity index 87% rename from telephony/java/android/telephony/ims/internal/ImsCallSessionListener.java rename to telephony/java/android/telephony/ims/ImsCallSessionListener.java index 5d16dd5b30e..96c7af6bd03 100644 --- a/telephony/java/android/telephony/ims/internal/ImsCallSessionListener.java +++ b/telephony/java/android/telephony/ims/ImsCallSessionListener.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open 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,16 +14,17 @@ * limitations under the License */ -package android.telephony.ims.internal; +package android.telephony.ims; import android.os.RemoteException; -import android.telephony.ims.internal.aidl.IImsCallSessionListener; +import android.telephony.ims.aidl.IImsCallSessionListener; import com.android.ims.ImsCallProfile; import com.android.ims.ImsConferenceState; import com.android.ims.ImsReasonInfo; import com.android.ims.ImsStreamMediaProfile; import com.android.ims.ImsSuppServiceNotification; +import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.ImsCallSession; /** @@ -137,6 +138,20 @@ public class ImsCallSessionListener { profile); } + /** + * Called when the session merge has been started. At this point, the {@code newSession} + * represents the session which has been initiated to the IMS conference server for the + * new merged conference. + * + * @param newSession the session object that is merged with an active & hold session + * + * @hide + */ + public void callSessionMergeStarted(IImsCallSession newSession, ImsCallProfile profile) + throws RemoteException { + mListener.callSessionMergeStarted(newSession, profile); + } + /** * Called when the session merge is successful and the merged session is active. * @@ -146,6 +161,17 @@ public class ImsCallSessionListener { mListener.callSessionMergeComplete(newSession != null ? newSession.getSession() : null); } + /** + * Called when the session merge is successful and the merged session is active. + * + * @param newSession the new session object that is used for the conference + * + * @hide + */ + public void callSessionMergeComplete(IImsCallSession newSession) throws RemoteException { + mListener.callSessionMergeComplete(newSession); + } + /** * Called when the session merge has failed. * @@ -190,6 +216,18 @@ public class ImsCallSessionListener { profile); } + /** + * Called when the session has been extended to a conference session. + * + * @param newSession the session object that is extended to the conference + * from the active session + * @hide + */ + public void callSessionConferenceExtended(IImsCallSession newSession, ImsCallProfile profile) + throws RemoteException { + mListener.callSessionConferenceExtended(newSession, profile); + } + /** * Called when the conference extension has failed. * @@ -208,6 +246,16 @@ public class ImsCallSessionListener { ? newSession.getSession() : null, profile); } + /** + * Called when the conference extension is received from the remote user. + * + * @hide + */ + public void callSessionConferenceExtendReceived(IImsCallSession newSession, + ImsCallProfile profile) throws RemoteException { + mListener.callSessionConferenceExtendReceived(newSession, profile); + } + /** * Called when the invitation request of the participants is delivered to the conference * server. diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl similarity index 99% rename from telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl rename to telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl index 2fb67442fa3..e6a18d027d6 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; import com.android.ims.ImsStreamMediaProfile; import com.android.ims.ImsCallProfile; diff --git a/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java new file mode 100644 index 00000000000..403bb60404c --- /dev/null +++ b/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telephony.ims.compat.feature; + +import com.android.ims.ImsCallProfile; +import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsCallSessionListener; +import com.android.ims.internal.ImsCallSession; + +/** + * Compatability layer for older implementations of MMTelFeature. + * + * @hide + */ + +public class MMTelFeature extends android.telephony.ims.feature.MMTelFeature { + + @Override + public final IImsCallSession createCallSession(int sessionId, ImsCallProfile profile) { + return createCallSession(sessionId, profile, null /*listener*/); + } + + /** + * Creates an {@link ImsCallSession} with the specified call profile. + * + * @param sessionId a session id which is obtained from {@link #startSession} + * @param profile a call profile to make the call + * @param listener An implementation of IImsCallSessionListener. + */ + public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile, + IImsCallSessionListener listener) { + return null; + } +} diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java new file mode 100644 index 00000000000..6725d2937a3 --- /dev/null +++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telephony.ims.compat.stub; + +import android.os.RemoteException; +import android.telephony.ims.ImsCallSessionListener; + +import com.android.ims.ImsCallProfile; +import com.android.ims.ImsConferenceState; +import com.android.ims.ImsReasonInfo; +import com.android.ims.ImsStreamMediaProfile; +import com.android.ims.ImsSuppServiceNotification; +import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsCallSessionListener; +import com.android.ims.internal.ImsCallSession; + +/** + * Compat implementation of ImsCallSessionImplBase for older implementations. + * + * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you + * will break other implementations of ImsCallSession maintained by other ImsServices. + * + * @hide + */ + +public class ImsCallSessionImplBase extends android.telephony.ims.stub.ImsCallSessionImplBase { + + @Override + public final void setListener(ImsCallSessionListener listener) { + setListener(new ImsCallSessionListenerConverter(listener)); + } + + /** + * Sets the listener to listen to the session events. An {@link ImsCallSession} + * can only hold one listener at a time. Subsequent calls to this method + * override the previous listener. + * + * @param listener to listen to the session events of this object + */ + public void setListener(IImsCallSessionListener listener) { + + } + + /** + * There are two different ImsCallSessionListeners that need to reconciled here, we need to + * convert the "old" version of the com.android.ims.internal.IImsCallSessionListener to the + * "new" version of the Listener android.telephony.ims.ImsCallSessionListener when calling + * back to the framework. + */ + private class ImsCallSessionListenerConverter extends IImsCallSessionListener.Stub { + + private final ImsCallSessionListener mNewListener; + + public ImsCallSessionListenerConverter(ImsCallSessionListener listener) { + mNewListener = listener; + } + + @Override + public void callSessionProgressing(IImsCallSession i, + ImsStreamMediaProfile imsStreamMediaProfile) throws RemoteException { + mNewListener.callSessionProgressing(imsStreamMediaProfile); + } + + @Override + public void callSessionStarted(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionInitiated(imsCallProfile); + } + + @Override + public void callSessionStartFailed(IImsCallSession i, ImsReasonInfo imsReasonInfo) + throws RemoteException { + mNewListener.callSessionInitiatedFailed(imsReasonInfo); + } + + @Override + public void callSessionTerminated(IImsCallSession i, ImsReasonInfo imsReasonInfo) + throws RemoteException { + mNewListener.callSessionTerminated(imsReasonInfo); + } + + @Override + public void callSessionHeld(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionHeld(imsCallProfile); + } + + @Override + public void callSessionHoldFailed(IImsCallSession i, ImsReasonInfo imsReasonInfo) + throws RemoteException { + mNewListener.callSessionHoldFailed(imsReasonInfo); + } + + @Override + public void callSessionHoldReceived(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionHoldReceived(imsCallProfile); + } + + @Override + public void callSessionResumed(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionResumed(imsCallProfile); + } + + @Override + public void callSessionResumeFailed(IImsCallSession i, ImsReasonInfo imsReasonInfo) + throws RemoteException { + mNewListener.callSessionResumeFailed(imsReasonInfo); + } + + @Override + public void callSessionResumeReceived(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionResumeReceived(imsCallProfile); + } + + @Override + public void callSessionMergeStarted(IImsCallSession i, IImsCallSession newSession, + ImsCallProfile profile) + throws RemoteException { + mNewListener.callSessionMergeStarted(newSession, profile); + } + + @Override + public void callSessionMergeComplete(IImsCallSession iImsCallSession) + throws RemoteException { + mNewListener.callSessionMergeComplete(iImsCallSession); + } + + @Override + public void callSessionMergeFailed(IImsCallSession i, ImsReasonInfo imsReasonInfo) + throws RemoteException { + mNewListener.callSessionMergeFailed(imsReasonInfo); + } + + @Override + public void callSessionUpdated(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionUpdated(imsCallProfile); + } + + @Override + public void callSessionUpdateFailed(IImsCallSession i, ImsReasonInfo imsReasonInfo) + throws RemoteException { + mNewListener.callSessionUpdateFailed(imsReasonInfo); + } + + @Override + public void callSessionUpdateReceived(IImsCallSession i, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionUpdateReceived(imsCallProfile); + } + + @Override + public void callSessionConferenceExtended(IImsCallSession i, IImsCallSession newSession, + ImsCallProfile imsCallProfile) throws RemoteException { + mNewListener.callSessionConferenceExtended(newSession, imsCallProfile); + } + + @Override + public void callSessionConferenceExtendFailed(IImsCallSession i, + ImsReasonInfo imsReasonInfo) throws RemoteException { + mNewListener.callSessionConferenceExtendFailed(imsReasonInfo); + } + + @Override + public void callSessionConferenceExtendReceived(IImsCallSession i, + IImsCallSession newSession, ImsCallProfile imsCallProfile) + throws RemoteException { + mNewListener.callSessionConferenceExtendReceived(newSession, imsCallProfile); + } + + @Override + public void callSessionInviteParticipantsRequestDelivered(IImsCallSession i) + throws RemoteException { + mNewListener.callSessionInviteParticipantsRequestDelivered(); + } + + @Override + public void callSessionInviteParticipantsRequestFailed(IImsCallSession i, + ImsReasonInfo imsReasonInfo) throws RemoteException { + mNewListener.callSessionInviteParticipantsRequestFailed(imsReasonInfo); + } + + @Override + public void callSessionRemoveParticipantsRequestDelivered(IImsCallSession i) + throws RemoteException { + mNewListener.callSessionRemoveParticipantsRequestDelivered(); + } + + @Override + public void callSessionRemoveParticipantsRequestFailed(IImsCallSession i, + ImsReasonInfo imsReasonInfo) throws RemoteException { + mNewListener.callSessionRemoveParticipantsRequestFailed(imsReasonInfo); + } + + @Override + public void callSessionConferenceStateUpdated(IImsCallSession i, + ImsConferenceState imsConferenceState) throws RemoteException { + mNewListener.callSessionConferenceStateUpdated(imsConferenceState); + } + + @Override + public void callSessionUssdMessageReceived(IImsCallSession i, int mode, String message) + throws RemoteException { + mNewListener.callSessionUssdMessageReceived(mode, message); + } + + @Override + public void callSessionHandover(IImsCallSession i, int srcAccessTech, int targetAccessTech, + ImsReasonInfo reasonInfo) throws RemoteException { + mNewListener.callSessionHandover(srcAccessTech, targetAccessTech, reasonInfo); + } + + @Override + public void callSessionHandoverFailed(IImsCallSession i, int srcAccessTech, + int targetAccessTech, ImsReasonInfo reasonInfo) throws RemoteException { + mNewListener.callSessionHandoverFailed(srcAccessTech, targetAccessTech, reasonInfo); + } + + @Override + public void callSessionMayHandover(IImsCallSession i, int srcAccessTech, int targetAccessTech) + throws RemoteException { + mNewListener.callSessionMayHandover(srcAccessTech, targetAccessTech); + } + + @Override + public void callSessionTtyModeReceived(IImsCallSession iImsCallSession, int mode) + throws RemoteException { + mNewListener.callSessionTtyModeReceived(mode); + } + + @Override + public void callSessionMultipartyStateChanged(IImsCallSession i, boolean isMultiparty) + throws RemoteException { + mNewListener.callSessionMultipartyStateChanged(isMultiparty); + } + + @Override + public void callSessionSuppServiceReceived(IImsCallSession i, + ImsSuppServiceNotification imsSuppServiceNotification) throws RemoteException { + mNewListener.callSessionSuppServiceReceived(imsSuppServiceNotification); + } + + @Override + public void callSessionRttModifyRequestReceived(IImsCallSession i, + ImsCallProfile imsCallProfile) throws RemoteException { + mNewListener.callSessionRttModifyRequestReceived(imsCallProfile); + } + + @Override + public void callSessionRttModifyResponseReceived(int status) throws RemoteException { + mNewListener.callSessionRttModifyResponseReceived(status); + } + + @Override + public void callSessionRttMessageReceived(String rttMessage) throws RemoteException { + mNewListener.callSessionRttMessageReceived(rttMessage); + } + } +} diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java index 93c316f3dcb..68d63dfbe32 100644 --- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java @@ -23,7 +23,6 @@ import android.telephony.ims.internal.stub.SmsImplBase; import com.android.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; -import com.android.ims.internal.IImsCallSessionListener; import com.android.ims.internal.IImsConfig; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMMTelFeature; @@ -110,10 +109,10 @@ public class MMTelFeature extends ImsFeature { } @Override - public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile, - IImsCallSessionListener listener) throws RemoteException { + public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile) + throws RemoteException { synchronized (mLock) { - return MMTelFeature.this.createCallSession(sessionId, profile, listener); + return MMTelFeature.this.createCallSession(sessionId, profile); } } @@ -326,10 +325,8 @@ public class MMTelFeature extends ImsFeature { * * @param sessionId a session id which is obtained from {@link #startSession} * @param profile a call profile to make the call - * @param listener An implementation of IImsCallSessionListener. */ - public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile, - IImsCallSessionListener listener) { + public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile) { return null; } diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl index e226adaac07..18afc6a23a4 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl +++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl @@ -19,7 +19,6 @@ package android.telephony.ims.internal.aidl; import android.os.Message; import android.telephony.ims.internal.aidl.IImsMmTelListener; import android.telephony.ims.internal.aidl.IImsCapabilityCallback; -import android.telephony.ims.internal.aidl.IImsCallSessionListener; import android.telephony.ims.internal.feature.CapabilityChangeRequest; import com.android.ims.ImsCallProfile; @@ -30,14 +29,14 @@ import com.android.ims.internal.IImsRegistrationListener; import com.android.ims.internal.IImsUt; /** - * See SmsImplBase for more information. + * See MmTelFeature for more information. * {@hide} */ interface IImsMmTelFeature { void setListener(IImsMmTelListener l); int getFeatureState(); ImsCallProfile createCallProfile(int callSessionType, int callType); - IImsCallSession createCallSession(in ImsCallProfile profile, IImsCallSessionListener listener); + IImsCallSession createCallSession(in ImsCallProfile profile); IImsUt getUtInterface(); IImsEcbm getEcbmInterface(); void setUiTtyMode(int uiTtyMode, in Message onCompleteMessage); @@ -49,4 +48,11 @@ interface IImsMmTelFeature { IImsCapabilityCallback c); oneway void queryCapabilityConfiguration(int capability, int radioTech, IImsCapabilityCallback c); + // SMS APIs + void setSmsListener(IImsSmsListener l); + oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry, + in byte[] pdu); + oneway void acknowledgeSms(int token, int messageRef, int result); + oneway void acknowledgeSmsReport(int token, int messageRef, int result); + String getSmsFormat(); } diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java index 9b576c72fa9..23e0302ab4f 100644 --- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java @@ -20,11 +20,10 @@ import android.annotation.IntDef; import android.os.Message; import android.os.RemoteException; import android.telecom.TelecomManager; -import android.telephony.ims.internal.ImsCallSessionListener; -import android.telephony.ims.internal.aidl.IImsCallSessionListener; import android.telephony.ims.internal.aidl.IImsCapabilityCallback; import android.telephony.ims.internal.aidl.IImsMmTelFeature; import android.telephony.ims.internal.aidl.IImsMmTelListener; +import android.telephony.ims.internal.stub.SmsImplBase; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.ImsEcbmImplBase; import android.telephony.ims.stub.ImsMultiEndpointImplBase; @@ -35,6 +34,7 @@ import com.android.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMultiEndpoint; +import com.android.ims.internal.IImsSmsListener; import com.android.ims.internal.IImsUt; import com.android.ims.internal.ImsCallSession; import com.android.internal.annotations.VisibleForTesting; @@ -80,11 +80,9 @@ public class MmTelFeature extends ImsFeature { } @Override - public IImsCallSession createCallSession(ImsCallProfile profile, - IImsCallSessionListener listener) throws RemoteException { + public IImsCallSession createCallSession(ImsCallProfile profile) throws RemoteException { synchronized (mLock) { - ImsCallSession s = MmTelFeature.this.createCallSession(profile, - new ImsCallSessionListener(listener)); + ImsCallSession s = MmTelFeature.this.createCallSession(profile); return s != null ? s.getSession() : null; } } @@ -143,6 +141,40 @@ public class MmTelFeature extends ImsFeature { IImsCapabilityCallback c) { queryCapabilityConfigurationInternal(capability, radioTech, c); } + + @Override + public void setSmsListener(IImsSmsListener l) throws RemoteException { + MmTelFeature.this.setSmsListener(l); + } + + @Override + public void sendSms(int token, int messageRef, String format, String smsc, boolean retry, + byte[] pdu) { + synchronized (mLock) { + MmTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu); + } + } + + @Override + public void acknowledgeSms(int token, int messageRef, int result) { + synchronized (mLock) { + MmTelFeature.this.acknowledgeSms(token, messageRef, result); + } + } + + @Override + public void acknowledgeSmsReport(int token, int messageRef, int result) { + synchronized (mLock) { + MmTelFeature.this.acknowledgeSmsReport(token, messageRef, result); + } + } + + @Override + public String getSmsFormat() { + synchronized (mLock) { + return MmTelFeature.this.getSmsFormat(); + } + } }; /** @@ -370,10 +402,8 @@ public class MmTelFeature extends ImsFeature { * {@link ImsCallSession} directly. * * @param profile a call profile to make the call - * @param listener An implementation of IImsCallSessionListener. */ - public ImsCallSession createCallSession(ImsCallProfile profile, - ImsCallSessionListener listener) { + public ImsCallSession createCallSession(ImsCallProfile profile) { // Base Implementation - Should be overridden return null; } @@ -415,6 +445,40 @@ public class MmTelFeature extends ImsFeature { // Base Implementation - Should be overridden } + public void setSmsListener(IImsSmsListener listener) { + getSmsImplementation().registerSmsListener(listener); + } + + public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, + byte[] pdu) { + getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu); + } + + public void acknowledgeSms(int token, int messageRef, + @SmsImplBase.DeliverStatusResult int result) { + getSmsImplementation().acknowledgeSms(token, messageRef, result); + } + + public void acknowledgeSmsReport(int token, int messageRef, + @SmsImplBase.StatusReportResult int result) { + getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); + } + + /** + * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default + * non-functional implementation is returned. + * + * @return an instance of {@link SmsImplBase} which should be implemented by the IMS + * Provider. + */ + public SmsImplBase getSmsImplementation() { + return new SmsImplBase(); + } + + private String getSmsFormat() { + return getSmsImplementation().getSmsFormat(); + } + /**{@inheritDoc}*/ @Override public void onFeatureRemoved() { diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java index 80b2f789e34..95243fdc5f1 100644 --- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java @@ -18,12 +18,13 @@ package android.telephony.ims.stub; import android.os.Message; import android.os.RemoteException; +import android.telephony.ims.ImsCallSessionListener; +import android.telephony.ims.aidl.IImsCallSessionListener; import com.android.ims.ImsCallProfile; import com.android.ims.ImsStreamMediaProfile; import com.android.ims.internal.ImsCallSession; import com.android.ims.internal.IImsCallSession; -import com.android.ims.internal.IImsCallSessionListener; import com.android.ims.internal.IImsVideoCallProvider; /** @@ -38,11 +39,26 @@ import com.android.ims.internal.IImsVideoCallProvider; public class ImsCallSessionImplBase extends IImsCallSession.Stub { + @Override + public final void setListener(IImsCallSessionListener listener) throws RemoteException { + setListener(new ImsCallSessionListener(listener)); + } + + /** + * Sets the listener to listen to the session events. An {@link ImsCallSession} + * can only hold one listener at a time. Subsequent calls to this method + * override the previous listener. + * + * @param listener to listen to the session events of this object + */ + public void setListener(ImsCallSessionListener listener) { + } + /** * Closes the object. This object is not usable after being closed. */ @Override - public void close() throws RemoteException { + public void close() { } @@ -52,7 +68,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @return the call ID */ @Override - public String getCallId() throws RemoteException { + public String getCallId() { return null; } @@ -62,7 +78,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @return the {@link ImsCallProfile} that this session is associated with */ @Override - public ImsCallProfile getCallProfile() throws RemoteException { + public ImsCallProfile getCallProfile() { return null; } @@ -72,7 +88,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @return the local {@link ImsCallProfile} that this session is associated with */ @Override - public ImsCallProfile getLocalCallProfile() throws RemoteException { + public ImsCallProfile getLocalCallProfile() { return null; } @@ -82,7 +98,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @return the remote {@link ImsCallProfile} that this session is associated with */ @Override - public ImsCallProfile getRemoteCallProfile() throws RemoteException { + public ImsCallProfile getRemoteCallProfile() { return null; } @@ -92,7 +108,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @return the string value associated with the specified property */ @Override - public String getProperty(String name) throws RemoteException { + public String getProperty(String name) { return null; } @@ -103,7 +119,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @return the session state */ @Override - public int getState() throws RemoteException { + public int getState() { return ImsCallSession.State.INVALID; } @@ -113,28 +129,17 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @return true if the session is in call, false otherwise */ @Override - public boolean isInCall() throws RemoteException { + public boolean isInCall() { return false; } - /** - * Sets the listener to listen to the session events. An {@link ImsCallSession} - * can only hold one listener at a time. Subsequent calls to this method - * override the previous listener. - * - * @param listener to listen to the session events of this object - */ - @Override - public void setListener(IImsCallSessionListener listener) throws RemoteException { - } - /** * Mutes or unmutes the mic for the active call. * * @param muted true if the call is muted, false otherwise */ @Override - public void setMute(boolean muted) throws RemoteException { + public void setMute(boolean muted) { } /** @@ -150,7 +155,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * {@link ImsCallSession.Listener#callSessionStartFailed} */ @Override - public void start(String callee, ImsCallProfile profile) throws RemoteException { + public void start(String callee, ImsCallProfile profile) { } /** @@ -166,8 +171,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * {@link ImsCallSession.Listener#callSessionStartFailed} */ @Override - public void startConference(String[] participants, ImsCallProfile profile) - throws RemoteException { + public void startConference(String[] participants, ImsCallProfile profile) { } /** @@ -178,7 +182,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionStarted} */ @Override - public void accept(int callType, ImsStreamMediaProfile profile) throws RemoteException { + public void accept(int callType, ImsStreamMediaProfile profile) { } /** @@ -189,7 +193,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * {@link ImsCallSession.Listener#callSessionStartFailed} */ @Override - public void reject(int reason) throws RemoteException { + public void reject(int reason) { } /** @@ -201,7 +205,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionTerminated} */ @Override - public void terminate(int reason) throws RemoteException { + public void terminate(int reason) { } /** @@ -213,7 +217,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * {@link ImsCallSession.Listener#callSessionHoldFailed} */ @Override - public void hold(ImsStreamMediaProfile profile) throws RemoteException { + public void hold(ImsStreamMediaProfile profile) { } /** @@ -225,7 +229,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * {@link ImsCallSession.Listener#callSessionResumeFailed} */ @Override - public void resume(ImsStreamMediaProfile profile) throws RemoteException { + public void resume(ImsStreamMediaProfile profile) { } /** @@ -240,7 +244,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * {@link ImsCallSession.Listener#callSessionMergeFailed} */ @Override - public void merge() throws RemoteException { + public void merge() { } /** @@ -252,7 +256,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * {@link ImsCallSession.Listener#callSessionUpdateFailed} */ @Override - public void update(int callType, ImsStreamMediaProfile profile) throws RemoteException { + public void update(int callType, ImsStreamMediaProfile profile) { } /** @@ -264,7 +268,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * {@link ImsCallSession.Listener#callSessionConferenceExtendFailed} */ @Override - public void extendToConference(String[] participants) throws RemoteException { + public void extendToConference(String[] participants) { } /** @@ -286,7 +290,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestFailed} */ @Override - public void removeParticipants(String[] participants) throws RemoteException { + public void removeParticipants(String[] participants) { } /** @@ -297,7 +301,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. */ @Override - public void sendDtmf(char c, Message result) throws RemoteException { + public void sendDtmf(char c, Message result) { } /** @@ -308,14 +312,14 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. */ @Override - public void startDtmf(char c) throws RemoteException { + public void startDtmf(char c) { } /** * Stop a DTMF code. */ @Override - public void stopDtmf() throws RemoteException { + public void stopDtmf() { } /** @@ -324,7 +328,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @param ussdMessage USSD message to send */ @Override - public void sendUssd(String ussdMessage) throws RemoteException { + public void sendUssd(String ussdMessage) { } /** @@ -333,7 +337,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * intermediates between the propriety implementation and Telecomm/InCall. */ @Override - public IImsVideoCallProvider getVideoCallProvider() throws RemoteException { + public IImsVideoCallProvider getVideoCallProvider() { return null; } @@ -342,7 +346,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @return {@code True} if the session is multiparty. */ @Override - public boolean isMultiparty() throws RemoteException { + public boolean isMultiparty() { return false; } diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java deleted file mode 100644 index 6c18935d16e..00000000000 --- a/telephony/java/android/telephony/ims/stub/ImsCallSessionListenerImplBase.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * 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.telephony.ims.stub; - -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsConferenceState; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.ImsSuppServiceNotification; -import com.android.ims.internal.IImsCallSession; -import com.android.ims.internal.IImsCallSessionListener; - -/** - * Base implementation of ImsCallSessionListenerBase, which implements stub versions of the methods - * in the IImsCallSessionListener AIDL. Override the methods that your implementation of - * ImsCallSessionListener supports. - * - * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you - * will break other implementations of ImsCallSessionListener maintained by other ImsServices. - * - * @hide - */ -public class ImsCallSessionListenerImplBase extends IImsCallSessionListener.Stub { - /** - * Notifies the result of the basic session operation (setup / terminate). - */ - @Override - public void callSessionProgressing(IImsCallSession session, ImsStreamMediaProfile profile) { - // no-op - } - - @Override - public void callSessionStarted(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionStartFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionTerminated(IImsCallSession session, ImsReasonInfo reasonInfo) { - // no-op - } - - /** - * Notifies the result of the call hold/resume operation. - */ - @Override - public void callSessionHeld(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionHoldFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionHoldReceived(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionResumed(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionResumeFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionResumeReceived(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - /** - * Notifies the result of call merge operation. - */ - @Override - public void callSessionMergeStarted(IImsCallSession session, IImsCallSession newSession, - ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionMergeComplete(IImsCallSession session) { - // no-op - } - - @Override - public void callSessionMergeFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { - // no-op - } - - /** - * Notifies the result of call upgrade / downgrade or any other call - * updates. - */ - @Override - public void callSessionUpdated(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionUpdateFailed(IImsCallSession session, ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionUpdateReceived(IImsCallSession session, ImsCallProfile profile) { - // no-op - } - - /** - * Notifies the result of conference extension. - */ - @Override - public void callSessionConferenceExtended(IImsCallSession session, IImsCallSession newSession, - ImsCallProfile profile) { - // no-op - } - - @Override - public void callSessionConferenceExtendFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionConferenceExtendReceived(IImsCallSession session, - IImsCallSession newSession, - ImsCallProfile profile) { - // no-op - } - - /** - * Notifies the result of the participant invitation / removal to/from the - * conference session. - */ - @Override - public void callSessionInviteParticipantsRequestDelivered(IImsCallSession session) { - // no-op - } - - @Override - public void callSessionInviteParticipantsRequestFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionRemoveParticipantsRequestDelivered(IImsCallSession session) { - // no-op - } - - @Override - public void callSessionRemoveParticipantsRequestFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { - // no-op - } - - /** - * Notifies the changes of the conference info. the conference session. - */ - @Override - public void callSessionConferenceStateUpdated(IImsCallSession session, - ImsConferenceState state) { - // no-op - } - - /** - * Notifies the incoming USSD message. - */ - @Override - public void callSessionUssdMessageReceived(IImsCallSession session, int mode, - String ussdMessage) { - // no-op - } - - /** - * Notifies of a case where a {@link com.android.ims.internal.ImsCallSession} may potentially - * handover from one radio technology to another. - * @param session - * @param srcAccessTech The source radio access technology; one of the access technology - * constants defined in {@link android.telephony.ServiceState}. For - * example {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. - * @param targetAccessTech The target radio access technology; one of the access technology - * constants defined in {@link android.telephony.ServiceState}. For - * example {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. - */ - @Override - public void callSessionMayHandover(IImsCallSession session, int srcAccessTech, - int targetAccessTech) { - // no-op - } - - /** - * Notifies of handover information for this call - */ - @Override - public void callSessionHandover(IImsCallSession session, int srcAccessTech, - int targetAccessTech, - ImsReasonInfo reasonInfo) { - // no-op - } - - @Override - public void callSessionHandoverFailed(IImsCallSession session, int srcAccessTech, - int targetAccessTech, - ImsReasonInfo reasonInfo) { - // no-op - } - - /** - * Notifies the TTY mode change by remote party. - * - * @param mode one of the following: - - * {@link com.android.internal.telephony.Phone#TTY_MODE_OFF} - - * {@link com.android.internal.telephony.Phone#TTY_MODE_FULL} - - * {@link com.android.internal.telephony.Phone#TTY_MODE_HCO} - - * {@link com.android.internal.telephony.Phone#TTY_MODE_VCO} - */ - @Override - public void callSessionTtyModeReceived(IImsCallSession session, int mode) { - // no-op - } - - /** - * Notifies of a change to the multiparty state for this - * {@code ImsCallSession}. - * - * @param session The call session. - * @param isMultiParty {@code true} if the session became multiparty, - * {@code false} otherwise. - */ - @Override - public void callSessionMultipartyStateChanged(IImsCallSession session, boolean isMultiParty) { - // no-op - } - - /** - * Notifies the supplementary service information for the current session. - */ - @Override - public void callSessionSuppServiceReceived(IImsCallSession session, - ImsSuppServiceNotification suppSrvNotification) { - // no-op - } - - /** - * Received RTT modify request from Remote Party - * @param session The call session. - * @param callProfile ImsCallProfile with updated attribute - */ - @Override - public void callSessionRttModifyRequestReceived(IImsCallSession session, - ImsCallProfile callProfile) { - // no-op - } - - /** - * Received response for RTT modify request - * @param status true : Accepted the request - * false : Declined the request - */ - @Override - public void callSessionRttModifyResponseReceived(int status) { - // no -op - } - - /** - * Device received RTT message from Remote UE - * @param rttMessage RTT message received - */ - @Override - public void callSessionRttMessageReceived(String rttMessage) { - // no-op - } -} - diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl index c6fc5e563bf..b477deab2d5 100644 --- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl +++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl @@ -17,9 +17,10 @@ package com.android.ims.internal; import android.os.Message; +import android.telephony.ims.aidl.IImsCallSessionListener; + import com.android.ims.ImsCallProfile; import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.internal.IImsCallSessionListener; import com.android.ims.internal.IImsVideoCallProvider; /** diff --git a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl index 10c7f3e8a2d..290a6824025 100644 --- a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl +++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl @@ -20,7 +20,6 @@ import android.app.PendingIntent; import com.android.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; -import com.android.ims.internal.IImsCallSessionListener; import com.android.ims.internal.IImsConfig; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMultiEndpoint; @@ -44,8 +43,7 @@ interface IImsMMTelFeature { void addRegistrationListener(in IImsRegistrationListener listener); void removeRegistrationListener(in IImsRegistrationListener listener); ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType); - IImsCallSession createCallSession(int sessionId, in ImsCallProfile profile, - IImsCallSessionListener listener); + IImsCallSession createCallSession(int sessionId, in ImsCallProfile profile); IImsCallSession getPendingCallSession(int sessionId, String callId); IImsUt getUtInterface(); IImsConfig getConfigInterface(); diff --git a/telephony/java/com/android/ims/internal/ImsCallSession.java b/telephony/java/com/android/ims/internal/ImsCallSession.java index 1736b80c562..e914f484fc7 100644 --- a/telephony/java/com/android/ims/internal/ImsCallSession.java +++ b/telephony/java/com/android/ims/internal/ImsCallSession.java @@ -18,10 +18,10 @@ package com.android.ims.internal; import android.os.Message; import android.os.RemoteException; +import android.telephony.ims.aidl.IImsCallSessionListener; import java.util.Objects; -import android.telephony.ims.stub.ImsCallSessionListenerImplBase; import android.util.Log; import com.android.ims.ImsCallProfile; import com.android.ims.ImsConferenceState; @@ -987,7 +987,6 @@ public class ImsCallSession { * Sends Rtt Message * * @param rttMessage rtt text to be sent - * @throws ImsException if call is absent */ public void sendRttMessage(String rttMessage) { if (mClosed) { @@ -1004,7 +1003,6 @@ public class ImsCallSession { * Sends RTT Upgrade request * * @param to : expected profile - * @throws CallStateException */ public void sendRttModifyRequest(ImsCallProfile to) { if (mClosed) { @@ -1021,7 +1019,6 @@ public class ImsCallSession { * Sends RTT Upgrade response * * @param response : response for upgrade - * @throws CallStateException */ public void sendRttModifyResponse(boolean response) { if (mClosed) { @@ -1040,37 +1037,33 @@ public class ImsCallSession { * the application is notified by having one of the methods called on * the {@link IImsCallSessionListener}. */ - private class IImsCallSessionListenerProxy extends ImsCallSessionListenerImplBase { + private class IImsCallSessionListenerProxy extends IImsCallSessionListener.Stub { /** * Notifies the result of the basic session operation (setup / terminate). */ @Override - public void callSessionProgressing(IImsCallSession session, - ImsStreamMediaProfile profile) { + public void callSessionProgressing(ImsStreamMediaProfile profile) { if (mListener != null) { mListener.callSessionProgressing(ImsCallSession.this, profile); } } @Override - public void callSessionStarted(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionInitiated(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionStarted(ImsCallSession.this, profile); } } @Override - public void callSessionStartFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionStartFailed(ImsCallSession.this, reasonInfo); } } @Override - public void callSessionTerminated(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionTerminated(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionTerminated(ImsCallSession.this, reasonInfo); } @@ -1080,48 +1073,42 @@ public class ImsCallSession { * Notifies the result of the call hold/resume operation. */ @Override - public void callSessionHeld(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionHeld(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionHeld(ImsCallSession.this, profile); } } @Override - public void callSessionHoldFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionHoldFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionHoldFailed(ImsCallSession.this, reasonInfo); } } @Override - public void callSessionHoldReceived(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionHoldReceived(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionHoldReceived(ImsCallSession.this, profile); } } @Override - public void callSessionResumed(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionResumed(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionResumed(ImsCallSession.this, profile); } } @Override - public void callSessionResumeFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionResumeFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionResumeFailed(ImsCallSession.this, reasonInfo); } } @Override - public void callSessionResumeReceived(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionResumeReceived(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionResumeReceived(ImsCallSession.this, profile); } @@ -1130,13 +1117,11 @@ public class ImsCallSession { /** * Notifies the start of a call merge operation. * - * @param session The call session. * @param newSession The merged call session. * @param profile The call profile. */ @Override - public void callSessionMergeStarted(IImsCallSession session, - IImsCallSession newSession, ImsCallProfile profile) { + public void callSessionMergeStarted(IImsCallSession newSession, ImsCallProfile profile) { // This callback can be used for future use to add additional // functionality that may be needed between conference start and complete Log.d(TAG, "callSessionMergeStarted"); @@ -1173,12 +1158,10 @@ public class ImsCallSession { /** * Notifies of a failure to perform a call merge operation. * - * @param session The call session. * @param reasonInfo The merge failure reason. */ @Override - public void callSessionMergeFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionMergeFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionMergeFailed(ImsCallSession.this, reasonInfo); } @@ -1188,24 +1171,21 @@ public class ImsCallSession { * Notifies the result of call upgrade / downgrade or any other call updates. */ @Override - public void callSessionUpdated(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionUpdated(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionUpdated(ImsCallSession.this, profile); } } @Override - public void callSessionUpdateFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionUpdateFailed(ImsCallSession.this, reasonInfo); } } @Override - public void callSessionUpdateReceived(IImsCallSession session, - ImsCallProfile profile) { + public void callSessionUpdateReceived(ImsCallProfile profile) { if (mListener != null) { mListener.callSessionUpdateReceived(ImsCallSession.this, profile); } @@ -1215,8 +1195,8 @@ public class ImsCallSession { * Notifies the result of conference extension. */ @Override - public void callSessionConferenceExtended(IImsCallSession session, - IImsCallSession newSession, ImsCallProfile profile) { + public void callSessionConferenceExtended(IImsCallSession newSession, + ImsCallProfile profile) { if (mListener != null) { mListener.callSessionConferenceExtended(ImsCallSession.this, new ImsCallSession(newSession), profile); @@ -1224,16 +1204,15 @@ public class ImsCallSession { } @Override - public void callSessionConferenceExtendFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionConferenceExtendFailed(ImsCallSession.this, reasonInfo); } } @Override - public void callSessionConferenceExtendReceived(IImsCallSession session, - IImsCallSession newSession, ImsCallProfile profile) { + public void callSessionConferenceExtendReceived(IImsCallSession newSession, + ImsCallProfile profile) { if (mListener != null) { mListener.callSessionConferenceExtendReceived(ImsCallSession.this, new ImsCallSession(newSession), profile); @@ -1245,15 +1224,14 @@ public class ImsCallSession { * the conference session. */ @Override - public void callSessionInviteParticipantsRequestDelivered(IImsCallSession session) { + public void callSessionInviteParticipantsRequestDelivered() { if (mListener != null) { mListener.callSessionInviteParticipantsRequestDelivered(ImsCallSession.this); } } @Override - public void callSessionInviteParticipantsRequestFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionInviteParticipantsRequestFailed(ImsCallSession.this, reasonInfo); @@ -1261,15 +1239,14 @@ public class ImsCallSession { } @Override - public void callSessionRemoveParticipantsRequestDelivered(IImsCallSession session) { + public void callSessionRemoveParticipantsRequestDelivered() { if (mListener != null) { mListener.callSessionRemoveParticipantsRequestDelivered(ImsCallSession.this); } } @Override - public void callSessionRemoveParticipantsRequestFailed(IImsCallSession session, - ImsReasonInfo reasonInfo) { + public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionRemoveParticipantsRequestFailed(ImsCallSession.this, reasonInfo); @@ -1280,8 +1257,7 @@ public class ImsCallSession { * Notifies the changes of the conference info. in the conference session. */ @Override - public void callSessionConferenceStateUpdated(IImsCallSession session, - ImsConferenceState state) { + public void callSessionConferenceStateUpdated(ImsConferenceState state) { if (mListener != null) { mListener.callSessionConferenceStateUpdated(ImsCallSession.this, state); } @@ -1291,8 +1267,7 @@ public class ImsCallSession { * Notifies the incoming USSD message. */ @Override - public void callSessionUssdMessageReceived(IImsCallSession session, - int mode, String ussdMessage) { + public void callSessionUssdMessageReceived(int mode, String ussdMessage) { if (mListener != null) { mListener.callSessionUssdMessageReceived(ImsCallSession.this, mode, ussdMessage); } @@ -1301,7 +1276,6 @@ public class ImsCallSession { /** * Notifies of a case where a {@link com.android.ims.internal.ImsCallSession} may * potentially handover from one radio technology to another. - * @param session * @param srcAccessTech The source radio access technology; one of the access technology * constants defined in {@link android.telephony.ServiceState}. For * example @@ -1312,8 +1286,7 @@ public class ImsCallSession { * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. */ @Override - public void callSessionMayHandover(IImsCallSession session, - int srcAccessTech, int targetAccessTech) { + public void callSessionMayHandover(int srcAccessTech, int targetAccessTech) { if (mListener != null) { mListener.callSessionMayHandover(ImsCallSession.this, srcAccessTech, targetAccessTech); @@ -1324,9 +1297,8 @@ public class ImsCallSession { * Notifies of handover information for this call */ @Override - public void callSessionHandover(IImsCallSession session, - int srcAccessTech, int targetAccessTech, - ImsReasonInfo reasonInfo) { + public void callSessionHandover(int srcAccessTech, int targetAccessTech, + ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionHandover(ImsCallSession.this, srcAccessTech, targetAccessTech, reasonInfo); @@ -1337,9 +1309,8 @@ public class ImsCallSession { * Notifies of handover failure info for this call */ @Override - public void callSessionHandoverFailed(IImsCallSession session, - int srcAccessTech, int targetAccessTech, - ImsReasonInfo reasonInfo) { + public void callSessionHandoverFailed(int srcAccessTech, int targetAccessTech, + ImsReasonInfo reasonInfo) { if (mListener != null) { mListener.callSessionHandoverFailed(ImsCallSession.this, srcAccessTech, targetAccessTech, reasonInfo); @@ -1350,8 +1321,7 @@ public class ImsCallSession { * Notifies the TTY mode received from remote party. */ @Override - public void callSessionTtyModeReceived(IImsCallSession session, - int mode) { + public void callSessionTtyModeReceived(int mode) { if (mListener != null) { mListener.callSessionTtyModeReceived(ImsCallSession.this, mode); } @@ -1360,21 +1330,17 @@ public class ImsCallSession { /** * Notifies of a change to the multiparty state for this {@code ImsCallSession}. * - * @param session The call session. * @param isMultiParty {@code true} if the session became multiparty, {@code false} * otherwise. */ - public void callSessionMultipartyStateChanged(IImsCallSession session, - boolean isMultiParty) { - + public void callSessionMultipartyStateChanged(boolean isMultiParty) { if (mListener != null) { mListener.callSessionMultipartyStateChanged(ImsCallSession.this, isMultiParty); } } @Override - public void callSessionSuppServiceReceived(IImsCallSession session, - ImsSuppServiceNotification suppServiceInfo ) { + public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppServiceInfo ) { if (mListener != null) { mListener.callSessionSuppServiceReceived(ImsCallSession.this, suppServiceInfo); } @@ -1384,8 +1350,7 @@ public class ImsCallSession { * Received RTT modify request from remote party */ @Override - public void callSessionRttModifyRequestReceived(IImsCallSession session, - ImsCallProfile callProfile) { + public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) { if (mListener != null) { mListener.callSessionRttModifyRequestReceived(ImsCallSession.this, callProfile); } -- GitLab From 112c36d39f3668f5c90edc818045c4318957d018 Mon Sep 17 00:00:00 2001 From: Brad Ebinger Date: Tue, 16 Jan 2018 09:33:47 -0800 Subject: [PATCH 015/416] Integrate new MMTel APIs into the framework Performs the bulk of the work of: 1) Moving the old MMTel APIs to a hidden .compat namespace to support older vendor versions of the code. 2) Replace the compat MMTel APIs with the new ImsService APIs and integrate them into existing code. This is one of two CLs, this CL integrates the new APIs, the next CL creates the compat layer in telephony to translate the .compat APIs to the new APIs to allow Telephony to work with older versions of the API. Before commit, the corresponding vendor changes will have to be submitted as well. Bug: 63987047 Test: Telephony Unit tests Change-Id: Icc9ecfdad000f42399beeac142083e62962c12d3 --- Android.bp | 23 +- .../android/telephony/TelephonyManager.java | 81 +-- .../android/telephony/ims/ImsService.java | 213 ++++++-- .../aidl/IImsCapabilityCallback.aidl | 2 +- .../ims/{internal => }/aidl/IImsConfig.aidl | 4 +- .../aidl/IImsConfigCallback.aidl | 2 +- .../{internal => }/aidl/IImsMmTelFeature.aidl | 10 +- .../aidl/IImsMmTelListener.aidl | 6 +- .../{internal => }/aidl/IImsRcsFeature.aidl | 2 +- .../telephony/ims/aidl}/IImsRegistration.aidl | 4 +- .../ims/aidl}/IImsRegistrationCallback.aidl | 4 +- .../aidl/IImsServiceController.aidl | 16 +- .../aidl/IImsServiceControllerListener.aidl | 4 +- .../telephony/ims/aidl}/IImsSmsListener.aidl | 4 +- .../telephony/ims/compat/ImsService.java | 237 +++++++++ .../ims/compat/feature/ImsFeature.java | 202 ++++++++ .../ims/compat/feature/MMTelFeature.java | 325 +++++++++++- .../feature/RcsFeature.java | 18 +- .../ims/compat/stub/ImsConfigImplBase.java | 150 ++++++ .../feature/CapabilityChangeRequest.aidl | 2 +- .../feature/CapabilityChangeRequest.java | 7 +- .../telephony/ims/feature/ImsFeature.java | 353 +++++++++++-- .../telephony/ims/feature/MMTelFeature.java | 439 ----------------- .../{internal => }/feature/MmTelFeature.java | 122 ++++- .../telephony/ims/feature/RcsFeature.java | 15 +- .../telephony/ims/internal/ImsService.java | 339 ------------- .../ims/internal/feature/ImsFeature.java | 462 ------------------ .../ims/internal/stub/ImsConfigImplBase.java | 173 ------- .../telephony/ims/stub/ImsConfigImplBase.java | 419 ++++++++-------- .../stub/ImsFeatureConfiguration.aidl | 2 +- .../stub/ImsFeatureConfiguration.java | 10 +- .../ims/stub/ImsRegistrationImplBase.java | 5 +- .../ImsSmsImplBase.java} | 7 +- telephony/java/com/android/ims/ImsConfig.java | 160 +++--- .../internal/IImsFeatureStatusCallback.aidl | 25 - .../ims/internal/IImsMMTelFeature.aidl | 9 - .../ims/internal/IImsServiceController.aidl | 2 - .../telephony/ExponentialBackoff.java | 44 +- .../internal/telephony/ITelephony.aidl | 33 +- 39 files changed, 1952 insertions(+), 1983 deletions(-) rename telephony/java/android/telephony/ims/{internal => }/aidl/IImsCapabilityCallback.aidl (95%) rename telephony/java/android/telephony/ims/{internal => }/aidl/IImsConfig.aidl (92%) rename telephony/java/android/telephony/ims/{internal => }/aidl/IImsConfigCallback.aidl (95%) rename telephony/java/android/telephony/ims/{internal => }/aidl/IImsMmTelFeature.aidl (87%) rename telephony/java/android/telephony/ims/{internal => }/aidl/IImsMmTelListener.aidl (86%) rename telephony/java/android/telephony/ims/{internal => }/aidl/IImsRcsFeature.aidl (94%) rename telephony/java/{com/android/ims/internal => android/telephony/ims/aidl}/IImsRegistration.aidl (90%) rename telephony/java/{com/android/ims/internal => android/telephony/ims/aidl}/IImsRegistrationCallback.aidl (91%) rename telephony/java/android/telephony/ims/{internal => }/aidl/IImsServiceController.aidl (77%) rename telephony/java/android/telephony/ims/{internal => }/aidl/IImsServiceControllerListener.aidl (87%) rename telephony/java/{com/android/ims/internal => android/telephony/ims/aidl}/IImsSmsListener.aidl (92%) create mode 100644 telephony/java/android/telephony/ims/compat/ImsService.java create mode 100644 telephony/java/android/telephony/ims/compat/feature/ImsFeature.java rename telephony/java/android/telephony/ims/{internal => compat}/feature/RcsFeature.java (76%) create mode 100644 telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java rename telephony/java/android/telephony/ims/{internal => }/feature/CapabilityChangeRequest.aidl (93%) rename telephony/java/android/telephony/ims/{internal => }/feature/CapabilityChangeRequest.java (97%) delete mode 100644 telephony/java/android/telephony/ims/feature/MMTelFeature.java rename telephony/java/android/telephony/ims/{internal => }/feature/MmTelFeature.java (81%) delete mode 100644 telephony/java/android/telephony/ims/internal/ImsService.java delete mode 100644 telephony/java/android/telephony/ims/internal/feature/ImsFeature.java delete mode 100644 telephony/java/android/telephony/ims/internal/stub/ImsConfigImplBase.java rename telephony/java/android/telephony/ims/{internal => }/stub/ImsFeatureConfiguration.aidl (93%) rename telephony/java/android/telephony/ims/{internal => }/stub/ImsFeatureConfiguration.java (94%) rename telephony/java/android/telephony/ims/{internal/stub/SmsImplBase.java => stub/ImsSmsImplBase.java} (98%) delete mode 100644 telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl diff --git a/Android.bp b/Android.bp index 3b87f44b300..f3ab960ff33 100644 --- a/Android.bp +++ b/Android.bp @@ -477,15 +477,17 @@ java_library { "telephony/java/android/telephony/data/IDataService.aidl", "telephony/java/android/telephony/data/IDataServiceCallback.aidl", "telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsConfigCallback.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl", + "telephony/java/android/telephony/ims/aidl/IImsCapabilityCallback.aidl", + "telephony/java/android/telephony/ims/aidl/IImsConfig.aidl", + "telephony/java/android/telephony/ims/aidl/IImsConfigCallback.aidl", + "telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl", + "telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl", + "telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl", + "telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl", + "telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl", + "telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl", + "telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl", + "telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl", "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl", "telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl", @@ -504,13 +506,10 @@ java_library { "telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl", "telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl", "telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl", - "telephony/java/com/android/ims/internal/IImsRegistration.aidl", - "telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl", "telephony/java/com/android/ims/internal/IImsRcsFeature.aidl", "telephony/java/com/android/ims/internal/IImsService.aidl", "telephony/java/com/android/ims/internal/IImsServiceController.aidl", "telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl", - "telephony/java/com/android/ims/internal/IImsSmsListener.aidl", "telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl", "telephony/java/com/android/ims/internal/IImsUt.aidl", "telephony/java/com/android/ims/internal/IImsUtListener.aidl", diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 0a6d960421b..0239fcf8ac0 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -48,12 +48,13 @@ import android.telecom.PhoneAccount; import android.telecom.PhoneAccountHandle; import android.telecom.TelecomManager; import android.telephony.VisualVoicemailService.VisualVoicemailTask; +import android.telephony.ims.aidl.IImsConfig; +import android.telephony.ims.aidl.IImsMmTelFeature; +import android.telephony.ims.aidl.IImsRcsFeature; +import android.telephony.ims.aidl.IImsRegistration; import android.telephony.ims.feature.ImsFeature; import android.util.Log; -import com.android.ims.internal.IImsMMTelFeature; -import com.android.ims.internal.IImsRcsFeature; -import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telecom.ITelecomService; @@ -5094,57 +5095,60 @@ public class TelephonyManager { } } - /** @hide */ - @IntDef({ImsFeature.EMERGENCY_MMTEL, ImsFeature.MMTEL, ImsFeature.RCS}) - @Retention(RetentionPolicy.SOURCE) - public @interface Feature {} + /** + * Enables IMS for the framework. This will trigger IMS registration and ImsFeature capability + * status updates, if not already enabled. + * @hide + */ + public void enableIms(int slotId) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.enableIms(slotId); + } + } catch (RemoteException e) { + Rlog.e(TAG, "enableIms, RemoteException: " + + e.getMessage()); + } + } /** - * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id and MMTel - * feature or {@link null} if the service is not available. If an MMTelFeature is available, the - * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates. - * @param slotIndex The SIM slot that we are requesting the {@link IImsMMTelFeature} for. - * @param callback Listener that will send updates to ImsManager when there are updates to - * ImsServiceController. - * @return {@link IImsMMTelFeature} interface for the feature specified or {@code null} if - * it is unavailable. + * Disables IMS for the framework. This will trigger IMS de-registration and trigger ImsFeature + * status updates to disabled. * @hide */ - public @Nullable IImsMMTelFeature getImsMMTelFeatureAndListen(int slotIndex, - IImsServiceFeatureCallback callback) { + public void disableIms(int slotId) { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.getMMTelFeatureAndListen(slotIndex, callback); + telephony.disableIms(slotId); } } catch (RemoteException e) { - Rlog.e(TAG, "getImsMMTelFeatureAndListen, RemoteException: " + Rlog.e(TAG, "disableIms, RemoteException: " + e.getMessage()); } - return null; } /** - * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id and MMTel - * feature for emergency calling or {@link null} if the service is not available. If an - * MMTelFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a - * listener for feature updates. - * @param slotIndex The SIM slot that we are requesting the {@link IImsMMTelFeature} for. + * Returns the {@link IImsMmTelFeature} that corresponds to the given slot Id and MMTel + * feature or {@link null} if the service is not available. If an MMTelFeature is available, the + * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates. + * @param slotIndex The SIM slot that we are requesting the {@link IImsMmTelFeature} for. * @param callback Listener that will send updates to ImsManager when there are updates to * ImsServiceController. - * @return {@link IImsMMTelFeature} interface for the feature specified or {@code null} if + * @return {@link IImsMmTelFeature} interface for the feature specified or {@code null} if * it is unavailable. * @hide */ - public @Nullable IImsMMTelFeature getImsEmergencyMMTelFeatureAndListen(int slotIndex, + public @Nullable IImsMmTelFeature getImsMmTelFeatureAndListen(int slotIndex, IImsServiceFeatureCallback callback) { try { ITelephony telephony = getITelephony(); if (telephony != null) { - return telephony.getEmergencyMMTelFeatureAndListen(slotIndex, callback); + return telephony.getMmTelFeatureAndListen(slotIndex, callback); } } catch (RemoteException e) { - Rlog.e(TAG, "getImsEmergencyMMTelFeatureAndListen, RemoteException: " + Rlog.e(TAG, "getImsMmTelFeatureAndListen, RemoteException: " + e.getMessage()); } return null; @@ -5195,6 +5199,25 @@ public class TelephonyManager { return null; } + /** + * @return the {@IImsConfig} interface that corresponds with the slot index and feature. + * @param slotIndex The SIM slot corresponding to the ImsService ImsConfig is active for. + * @param feature An integer indicating the feature that we wish to get the ImsConfig for. + * Corresponds to features defined in ImsFeature. + * @hide + */ + public @Nullable IImsConfig getImsConfig(int slotIndex, int feature) { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getImsConfig(slotIndex, feature); + } + } catch (RemoteException e) { + Rlog.e(TAG, "getImsRegistration, RemoteException: " + e.getMessage()); + } + return null; + } + /** * Set IMS registration state * diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java index aaa0f08594d..2f32bae88f0 100644 --- a/telephony/java/android/telephony/ims/ImsService.java +++ b/telephony/java/android/telephony/ims/ImsService.java @@ -16,25 +16,28 @@ package android.telephony.ims; -import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.telephony.CarrierConfigManager; +import android.telephony.ims.aidl.IImsConfig; +import android.telephony.ims.aidl.IImsMmTelFeature; +import android.telephony.ims.aidl.IImsRcsFeature; +import android.telephony.ims.aidl.IImsRegistration; +import android.telephony.ims.aidl.IImsServiceController; +import android.telephony.ims.aidl.IImsServiceControllerListener; import android.telephony.ims.feature.ImsFeature; -import android.telephony.ims.feature.MMTelFeature; +import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.feature.RcsFeature; +import android.telephony.ims.stub.ImsConfigImplBase; +import android.telephony.ims.stub.ImsFeatureConfiguration; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; import android.util.SparseArray; import com.android.ims.internal.IImsFeatureStatusCallback; -import com.android.ims.internal.IImsMMTelFeature; -import com.android.ims.internal.IImsRcsFeature; -import com.android.ims.internal.IImsRegistration; -import com.android.ims.internal.IImsServiceController; import com.android.internal.annotations.VisibleForTesting; import static android.Manifest.permission.MODIFY_PHONE_STATE; @@ -62,14 +65,11 @@ import static android.Manifest.permission.MODIFY_PHONE_STATE; * 1) Defined as the default ImsService for the device in the device overlay using * "config_ims_package". * 2) Defined as a Carrier Provided ImsService in the Carrier Configuration using - * {@link CarrierConfigManager#KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING}. + * {@link CarrierConfigManager#KEY_CTONFIG_IMS_PACKAGE_OVERRIDE_STRING}. * * The features that are currently supported in an ImsService are: * - RCS_FEATURE: This ImsService implements the RcsFeature class. - * - MMTEL_FEATURE: This ImsService implements the MMTelFeature class. - * - EMERGENCY_MMTEL_FEATURE: This ImsService implements the MMTelFeature class and will be - * available to place emergency calls at all times. This MUST be implemented by the default - * ImsService provided in the device overlay. + * - MMTEL_FEATURE: This ImsService implements the MmTelFeature class. * @hide */ @SystemApi @@ -89,20 +89,36 @@ public class ImsService extends Service { // call ImsFeature#onFeatureRemoved. private final SparseArray> mFeaturesBySlot = new SparseArray<>(); + private IImsServiceControllerListener mListener; + + /** + * Listener that notifies the framework of ImsService changes. * @hide */ - protected final IBinder mImsServiceController = new IImsServiceController.Stub() { + public static class Listener extends IImsServiceControllerListener.Stub { + /** + * The IMS features that this ImsService supports has changed. + * @param c a new {@link ImsFeatureConfiguration} containing {@link ImsFeature.FeatureType}s + * that this ImsService supports. This may trigger the addition/removal of feature + * in this service. + */ + public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) { + } + } + /** + * @hide + */ + protected final IBinder mImsServiceController = new IImsServiceController.Stub() { @Override - public IImsMMTelFeature createEmergencyMMTelFeature(int slotId, - IImsFeatureStatusCallback c) { - return createEmergencyMMTelFeatureInternal(slotId, c); + public void setListener(IImsServiceControllerListener l) { + mListener = l; } @Override - public IImsMMTelFeature createMMTelFeature(int slotId, IImsFeatureStatusCallback c) { - return createMMTelFeatureInternal(slotId, c); + public IImsMmTelFeature createMmTelFeature(int slotId, IImsFeatureStatusCallback c) { + return createMmTelFeatureInternal(slotId, c); } @Override @@ -116,11 +132,43 @@ public class ImsService extends Service { ImsService.this.removeImsFeature(slotId, featureType, c); } + @Override + public ImsFeatureConfiguration querySupportedImsFeatures() { + return ImsService.this.querySupportedImsFeatures(); + } + + @Override + public void notifyImsServiceReadyForFeatureCreation() { + ImsService.this.readyForFeatureCreation(); + } + + @Override + public void notifyImsFeatureReady(int slotId, int featureType) + throws RemoteException { + ImsService.this.notifyImsFeatureReady(slotId, featureType); + } + + @Override + public IImsConfig getConfig(int slotId) throws RemoteException { + ImsConfigImplBase c = ImsService.this.getConfig(slotId); + return c != null ? c.getIImsConfig() : null; + } + @Override public IImsRegistration getRegistration(int slotId) throws RemoteException { ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId); return r != null ? r.getBinder() : null; } + + @Override + public void enableIms(int slotId) { + ImsService.this.enableIms(slotId); + } + + @Override + public void disableIms(int slotId) { + ImsService.this.disableIms(slotId); + } }; /** @@ -143,47 +191,35 @@ public class ImsService extends Service { return mFeaturesBySlot.get(slotId); } - private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId, + private IImsMmTelFeature createMmTelFeatureInternal(int slotId, IImsFeatureStatusCallback c) { - MMTelFeature f = onCreateEmergencyMMTelImsFeature(slotId); + MmTelFeature f = createMmTelFeature(slotId); if (f != null) { - setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL, c); - return f.getBinder(); - } else { - return null; - } - } - - private IImsMMTelFeature createMMTelFeatureInternal(int slotId, - IImsFeatureStatusCallback c) { - MMTelFeature f = onCreateMMTelImsFeature(slotId); - if (f != null) { - setupFeature(f, slotId, ImsFeature.MMTEL, c); + setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL, c); return f.getBinder(); } else { + Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned."); return null; } } private IImsRcsFeature createRcsFeatureInternal(int slotId, IImsFeatureStatusCallback c) { - RcsFeature f = onCreateRcsFeature(slotId); + RcsFeature f = createRcsFeature(slotId); if (f != null) { - setupFeature(f, slotId, ImsFeature.RCS, c); + setupFeature(f, slotId, ImsFeature.FEATURE_RCS, c); return f.getBinder(); } else { + Log.e(LOG_TAG, "createRcsFeatureInternal: null feature returned."); return null; } } private void setupFeature(ImsFeature f, int slotId, int featureType, IImsFeatureStatusCallback c) { - f.setContext(this); - f.setSlotId(slotId); f.addImsFeatureStatusCallback(c); + f.initialize(this, slotId); addImsFeature(slotId, featureType, f); - // TODO: Remove once new onFeatureReady AIDL is merged in. - f.onFeatureReady(); } private void addImsFeature(int slotId, int featureType, ImsFeature f) { @@ -221,32 +257,115 @@ public class ImsService extends Service { } } + private void notifyImsFeatureReady(int slotId, int featureType) { + synchronized (mFeaturesBySlot) { + // get ImsFeature associated with the slot/feature + SparseArray features = mFeaturesBySlot.get(slotId); + if (features == null) { + Log.w(LOG_TAG, "Can not notify ImsFeature ready. No ImsFeatures exist on " + + "slot " + slotId); + return; + } + ImsFeature f = features.get(featureType); + if (f == null) { + Log.w(LOG_TAG, "Can not notify ImsFeature ready. No feature with type " + + featureType + " exists on slot " + slotId); + return; + } + f.onFeatureReady(); + } + } + /** - * @return An implementation of MMTelFeature that will be used by the system for MMTel - * functionality. Must be able to handle emergency calls at any time as well. + * When called, provide the {@link ImsFeatureConfiguration} that this ImsService currently + * supports. This will trigger the framework to set up the {@link ImsFeature}s that correspond + * to the {@link ImsFeature.FeatureType}s configured here. + * @return an {@link ImsFeatureConfiguration} containing Features this ImsService supports, + * defined in {@link ImsFeature.FeatureType}. * @hide */ - public @Nullable MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) { - return null; + public ImsFeatureConfiguration querySupportedImsFeatures() { + // Return empty for base implementation + return new ImsFeatureConfiguration(); + } + + /** + * Updates the framework with a new {@link ImsFeatureConfiguration} containing the updated + * features, defined in {@link ImsFeature.FeatureType} that this ImsService supports. This may + * trigger the framework to add/remove new ImsFeatures, depending on the configuration. + * @hide + */ + public final void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) + throws RemoteException { + if (mListener == null) { + throw new IllegalStateException("Framework is not ready"); + } + mListener.onUpdateSupportedImsFeatures(c); + } + + /** + * The ImsService has been bound and is ready for ImsFeature creation based on the Features that + * the ImsService has registered for with the framework, either in the manifest or via + * The ImsService should use this signal instead of onCreate/onBind or similar to perform + * feature initialization because the framework may bind to this service multiple times to + * query the ImsService's {@link ImsFeatureConfiguration} via + * {@link #querySupportedImsFeatures()}before creating features. + * @hide + */ + public void readyForFeatureCreation() { + } + + /** + * The framework has enabled IMS for the slot specified, the ImsService should register for IMS + * and perform all appropriate initialization to bring up all ImsFeatures. + * @hide + */ + public void enableIms(int slotId) { } /** - * @return An implementation of MMTelFeature that will be used by the system for MMTel - * functionality. + * The framework has disabled IMS for the slot specified. The ImsService must deregister for IMS + * and set capability status to false for all ImsFeatures. * @hide */ - public @Nullable MMTelFeature onCreateMMTelImsFeature(int slotId) { + public void disableIms(int slotId) { + } + + /** + * When called, the framework is requesting that a new MmTelFeature is created for the specified + * slot. + * + * @param slotId The slot ID that the MMTel Feature is being created for. + * @return The newly created MmTelFeature associated with the slot or null if the feature is not + * supported. + * @hide + */ + public MmTelFeature createMmTelFeature(int slotId) { return null; } /** - * @return An implementation of RcsFeature that will be used by the system for RCS. + * When called, the framework is requesting that a new RcsFeature is created for the specified + * slot + * + * @param slotId The slot ID that the RCS Feature is being created for. + * @return The newly created RcsFeature associated with the slot or null if the feature is not + * supported. * @hide */ - public @Nullable RcsFeature onCreateRcsFeature(int slotId) { + public RcsFeature createRcsFeature(int slotId) { return null; } + /** + * @param slotId The slot that the IMS configuration is associated with. + * @return ImsConfig implementation that is associated with the specified slot. + * @hide + */ + public ImsConfigImplBase getConfig(int slotId) { + return new ImsConfigImplBase(); + } + /** * @param slotId The slot that is associated with the IMS Registration. * @return the ImsRegistration implementation associated with the slot. @@ -255,4 +374,4 @@ public class ImsService extends Service { public ImsRegistrationImplBase getRegistration(int slotId) { return new ImsRegistrationImplBase(); } -} +} \ No newline at end of file diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsCapabilityCallback.aidl similarity index 95% rename from telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl rename to telephony/java/android/telephony/ims/aidl/IImsCapabilityCallback.aidl index fd2eb24610e..c755703c042 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsCapabilityCallback.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; /** * See ImsFeature#CapabilityCallback for more information. diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl similarity index 92% rename from telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl rename to telephony/java/android/telephony/ims/aidl/IImsConfig.aidl index 3d424a33012..4433c1c03c1 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl @@ -15,9 +15,9 @@ */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; -import android.telephony.ims.internal.aidl.IImsConfigCallback; +import android.telephony.ims.aidl.IImsConfigCallback; import com.android.ims.ImsConfigListener; diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsConfigCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfigCallback.aidl similarity index 95% rename from telephony/java/android/telephony/ims/internal/aidl/IImsConfigCallback.aidl rename to telephony/java/android/telephony/ims/aidl/IImsConfigCallback.aidl index 52efd2322c1..2b3f1ca7196 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsConfigCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsConfigCallback.aidl @@ -15,7 +15,7 @@ */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; /** * Provides callback interface for ImsConfig when a value has changed. diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl similarity index 87% rename from telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl rename to telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl index 18afc6a23a4..f9b15c0b161 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl @@ -14,12 +14,13 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; import android.os.Message; -import android.telephony.ims.internal.aidl.IImsMmTelListener; -import android.telephony.ims.internal.aidl.IImsCapabilityCallback; -import android.telephony.ims.internal.feature.CapabilityChangeRequest; +import android.telephony.ims.aidl.IImsMmTelListener; +import android.telephony.ims.aidl.IImsSmsListener; +import android.telephony.ims.aidl.IImsCapabilityCallback; +import android.telephony.ims.feature.CapabilityChangeRequest; import com.android.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; @@ -37,6 +38,7 @@ interface IImsMmTelFeature { int getFeatureState(); ImsCallProfile createCallProfile(int callSessionType, int callType); IImsCallSession createCallSession(in ImsCallProfile profile); + int shouldProcessCall(in String[] uris); IImsUt getUtInterface(); IImsEcbm getEcbmInterface(); void setUiTtyMode(int uiTtyMode, in Message onCompleteMessage); diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl similarity index 86% rename from telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl rename to telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl index 43f5098af3c..904e7cad9c1 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl @@ -14,7 +14,9 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; + +import android.os.Bundle; import com.android.ims.internal.IImsCallSession; @@ -23,6 +25,6 @@ import com.android.ims.internal.IImsCallSession; * {@hide} */ oneway interface IImsMmTelListener { - void onIncomingCall(IImsCallSession c); + void onIncomingCall(IImsCallSession c, in Bundle extras); void onVoiceMessageCountUpdate(int count); } \ No newline at end of file diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl similarity index 94% rename from telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl rename to telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl index f6005b66bd3..691cfba9a28 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRcsFeature.aidl @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; /** * See RcsFeature for more information. diff --git a/telephony/java/com/android/ims/internal/IImsRegistration.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl similarity index 90% rename from telephony/java/com/android/ims/internal/IImsRegistration.aidl rename to telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl index 6de264ec90f..4ae0a75ad02 100644 --- a/telephony/java/com/android/ims/internal/IImsRegistration.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl @@ -15,9 +15,9 @@ */ -package com.android.ims.internal; +package android.telephony.ims.aidl; -import com.android.ims.internal.IImsRegistrationCallback; +import android.telephony.ims.aidl.IImsRegistrationCallback; /** * See ImsRegistration for more information. diff --git a/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl similarity index 91% rename from telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl rename to telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl index 5f21167422d..a745a3819f4 100644 --- a/telephony/java/com/android/ims/internal/IImsRegistrationCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl @@ -15,10 +15,10 @@ */ -package com.android.ims.internal; +package android.telephony.ims.aidl; import android.net.Uri; -import android.telephony.ims.internal.stub.ImsFeatureConfiguration; +import android.telephony.ims.stub.ImsFeatureConfiguration; import com.android.ims.ImsReasonInfo; diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl b/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl similarity index 77% rename from telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl rename to telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl index 82a85254bbc..86f8606ac39 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsServiceController.aidl @@ -14,16 +14,16 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; -import android.telephony.ims.internal.aidl.IImsMmTelFeature; -import android.telephony.ims.internal.aidl.IImsRcsFeature; -import android.telephony.ims.internal.aidl.IImsConfig; -import android.telephony.ims.internal.aidl.IImsServiceControllerListener; -import android.telephony.ims.internal.stub.ImsFeatureConfiguration; +import android.telephony.ims.aidl.IImsMmTelFeature; +import android.telephony.ims.aidl.IImsRcsFeature; +import android.telephony.ims.aidl.IImsConfig; +import android.telephony.ims.aidl.IImsRegistration; +import android.telephony.ims.aidl.IImsServiceControllerListener; +import android.telephony.ims.stub.ImsFeatureConfiguration; import com.android.ims.internal.IImsFeatureStatusCallback; -import com.android.ims.internal.IImsRegistration; /** * See ImsService and MmTelFeature for more information. @@ -41,4 +41,6 @@ interface IImsServiceController { void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c); IImsConfig getConfig(int slotId); IImsRegistration getRegistration(int slotId); + oneway void enableIms(int slotId); + oneway void disableIms(int slotId); } diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl similarity index 87% rename from telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl rename to telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl index 01cca2db097..54f6120aa99 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsServiceControllerListener.aidl @@ -14,9 +14,9 @@ * limitations under the License. */ -package android.telephony.ims.internal.aidl; +package android.telephony.ims.aidl; -import android.telephony.ims.internal.stub.ImsFeatureConfiguration; +import android.telephony.ims.stub.ImsFeatureConfiguration; /** * See ImsService#Listener for more information. diff --git a/telephony/java/com/android/ims/internal/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl similarity index 92% rename from telephony/java/com/android/ims/internal/IImsSmsListener.aidl rename to telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl index 5a4b7e48902..606df15b178 100644 --- a/telephony/java/com/android/ims/internal/IImsSmsListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsSmsListener.aidl @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.ims.internal; +package android.telephony.ims.aidl; /** * See SmsImplBase for more information. * {@hide} */ -interface IImsSmsListener { +oneway interface IImsSmsListener { void onSendSmsResult(int token, int messageRef, int status, int reason); void onSmsStatusReportReceived(int token, int messageRef, in String format, in byte[] pdu); diff --git a/telephony/java/android/telephony/ims/compat/ImsService.java b/telephony/java/android/telephony/ims/compat/ImsService.java new file mode 100644 index 00000000000..cf1efb34bd0 --- /dev/null +++ b/telephony/java/android/telephony/ims/compat/ImsService.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telephony.ims.compat; + +import android.annotation.Nullable; +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.RemoteException; +import android.telephony.CarrierConfigManager; +import android.telephony.ims.compat.feature.ImsFeature; +import android.telephony.ims.compat.feature.MMTelFeature; +import android.telephony.ims.compat.feature.RcsFeature; +import android.util.Log; +import android.util.SparseArray; + +import com.android.ims.internal.IImsFeatureStatusCallback; +import com.android.ims.internal.IImsMMTelFeature; +import com.android.ims.internal.IImsRcsFeature; +import com.android.ims.internal.IImsServiceController; +import com.android.internal.annotations.VisibleForTesting; + +/** + * Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend + * ImsService must register the service in their AndroidManifest to be detected by the framework. + * First, the application must declare that they use the "android.permission.BIND_IMS_SERVICE" + * permission. Then, the ImsService definition in the manifest must follow the following format: + * + * ... + * + * + * + * + * + * + * + * ... + * + * The telephony framework will then bind to the ImsService you have defined in your manifest + * if you are either: + * 1) Defined as the default ImsService for the device in the device overlay using + * "config_ims_package". + * 2) Defined as a Carrier Provided ImsService in the Carrier Configuration using + * {@link CarrierConfigManager#KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING}. + * + * The features that are currently supported in an ImsService are: + * - RCS_FEATURE: This ImsService implements the RcsFeature class. + * - MMTEL_FEATURE: This ImsService implements the MMTelFeature class. + * - EMERGENCY_MMTEL_FEATURE: This ImsService implements the MMTelFeature class and will be + * available to place emergency calls at all times. This MUST be implemented by the default + * ImsService provided in the device overlay. + * @hide + */ +public class ImsService extends Service { + + private static final String LOG_TAG = "ImsService(Compat)"; + + /** + * The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService. + * @hide + */ + public static final String SERVICE_INTERFACE = "android.telephony.ims.compat.ImsService"; + + // A map of slot Id -> map of features (indexed by ImsFeature feature id) corresponding to that + // slot. + // We keep track of this to facilitate cleanup of the IImsFeatureStatusCallback and + // call ImsFeature#onFeatureRemoved. + private final SparseArray> mFeaturesBySlot = new SparseArray<>(); + + /** + * @hide + */ + protected final IBinder mImsServiceController = new IImsServiceController.Stub() { + + @Override + public IImsMMTelFeature createEmergencyMMTelFeature(int slotId, + IImsFeatureStatusCallback c) { + return createEmergencyMMTelFeatureInternal(slotId, c); + } + + @Override + public IImsMMTelFeature createMMTelFeature(int slotId, IImsFeatureStatusCallback c) { + return createMMTelFeatureInternal(slotId, c); + } + + @Override + public IImsRcsFeature createRcsFeature(int slotId, IImsFeatureStatusCallback c) { + return createRcsFeatureInternal(slotId, c); + } + + @Override + public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) + throws RemoteException { + ImsService.this.removeImsFeature(slotId, featureType, c); + } + }; + + /** + * @hide + */ + @Override + public IBinder onBind(Intent intent) { + if(SERVICE_INTERFACE.equals(intent.getAction())) { + Log.i(LOG_TAG, "ImsService(Compat) Bound."); + return mImsServiceController; + } + return null; + } + + /** + * @hide + */ + @VisibleForTesting + public SparseArray getFeatures(int slotId) { + return mFeaturesBySlot.get(slotId); + } + + private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId, + IImsFeatureStatusCallback c) { + MMTelFeature f = onCreateEmergencyMMTelImsFeature(slotId); + if (f != null) { + setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL, c); + return f.getBinder(); + } else { + return null; + } + } + + private IImsMMTelFeature createMMTelFeatureInternal(int slotId, + IImsFeatureStatusCallback c) { + MMTelFeature f = onCreateMMTelImsFeature(slotId); + if (f != null) { + setupFeature(f, slotId, ImsFeature.MMTEL, c); + return f.getBinder(); + } else { + return null; + } + } + + private IImsRcsFeature createRcsFeatureInternal(int slotId, + IImsFeatureStatusCallback c) { + RcsFeature f = onCreateRcsFeature(slotId); + if (f != null) { + setupFeature(f, slotId, ImsFeature.RCS, c); + return f.getBinder(); + } else { + return null; + } + } + + private void setupFeature(ImsFeature f, int slotId, int featureType, + IImsFeatureStatusCallback c) { + f.setContext(this); + f.setSlotId(slotId); + f.addImsFeatureStatusCallback(c); + addImsFeature(slotId, featureType, f); + // TODO: Remove once new onFeatureReady AIDL is merged in. + f.onFeatureReady(); + } + + private void addImsFeature(int slotId, int featureType, ImsFeature f) { + synchronized (mFeaturesBySlot) { + // Get SparseArray for Features, by querying slot Id + SparseArray features = mFeaturesBySlot.get(slotId); + if (features == null) { + // Populate new SparseArray of features if it doesn't exist for this slot yet. + features = new SparseArray<>(); + mFeaturesBySlot.put(slotId, features); + } + features.put(featureType, f); + } + } + + private void removeImsFeature(int slotId, int featureType, + IImsFeatureStatusCallback c) { + synchronized (mFeaturesBySlot) { + // get ImsFeature associated with the slot/feature + SparseArray features = mFeaturesBySlot.get(slotId); + if (features == null) { + Log.w(LOG_TAG, "Can not remove ImsFeature. No ImsFeatures exist on slot " + + slotId); + return; + } + ImsFeature f = features.get(featureType); + if (f == null) { + Log.w(LOG_TAG, "Can not remove ImsFeature. No feature with type " + + featureType + " exists on slot " + slotId); + return; + } + f.removeImsFeatureStatusCallback(c); + f.onFeatureRemoved(); + features.remove(featureType); + } + } + + /** + * @return An implementation of MMTelFeature that will be used by the system for MMTel + * functionality. Must be able to handle emergency calls at any time as well. + * @hide + */ + public @Nullable MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) { + return null; + } + + /** + * @return An implementation of MMTelFeature that will be used by the system for MMTel + * functionality. + * @hide + */ + public @Nullable MMTelFeature onCreateMMTelImsFeature(int slotId) { + return null; + } + + /** + * @return An implementation of RcsFeature that will be used by the system for RCS. + * @hide + */ + public @Nullable RcsFeature onCreateRcsFeature(int slotId) { + return null; + } +} diff --git a/telephony/java/android/telephony/ims/compat/feature/ImsFeature.java b/telephony/java/android/telephony/ims/compat/feature/ImsFeature.java new file mode 100644 index 00000000000..0a12cae26af --- /dev/null +++ b/telephony/java/android/telephony/ims/compat/feature/ImsFeature.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telephony.ims.compat.feature; + +import android.annotation.IntDef; +import android.content.Context; +import android.content.Intent; +import android.os.IInterface; +import android.os.RemoteException; +import android.telephony.SubscriptionManager; +import android.util.Log; + +import com.android.ims.internal.IImsFeatureStatusCallback; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * Base class for all IMS features that are supported by the framework. + * @hide + */ +public abstract class ImsFeature { + + private static final String LOG_TAG = "ImsFeature"; + + /** + * Action to broadcast when ImsService is up. + * Internal use only. + * Only defined here separately compatibility purposes with the old ImsService. + * @hide + */ + public static final String ACTION_IMS_SERVICE_UP = + "com.android.ims.IMS_SERVICE_UP"; + + /** + * Action to broadcast when ImsService is down. + * Internal use only. + * Only defined here separately for compatibility purposes with the old ImsService. + * @hide + */ + public static final String ACTION_IMS_SERVICE_DOWN = + "com.android.ims.IMS_SERVICE_DOWN"; + + /** + * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents. + * A long value; the phone ID corresponding to the IMS service coming up or down. + * Only defined here separately for compatibility purposes with the old ImsService. + * @hide + */ + public static final String EXTRA_PHONE_ID = "android:phone_id"; + + // Invalid feature value + public static final int INVALID = -1; + // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously + // defined values in ImsServiceClass for compatibility purposes. + public static final int EMERGENCY_MMTEL = 0; + public static final int MMTEL = 1; + public static final int RCS = 2; + // Total number of features defined + public static final int MAX = 3; + + // Integer values defining the state of the ImsFeature at any time. + @IntDef(flag = true, + value = { + STATE_NOT_AVAILABLE, + STATE_INITIALIZING, + STATE_READY, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ImsState {} + public static final int STATE_NOT_AVAILABLE = 0; + public static final int STATE_INITIALIZING = 1; + public static final int STATE_READY = 2; + + private final Set mStatusCallbacks = Collections.newSetFromMap( + new WeakHashMap()); + private @ImsState int mState = STATE_NOT_AVAILABLE; + private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; + protected Context mContext; + + public void setContext(Context context) { + mContext = context; + } + + public void setSlotId(int slotId) { + mSlotId = slotId; + } + + public int getFeatureState() { + return mState; + } + + protected final void setFeatureState(@ImsState int state) { + if (mState != state) { + mState = state; + notifyFeatureState(state); + } + } + + public void addImsFeatureStatusCallback(IImsFeatureStatusCallback c) { + if (c == null) { + return; + } + try { + // If we have just connected, send queued status. + c.notifyImsFeatureStatus(mState); + // Add the callback if the callback completes successfully without a RemoteException. + synchronized (mStatusCallbacks) { + mStatusCallbacks.add(c); + } + } catch (RemoteException e) { + Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); + } + } + + public void removeImsFeatureStatusCallback(IImsFeatureStatusCallback c) { + if (c == null) { + return; + } + synchronized (mStatusCallbacks) { + mStatusCallbacks.remove(c); + } + } + + /** + * Internal method called by ImsFeature when setFeatureState has changed. + * @param state + */ + private void notifyFeatureState(@ImsState int state) { + synchronized (mStatusCallbacks) { + for (Iterator iter = mStatusCallbacks.iterator(); + iter.hasNext(); ) { + IImsFeatureStatusCallback callback = iter.next(); + try { + Log.i(LOG_TAG, "notifying ImsFeatureState=" + state); + callback.notifyImsFeatureStatus(state); + } catch (RemoteException e) { + // remove if the callback is no longer alive. + iter.remove(); + Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); + } + } + } + sendImsServiceIntent(state); + } + + /** + * Provide backwards compatibility using deprecated service UP/DOWN intents. + */ + private void sendImsServiceIntent(@ImsState int state) { + if(mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { + return; + } + Intent intent; + switch (state) { + case ImsFeature.STATE_NOT_AVAILABLE: + case ImsFeature.STATE_INITIALIZING: + intent = new Intent(ACTION_IMS_SERVICE_DOWN); + break; + case ImsFeature.STATE_READY: + intent = new Intent(ACTION_IMS_SERVICE_UP); + break; + default: + intent = new Intent(ACTION_IMS_SERVICE_DOWN); + } + intent.putExtra(EXTRA_PHONE_ID, mSlotId); + mContext.sendBroadcast(intent); + } + + /** + * Called when the feature is ready to use. + */ + public abstract void onFeatureReady(); + + /** + * Called when the feature is being removed and must be cleaned up. + */ + public abstract void onFeatureRemoved(); + + /** + * @return Binder instance + */ + public abstract IInterface getBinder(); +} diff --git a/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java index 403bb60404c..f5c5857b7ab 100644 --- a/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java +++ b/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java @@ -16,33 +16,342 @@ package android.telephony.ims.compat.feature; +import android.app.PendingIntent; +import android.os.Message; +import android.os.RemoteException; + import com.android.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; -import com.android.ims.internal.IImsCallSessionListener; +import com.android.ims.internal.IImsConfig; +import com.android.ims.internal.IImsEcbm; +import com.android.ims.internal.IImsMMTelFeature; +import com.android.ims.internal.IImsMultiEndpoint; +import com.android.ims.internal.IImsRegistrationListener; +import com.android.ims.internal.IImsUt; import com.android.ims.internal.ImsCallSession; /** - * Compatability layer for older implementations of MMTelFeature. + * Base implementation for MMTel. + * Any class wishing to use MMTelFeature should extend this class and implement all methods that the + * service supports. * * @hide */ -public class MMTelFeature extends android.telephony.ims.feature.MMTelFeature { +public class MMTelFeature extends ImsFeature { + + // Lock for feature synchronization + private final Object mLock = new Object(); + + private final IImsMMTelFeature mImsMMTelBinder = new IImsMMTelFeature.Stub() { + + @Override + public int startSession(PendingIntent incomingCallIntent, + IImsRegistrationListener listener) throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.startSession(incomingCallIntent, listener); + } + } + + @Override + public void endSession(int sessionId) throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.endSession(sessionId); + } + } + + @Override + public boolean isConnected(int callSessionType, int callType) + throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.isConnected(callSessionType, callType); + } + } + + @Override + public boolean isOpened() throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.isOpened(); + } + } + + @Override + public int getFeatureStatus() throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.getFeatureState(); + } + } + + @Override + public void addRegistrationListener(IImsRegistrationListener listener) + throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.addRegistrationListener(listener); + } + } + + @Override + public void removeRegistrationListener(IImsRegistrationListener listener) + throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.removeRegistrationListener(listener); + } + } + + @Override + public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType) + throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.createCallProfile(sessionId, callSessionType, callType); + } + } + + @Override + public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile) + throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.createCallSession(sessionId, profile); + } + } + + @Override + public IImsCallSession getPendingCallSession(int sessionId, String callId) + throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.getPendingCallSession(sessionId, callId); + } + } + @Override + public IImsUt getUtInterface() throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.getUtInterface(); + } + } + + @Override + public IImsConfig getConfigInterface() throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.getConfigInterface(); + } + } + + @Override + public void turnOnIms() throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.turnOnIms(); + } + } + + @Override + public void turnOffIms() throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.turnOffIms(); + } + } + + @Override + public IImsEcbm getEcbmInterface() throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.getEcbmInterface(); + } + } + + @Override + public void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.setUiTTYMode(uiTtyMode, onComplete); + } + } + + @Override + public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { + synchronized (mLock) { + return MMTelFeature.this.getMultiEndpointInterface(); + } + } + }; + + /** + * @hide + */ @Override - public final IImsCallSession createCallSession(int sessionId, ImsCallProfile profile) { - return createCallSession(sessionId, profile, null /*listener*/); + public final IImsMMTelFeature getBinder() { + return mImsMMTelBinder; + } + + /** + * Notifies the MMTel feature that you would like to start a session. This should always be + * done before making/receiving IMS calls. The IMS service will register the device to the + * operator's network with the credentials (from ISIM) periodically in order to receive calls + * from the operator's network. When the IMS service receives a new call, it will send out an + * intent with the provided action string. The intent contains a call ID extra + * {@link IImsCallSession#getCallId} and it can be used to take a call. + * + * @param incomingCallIntent When an incoming call is received, the IMS service will call + * {@link PendingIntent#send} to send back the intent to the caller with + * ImsManager#INCOMING_CALL_RESULT_CODE as the result code and the intent to fill in the call + * ID; It cannot be null. + * @param listener To listen to IMS registration events; It cannot be null + * @return an integer (greater than 0) representing the session id associated with the session + * that has been started. + */ + public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener) { + return 0; + } + + /** + * End a previously started session using the associated sessionId. + * @param sessionId an integer (greater than 0) representing the ongoing session. See + * {@link #startSession}. + */ + public void endSession(int sessionId) { + } + + /** + * Checks if the IMS service has successfully registered to the IMS network with the specified + * service & call type. + * + * @param callSessionType a service type that is specified in {@link ImsCallProfile} + * {@link ImsCallProfile#SERVICE_TYPE_NORMAL} + * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY} + * @param callType a call type that is specified in {@link ImsCallProfile} + * {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO} + * {@link ImsCallProfile#CALL_TYPE_VOICE} + * {@link ImsCallProfile#CALL_TYPE_VT} + * {@link ImsCallProfile#CALL_TYPE_VS} + * @return true if the specified service id is connected to the IMS network; false otherwise + */ + public boolean isConnected(int callSessionType, int callType) { + return false; + } + + /** + * Checks if the specified IMS service is opened. + * + * @return true if the specified service id is opened; false otherwise + */ + public boolean isOpened() { + return false; + } + + /** + * Add a new registration listener for the client associated with the session Id. + * @param listener An implementation of IImsRegistrationListener. + */ + public void addRegistrationListener(IImsRegistrationListener listener) { + } + + /** + * Remove a previously registered listener using {@link #addRegistrationListener} for the client + * associated with the session Id. + * @param listener A previously registered IImsRegistrationListener + */ + public void removeRegistrationListener(IImsRegistrationListener listener) { + } + + /** + * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state. + * + * @param sessionId a session id which is obtained from {@link #startSession} + * @param callSessionType a service type that is specified in {@link ImsCallProfile} + * {@link ImsCallProfile#SERVICE_TYPE_NONE} + * {@link ImsCallProfile#SERVICE_TYPE_NORMAL} + * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY} + * @param callType a call type that is specified in {@link ImsCallProfile} + * {@link ImsCallProfile#CALL_TYPE_VOICE} + * {@link ImsCallProfile#CALL_TYPE_VT} + * {@link ImsCallProfile#CALL_TYPE_VT_TX} + * {@link ImsCallProfile#CALL_TYPE_VT_RX} + * {@link ImsCallProfile#CALL_TYPE_VT_NODIR} + * {@link ImsCallProfile#CALL_TYPE_VS} + * {@link ImsCallProfile#CALL_TYPE_VS_TX} + * {@link ImsCallProfile#CALL_TYPE_VS_RX} + * @return a {@link ImsCallProfile} object + */ + public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType) { + return null; } /** * Creates an {@link ImsCallSession} with the specified call profile. + * Use other methods, if applicable, instead of interacting with + * {@link ImsCallSession} directly. * * @param sessionId a session id which is obtained from {@link #startSession} * @param profile a call profile to make the call - * @param listener An implementation of IImsCallSessionListener. */ - public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile, - IImsCallSessionListener listener) { + public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile) { + return null; + } + + /** + * Retrieves the call session associated with a pending call. + * + * @param sessionId a session id which is obtained from {@link #startSession} + * @param callId a call id to make the call + */ + public IImsCallSession getPendingCallSession(int sessionId, String callId) { + return null; + } + + /** + * @return The Ut interface for the supplementary service configuration. + */ + public IImsUt getUtInterface() { + return null; + } + + /** + * @return The config interface for IMS Configuration + */ + public IImsConfig getConfigInterface() { + return null; + } + + /** + * Signal the MMTelFeature to turn on IMS when it has been turned off using {@link #turnOffIms} + */ + public void turnOnIms() { + } + + /** + * Signal the MMTelFeature to turn off IMS when it has been turned on using {@link #turnOnIms} + */ + public void turnOffIms() { + } + + /** + * @return The Emergency call-back mode interface for emergency VoLTE calls that support it. + */ + public IImsEcbm getEcbmInterface() { + return null; + } + + /** + * Sets the current UI TTY mode for the MMTelFeature. + * @param uiTtyMode An integer containing the new UI TTY Mode. + * @param onComplete A {@link Message} to be used when the mode has been set. + */ + public void setUiTTYMode(int uiTtyMode, Message onComplete) { + } + + /** + * @return MultiEndpoint interface for DEP notifications + */ + public IImsMultiEndpoint getMultiEndpointInterface() { return null; } + + @Override + public void onFeatureReady() { + + } + + /** + * {@inheritDoc} + */ + public void onFeatureRemoved() { + + } } diff --git a/telephony/java/android/telephony/ims/internal/feature/RcsFeature.java b/telephony/java/android/telephony/ims/compat/feature/RcsFeature.java similarity index 76% rename from telephony/java/android/telephony/ims/internal/feature/RcsFeature.java rename to telephony/java/android/telephony/ims/compat/feature/RcsFeature.java index 8d1bd9d27f7..228b3304510 100644 --- a/telephony/java/android/telephony/ims/internal/feature/RcsFeature.java +++ b/telephony/java/android/telephony/ims/compat/feature/RcsFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open 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,9 +14,10 @@ * limitations under the License */ -package android.telephony.ims.internal.feature; +package android.telephony.ims.compat.feature; -import android.telephony.ims.internal.aidl.IImsRcsFeature; + +import com.android.ims.internal.IImsRcsFeature; /** * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend @@ -36,19 +37,12 @@ public class RcsFeature extends ImsFeature { } @Override - public void changeEnabledCapabilities(CapabilityChangeRequest request, - CapabilityCallbackProxy c) { - // Do nothing for base implementation. - } - - @Override - public void onFeatureRemoved() { + public void onFeatureReady() { } - /**{@inheritDoc}*/ @Override - public void onFeatureReady() { + public void onFeatureRemoved() { } diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java new file mode 100644 index 00000000000..b5417e77c00 --- /dev/null +++ b/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telephony.ims.compat.stub; + +import android.os.RemoteException; + +import com.android.ims.ImsConfig; +import com.android.ims.ImsConfigListener; +import com.android.ims.internal.IImsConfig; + +/** + * Base implementation of ImsConfig, which implements stub versions of the methods + * in the IImsConfig AIDL. Override the methods that your implementation of ImsConfig supports. + * + * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you + * will break other implementations of ImsConfig maintained by other ImsServices. + * + * Provides APIs to get/set the IMS service feature/capability/parameters. + * The config items include: + * 1) Items provisioned by the operator. + * 2) Items configured by user. Mainly service feature class. + * + * @hide + */ + +public class ImsConfigImplBase extends IImsConfig.Stub { + + /** + * Gets the value for ims service/capabilities parameters from the provisioned + * value storage. Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @return value in Integer format. + */ + @Override + public int getProvisionedValue(int item) throws RemoteException { + return -1; + } + + /** + * Gets the value for ims service/capabilities parameters from the provisioned + * value storage. Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @return value in String format. + */ + @Override + public String getProvisionedStringValue(int item) throws RemoteException { + return null; + } + + /** + * Sets the value for IMS service/capabilities parameters by the operator device + * management entity. It sets the config item value in the provisioned storage + * from which the master value is derived. Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in Integer format. + * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + */ + @Override + public int setProvisionedValue(int item, int value) throws RemoteException { + return ImsConfig.OperationStatusConstants.FAILED; + } + + /** + * Sets the value for IMS service/capabilities parameters by the operator device + * management entity. It sets the config item value in the provisioned storage + * from which the master value is derived. Synchronous blocking call. + * + * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in String format. + * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + */ + @Override + public int setProvisionedStringValue(int item, String value) throws RemoteException { + return ImsConfig.OperationStatusConstants.FAILED; + } + + /** + * Gets the value of the specified IMS feature item for specified network type. + * This operation gets the feature config value from the master storage (i.e. final + * value). Asynchronous non-blocking call. + * + * @param feature as defined in com.android.ims.ImsConfig#FeatureConstants. + * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. + * @param listener feature value returned asynchronously through listener. + */ + @Override + public void getFeatureValue(int feature, int network, ImsConfigListener listener) + throws RemoteException { + } + + /** + * Sets the value for IMS feature item for specified network type. + * This operation stores the user setting in setting db from which master db + * is derived. + * + * @param feature as defined in com.android.ims.ImsConfig#FeatureConstants. + * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. + * @param value as defined in com.android.ims.ImsConfig#FeatureValueConstants. + * @param listener, provided if caller needs to be notified for set result. + */ + @Override + public void setFeatureValue(int feature, int network, int value, ImsConfigListener listener) + throws RemoteException { + } + + /** + * Gets the value for IMS VoLTE provisioned. + * This should be the same as the operator provisioned value if applies. + */ + @Override + public boolean getVolteProvisioned() throws RemoteException { + return false; + } + + /** + * Gets the value for IMS feature item video quality. + * + * @param listener Video quality value returned asynchronously through listener. + */ + @Override + public void getVideoQuality(ImsConfigListener listener) throws RemoteException { + } + + /** + * Sets the value for IMS feature item video quality. + * + * @param quality, defines the value of video quality. + * @param listener, provided if caller needs to be notified for set result. + */ + @Override + public void setVideoQuality(int quality, ImsConfigListener listener) throws RemoteException { + } +} diff --git a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.aidl b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.aidl similarity index 93% rename from telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.aidl rename to telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.aidl index f4ec0eb38f3..e789bd5ac94 100644 --- a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.aidl +++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.aidl @@ -14,6 +14,6 @@ * limitations under the License */ -package android.telephony.ims.internal.feature; +package android.telephony.ims.feature; parcelable CapabilityChangeRequest; diff --git a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java similarity index 97% rename from telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java rename to telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java index 5dbf077ee7c..efb6ffe9904 100644 --- a/telephony/java/android/telephony/ims/internal/feature/CapabilityChangeRequest.java +++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open 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,7 @@ * limitations under the License */ -package android.telephony.ims.internal.feature; +package android.telephony.ims.feature; import android.os.Parcel; import android.os.Parcelable; @@ -182,7 +182,8 @@ public class CapabilityChangeRequest implements Parcelable { if (this == o) return true; if (!(o instanceof CapabilityChangeRequest)) return false; - CapabilityChangeRequest that = (CapabilityChangeRequest) o; + CapabilityChangeRequest + that = (CapabilityChangeRequest) o; if (!mCapabilitiesToEnable.equals(that.mCapabilitiesToEnable)) return false; return mCapabilitiesToDisable.equals(that.mCapabilitiesToDisable); diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java index d47cea3097f..2cc0a8a2275 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,26 +17,29 @@ package android.telephony.ims.feature; import android.annotation.IntDef; +import android.annotation.NonNull; import android.content.Context; import android.content.Intent; import android.os.IInterface; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.telephony.SubscriptionManager; +import android.telephony.ims.aidl.IImsCapabilityCallback; import android.util.Log; import com.android.ims.internal.IImsFeatureStatusCallback; +import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; -import java.util.List; import java.util.Set; import java.util.WeakHashMap; /** * Base class for all IMS features that are supported by the framework. + * * @hide */ public abstract class ImsFeature { @@ -46,7 +49,8 @@ public abstract class ImsFeature { /** * Action to broadcast when ImsService is up. * Internal use only. - * Only defined here separately compatibility purposes with the old ImsService. + * Only defined here separately for compatibility purposes with the old ImsService. + * * @hide */ public static final String ACTION_IMS_SERVICE_UP = @@ -56,6 +60,7 @@ public abstract class ImsFeature { * Action to broadcast when ImsService is down. * Internal use only. * Only defined here separately for compatibility purposes with the old ImsService. + * * @hide */ public static final String ACTION_IMS_SERVICE_DOWN = @@ -65,67 +70,259 @@ public abstract class ImsFeature { * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents. * A long value; the phone ID corresponding to the IMS service coming up or down. * Only defined here separately for compatibility purposes with the old ImsService. + * * @hide */ public static final String EXTRA_PHONE_ID = "android:phone_id"; // Invalid feature value - public static final int INVALID = -1; + public static final int FEATURE_INVALID = -1; // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously // defined values in ImsServiceClass for compatibility purposes. - public static final int EMERGENCY_MMTEL = 0; - public static final int MMTEL = 1; - public static final int RCS = 2; + public static final int FEATURE_EMERGENCY_MMTEL = 0; + public static final int FEATURE_MMTEL = 1; + public static final int FEATURE_RCS = 2; // Total number of features defined - public static final int MAX = 3; + public static final int FEATURE_MAX = 3; + + // Integer values defining IMS features that are supported in ImsFeature. + @IntDef(flag = true, + value = { + FEATURE_EMERGENCY_MMTEL, + FEATURE_MMTEL, + FEATURE_RCS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FeatureType {} // Integer values defining the state of the ImsFeature at any time. @IntDef(flag = true, value = { - STATE_NOT_AVAILABLE, + STATE_UNAVAILABLE, STATE_INITIALIZING, STATE_READY, }) @Retention(RetentionPolicy.SOURCE) public @interface ImsState {} - public static final int STATE_NOT_AVAILABLE = 0; + + public static final int STATE_UNAVAILABLE = 0; public static final int STATE_INITIALIZING = 1; public static final int STATE_READY = 2; + // Integer values defining the result codes that should be returned from + // {@link changeEnabledCapabilities} when the framework tries to set a feature's capability. + @IntDef(flag = true, + value = { + CAPABILITY_ERROR_GENERIC, + CAPABILITY_SUCCESS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ImsCapabilityError {} + + public static final int CAPABILITY_ERROR_GENERIC = -1; + public static final int CAPABILITY_SUCCESS = 0; + + + /** + * The framework implements this callback in order to register for Feature Capability status + * updates, via {@link #onCapabilitiesStatusChanged(Capabilities)}, query Capability + * configurations, via {@link #onQueryCapabilityConfiguration}, as well as to receive error + * callbacks when the ImsService can not change the capability as requested, via + * {@link #onChangeCapabilityConfigurationError}. + */ + public static class CapabilityCallback extends IImsCapabilityCallback.Stub { + + @Override + public final void onCapabilitiesStatusChanged(int config) throws RemoteException { + onCapabilitiesStatusChanged(new Capabilities(config)); + } + + /** + * Returns the result of a query for the capability configuration of a requested capability. + * + * @param capability The capability that was requested. + * @param radioTech The IMS radio technology associated with the capability. + * @param isEnabled true if the capability is enabled, false otherwise. + */ + @Override + public void onQueryCapabilityConfiguration(int capability, int radioTech, + boolean isEnabled) { + + } + + /** + * Called when a change to the capability configuration has returned an error. + * + * @param capability The capability that was requested to be changed. + * @param radioTech The IMS radio technology associated with the capability. + * @param reason error associated with the failure to change configuration. + */ + @Override + public void onChangeCapabilityConfigurationError(int capability, int radioTech, + int reason) { + } + + /** + * The status of the feature's capabilities has changed to either available or unavailable. + * If unavailable, the feature is not able to support the unavailable capability at this + * time. + * + * @param config The new availability of the capabilities. + */ + public void onCapabilitiesStatusChanged(Capabilities config) { + } + } + + /** + * Used by the ImsFeature to call back to the CapabilityCallback that the framework has + * provided. + */ + protected static class CapabilityCallbackProxy { + private final IImsCapabilityCallback mCallback; + + public CapabilityCallbackProxy(IImsCapabilityCallback c) { + mCallback = c; + } + + /** + * This method notifies the provided framework callback that the request to change the + * indicated capability has failed and has not changed. + * + * @param capability The Capability that will be notified to the framework. + * @param radioTech The radio tech that this capability failed for. + * @param reason The reason this capability was unable to be changed. + */ + public void onChangeCapabilityConfigurationError(int capability, int radioTech, + @ImsCapabilityError int reason) { + if (mCallback == null) { + return; + } + try { + mCallback.onChangeCapabilityConfigurationError(capability, radioTech, reason); + } catch (RemoteException e) { + Log.e(LOG_TAG, "onChangeCapabilityConfigurationError called on dead binder."); + } + } + + public void onQueryCapabilityConfiguration(int capability, int radioTech, + boolean isEnabled) { + if (mCallback == null) { + return; + } + try { + mCallback.onQueryCapabilityConfiguration(capability, radioTech, isEnabled); + } catch (RemoteException e) { + Log.e(LOG_TAG, "onQueryCapabilityConfiguration called on dead binder."); + } + } + } + + /** + * Contains the capabilities defined and supported by an ImsFeature in the form of a bit mask. + */ + public static class Capabilities { + protected int mCapabilities = 0; + + public Capabilities() { + } + + protected Capabilities(int capabilities) { + mCapabilities = capabilities; + } + + /** + * @param capabilities Capabilities to be added to the configuration in the form of a + * bit mask. + */ + public void addCapabilities(int capabilities) { + mCapabilities |= capabilities; + } + + /** + * @param capabilities Capabilities to be removed to the configuration in the form of a + * bit mask. + */ + public void removeCapabilities(int capabilities) { + mCapabilities &= ~capabilities; + } + + /** + * @return true if all of the capabilities specified are capable. + */ + public boolean isCapable(int capabilities) { + return (mCapabilities & capabilities) == capabilities; + } + + public Capabilities copy() { + return new Capabilities(mCapabilities); + } + + /** + * @return a bitmask containing the capability flags directly. + */ + public int getMask() { + return mCapabilities; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Capabilities)) return false; + + Capabilities that = (Capabilities) o; + + return mCapabilities == that.mCapabilities; + } + + @Override + public int hashCode() { + return mCapabilities; + } + + @Override + public String toString() { + return "Capabilities: " + Integer.toBinaryString(mCapabilities); + } + } + private final Set mStatusCallbacks = Collections.newSetFromMap( new WeakHashMap()); - private @ImsState int mState = STATE_NOT_AVAILABLE; + private @ImsState int mState = STATE_UNAVAILABLE; private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; protected Context mContext; + private final Object mLock = new Object(); + private final RemoteCallbackList mCapabilityCallbacks + = new RemoteCallbackList<>(); + private Capabilities mCapabilityStatus = new Capabilities(); - public void setContext(Context context) { + public final void initialize(Context context, int slotId) { mContext = context; - } - - public void setSlotId(int slotId) { mSlotId = slotId; } - public int getFeatureState() { - return mState; + public final int getFeatureState() { + synchronized (mLock) { + return mState; + } } protected final void setFeatureState(@ImsState int state) { - if (mState != state) { - mState = state; - notifyFeatureState(state); + synchronized (mLock) { + if (mState != state) { + mState = state; + notifyFeatureState(state); + } } } - public void addImsFeatureStatusCallback(IImsFeatureStatusCallback c) { - if (c == null) { - return; - } + // Not final for testing, but shouldn't be extended! + @VisibleForTesting + public void addImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { try { // If we have just connected, send queued status. - c.notifyImsFeatureStatus(mState); + c.notifyImsFeatureStatus(getFeatureState()); // Add the callback if the callback completes successfully without a RemoteException. - synchronized (mStatusCallbacks) { + synchronized (mLock) { mStatusCallbacks.add(c); } } catch (RemoteException e) { @@ -133,23 +330,21 @@ public abstract class ImsFeature { } } - public void removeImsFeatureStatusCallback(IImsFeatureStatusCallback c) { - if (c == null) { - return; - } - synchronized (mStatusCallbacks) { + @VisibleForTesting + // Not final for testing, but should not be extended! + public void removeImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { + synchronized (mLock) { mStatusCallbacks.remove(c); } } /** * Internal method called by ImsFeature when setFeatureState has changed. - * @param state */ private void notifyFeatureState(@ImsState int state) { - synchronized (mStatusCallbacks) { + synchronized (mLock) { for (Iterator iter = mStatusCallbacks.iterator(); - iter.hasNext(); ) { + iter.hasNext(); ) { IImsFeatureStatusCallback callback = iter.next(); try { Log.i(LOG_TAG, "notifying ImsFeatureState=" + state); @@ -168,12 +363,12 @@ public abstract class ImsFeature { * Provide backwards compatibility using deprecated service UP/DOWN intents. */ private void sendImsServiceIntent(@ImsState int state) { - if(mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { + if (mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { return; } Intent intent; switch (state) { - case ImsFeature.STATE_NOT_AVAILABLE: + case ImsFeature.STATE_UNAVAILABLE: case ImsFeature.STATE_INITIALIZING: intent = new Intent(ACTION_IMS_SERVICE_DOWN); break; @@ -187,18 +382,92 @@ public abstract class ImsFeature { mContext.sendBroadcast(intent); } + public final void addCapabilityCallback(IImsCapabilityCallback c) { + mCapabilityCallbacks.register(c); + } + + public final void removeCapabilityCallback(IImsCapabilityCallback c) { + mCapabilityCallbacks.unregister(c); + } + /** - * Called when the feature is ready to use. + * @return the cached capabilities status for this feature. */ - public abstract void onFeatureReady(); + @VisibleForTesting + public Capabilities queryCapabilityStatus() { + synchronized (mLock) { + return mCapabilityStatus.copy(); + } + } + + // Called internally to request the change of enabled capabilities. + @VisibleForTesting + public final void requestChangeEnabledCapabilities(CapabilityChangeRequest request, + IImsCapabilityCallback c) throws RemoteException { + if (request == null) { + throw new IllegalArgumentException( + "ImsFeature#requestChangeEnabledCapabilities called with invalid params."); + } + changeEnabledCapabilities(request, new CapabilityCallbackProxy(c)); + } + + /** + * Called by the ImsFeature when the capabilities status has changed. + * + * @param c A {@link Capabilities} containing the new Capabilities status. + */ + protected final void notifyCapabilitiesStatusChanged(Capabilities c) { + synchronized (mLock) { + mCapabilityStatus = c.copy(); + } + int count = mCapabilityCallbacks.beginBroadcast(); + try { + for (int i = 0; i < count; i++) { + try { + mCapabilityCallbacks.getBroadcastItem(i).onCapabilitiesStatusChanged( + c.mCapabilities); + } catch (RemoteException e) { + Log.w(LOG_TAG, e + " " + "notifyCapabilitiesStatusChanged() - Skipping " + + "callback."); + } + } + } finally { + mCapabilityCallbacks.finishBroadcast(); + } + } /** - * Called when the feature is being removed and must be cleaned up. + * Features should override this method to receive Capability preference change requests from + * the framework using the provided {@link CapabilityChangeRequest}. If any of the capabilities + * in the {@link CapabilityChangeRequest} are not able to be completed due to an error, + * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError} should be called for + * each failed capability. + * + * @param request A {@link CapabilityChangeRequest} containing requested capabilities to + * enable/disable. + * @param c A {@link CapabilityCallbackProxy}, which will be used to call back to the framework + * setting a subset of these capabilities fail, using + * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError}. + */ + public abstract void changeEnabledCapabilities(CapabilityChangeRequest request, + CapabilityCallbackProxy c); + + /** + * Called when the framework is removing this feature and it needs to be cleaned up. */ public abstract void onFeatureRemoved(); /** - * @return Binder instance + * Called when the feature has been initialized and communication with the framework is set up. + * Any attempt by this feature to access the framework before this method is called will return + * with an {@link IllegalStateException}. + * The IMS provider should use this method to trigger registration for this feature on the IMS + * network, if needed. + */ + public abstract void onFeatureReady(); + + /** + * @return Binder instance that the framework will use to communicate with this feature. */ - public abstract IInterface getBinder(); + protected abstract IInterface getBinder(); } diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java deleted file mode 100644 index 68d63dfbe32..00000000000 --- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java +++ /dev/null @@ -1,439 +0,0 @@ -/* - * 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.telephony.ims.feature; - -import android.app.PendingIntent; -import android.os.Message; -import android.os.RemoteException; -import android.telephony.ims.internal.stub.SmsImplBase; - -import com.android.ims.ImsCallProfile; -import com.android.ims.internal.IImsCallSession; -import com.android.ims.internal.IImsConfig; -import com.android.ims.internal.IImsEcbm; -import com.android.ims.internal.IImsMMTelFeature; -import com.android.ims.internal.IImsMultiEndpoint; -import com.android.ims.internal.IImsRegistrationListener; -import com.android.ims.internal.IImsSmsListener; -import com.android.ims.internal.IImsUt; -import com.android.ims.internal.ImsCallSession; - -/** - * Base implementation for MMTel. - * Any class wishing to use MMTelFeature should extend this class and implement all methods that the - * service supports. - * - * @hide - */ - -public class MMTelFeature extends ImsFeature { - - // Lock for feature synchronization - private final Object mLock = new Object(); - - private final IImsMMTelFeature mImsMMTelBinder = new IImsMMTelFeature.Stub() { - - @Override - public int startSession(PendingIntent incomingCallIntent, - IImsRegistrationListener listener) throws RemoteException { - synchronized (mLock) { - return MMTelFeature.this.startSession(incomingCallIntent, listener); - } - } - - @Override - public void endSession(int sessionId) throws RemoteException { - synchronized (mLock) { - MMTelFeature.this.endSession(sessionId); - } - } - - @Override - public boolean isConnected(int callSessionType, int callType) - throws RemoteException { - synchronized (mLock) { - return MMTelFeature.this.isConnected(callSessionType, callType); - } - } - - @Override - public boolean isOpened() throws RemoteException { - synchronized (mLock) { - return MMTelFeature.this.isOpened(); - } - } - - @Override - public int getFeatureStatus() throws RemoteException { - synchronized (mLock) { - return MMTelFeature.this.getFeatureState(); - } - } - - @Override - public void addRegistrationListener(IImsRegistrationListener listener) - throws RemoteException { - synchronized (mLock) { - MMTelFeature.this.addRegistrationListener(listener); - } - } - - @Override - public void removeRegistrationListener(IImsRegistrationListener listener) - throws RemoteException { - synchronized (mLock) { - MMTelFeature.this.removeRegistrationListener(listener); - } - } - - @Override - public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType) - throws RemoteException { - synchronized (mLock) { - return MMTelFeature.this.createCallProfile(sessionId, callSessionType, callType); - } - } - - @Override - public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile) - throws RemoteException { - synchronized (mLock) { - return MMTelFeature.this.createCallSession(sessionId, profile); - } - } - - @Override - public IImsCallSession getPendingCallSession(int sessionId, String callId) - throws RemoteException { - synchronized (mLock) { - return MMTelFeature.this.getPendingCallSession(sessionId, callId); - } - } - - @Override - public IImsUt getUtInterface() throws RemoteException { - synchronized (mLock) { - return MMTelFeature.this.getUtInterface(); - } - } - - @Override - public IImsConfig getConfigInterface() throws RemoteException { - synchronized (mLock) { - return MMTelFeature.this.getConfigInterface(); - } - } - - @Override - public void turnOnIms() throws RemoteException { - synchronized (mLock) { - MMTelFeature.this.turnOnIms(); - } - } - - @Override - public void turnOffIms() throws RemoteException { - synchronized (mLock) { - MMTelFeature.this.turnOffIms(); - } - } - - @Override - public IImsEcbm getEcbmInterface() throws RemoteException { - synchronized (mLock) { - return MMTelFeature.this.getEcbmInterface(); - } - } - - @Override - public void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException { - synchronized (mLock) { - MMTelFeature.this.setUiTTYMode(uiTtyMode, onComplete); - } - } - - @Override - public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { - synchronized (mLock) { - return MMTelFeature.this.getMultiEndpointInterface(); - } - } - - @Override - public void setSmsListener(IImsSmsListener l) throws RemoteException { - synchronized (mLock) { - MMTelFeature.this.setSmsListener(l); - } - } - - @Override - public void sendSms(int token, int messageRef, String format, String smsc, boolean retry, - byte[] pdu) { - synchronized (mLock) { - MMTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu); - } - } - - @Override - public void acknowledgeSms(int token, int messageRef, int result) { - synchronized (mLock) { - MMTelFeature.this.acknowledgeSms(token, messageRef, result); - } - } - - @Override - public void acknowledgeSmsReport(int token, int messageRef, int result) { - synchronized (mLock) { - MMTelFeature.this.acknowledgeSmsReport(token, messageRef, result); - } - } - - @Override - public String getSmsFormat() { - synchronized (mLock) { - return MMTelFeature.this.getSmsFormat(); - } - } - - @Override - public void onSmsReady() { - synchronized (mLock) { - MMTelFeature.this.onSmsReady(); - } - } - }; - - /** - * @hide - */ - @Override - public final IImsMMTelFeature getBinder() { - return mImsMMTelBinder; - } - - /** - * Notifies the MMTel feature that you would like to start a session. This should always be - * done before making/receiving IMS calls. The IMS service will register the device to the - * operator's network with the credentials (from ISIM) periodically in order to receive calls - * from the operator's network. When the IMS service receives a new call, it will send out an - * intent with the provided action string. The intent contains a call ID extra - * {@link IImsCallSession#getCallId} and it can be used to take a call. - * - * @param incomingCallIntent When an incoming call is received, the IMS service will call - * {@link PendingIntent#send} to send back the intent to the caller with - * ImsManager#INCOMING_CALL_RESULT_CODE as the result code and the intent to fill in the call - * ID; It cannot be null. - * @param listener To listen to IMS registration events; It cannot be null - * @return an integer (greater than 0) representing the session id associated with the session - * that has been started. - */ - public int startSession(PendingIntent incomingCallIntent, IImsRegistrationListener listener) { - return 0; - } - - /** - * End a previously started session using the associated sessionId. - * @param sessionId an integer (greater than 0) representing the ongoing session. See - * {@link #startSession}. - */ - public void endSession(int sessionId) { - } - - /** - * Checks if the IMS service has successfully registered to the IMS network with the specified - * service & call type. - * - * @param callSessionType a service type that is specified in {@link ImsCallProfile} - * {@link ImsCallProfile#SERVICE_TYPE_NORMAL} - * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY} - * @param callType a call type that is specified in {@link ImsCallProfile} - * {@link ImsCallProfile#CALL_TYPE_VOICE_N_VIDEO} - * {@link ImsCallProfile#CALL_TYPE_VOICE} - * {@link ImsCallProfile#CALL_TYPE_VT} - * {@link ImsCallProfile#CALL_TYPE_VS} - * @return true if the specified service id is connected to the IMS network; false otherwise - */ - public boolean isConnected(int callSessionType, int callType) { - return false; - } - - /** - * Checks if the specified IMS service is opened. - * - * @return true if the specified service id is opened; false otherwise - */ - public boolean isOpened() { - return false; - } - - /** - * Add a new registration listener for the client associated with the session Id. - * @param listener An implementation of IImsRegistrationListener. - */ - public void addRegistrationListener(IImsRegistrationListener listener) { - } - - /** - * Remove a previously registered listener using {@link #addRegistrationListener} for the client - * associated with the session Id. - * @param listener A previously registered IImsRegistrationListener - */ - public void removeRegistrationListener(IImsRegistrationListener listener) { - } - - /** - * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state. - * - * @param sessionId a session id which is obtained from {@link #startSession} - * @param callSessionType a service type that is specified in {@link ImsCallProfile} - * {@link ImsCallProfile#SERVICE_TYPE_NONE} - * {@link ImsCallProfile#SERVICE_TYPE_NORMAL} - * {@link ImsCallProfile#SERVICE_TYPE_EMERGENCY} - * @param callType a call type that is specified in {@link ImsCallProfile} - * {@link ImsCallProfile#CALL_TYPE_VOICE} - * {@link ImsCallProfile#CALL_TYPE_VT} - * {@link ImsCallProfile#CALL_TYPE_VT_TX} - * {@link ImsCallProfile#CALL_TYPE_VT_RX} - * {@link ImsCallProfile#CALL_TYPE_VT_NODIR} - * {@link ImsCallProfile#CALL_TYPE_VS} - * {@link ImsCallProfile#CALL_TYPE_VS_TX} - * {@link ImsCallProfile#CALL_TYPE_VS_RX} - * @return a {@link ImsCallProfile} object - */ - public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType) { - return null; - } - - /** - * Creates an {@link ImsCallSession} with the specified call profile. - * Use other methods, if applicable, instead of interacting with - * {@link ImsCallSession} directly. - * - * @param sessionId a session id which is obtained from {@link #startSession} - * @param profile a call profile to make the call - */ - public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile) { - return null; - } - - /** - * Retrieves the call session associated with a pending call. - * - * @param sessionId a session id which is obtained from {@link #startSession} - * @param callId a call id to make the call - */ - public IImsCallSession getPendingCallSession(int sessionId, String callId) { - return null; - } - - /** - * @return The Ut interface for the supplementary service configuration. - */ - public IImsUt getUtInterface() { - return null; - } - - /** - * @return The config interface for IMS Configuration - */ - public IImsConfig getConfigInterface() { - return null; - } - - /** - * Signal the MMTelFeature to turn on IMS when it has been turned off using {@link #turnOffIms} - */ - public void turnOnIms() { - } - - /** - * Signal the MMTelFeature to turn off IMS when it has been turned on using {@link #turnOnIms} - */ - public void turnOffIms() { - } - - /** - * @return The Emergency call-back mode interface for emergency VoLTE calls that support it. - */ - public IImsEcbm getEcbmInterface() { - return null; - } - - /** - * Sets the current UI TTY mode for the MMTelFeature. - * @param uiTtyMode An integer containing the new UI TTY Mode. - * @param onComplete A {@link Message} to be used when the mode has been set. - */ - public void setUiTTYMode(int uiTtyMode, Message onComplete) { - } - - /** - * @return MultiEndpoint interface for DEP notifications - */ - public IImsMultiEndpoint getMultiEndpointInterface() { - return null; - } - - private void setSmsListener(IImsSmsListener listener) { - getSmsImplementation().registerSmsListener(listener); - } - - private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, - byte[] pdu) { - getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu); - } - - private void acknowledgeSms(int token, int messageRef, - @SmsImplBase.DeliverStatusResult int result) { - getSmsImplementation().acknowledgeSms(token, messageRef, result); - } - - private void acknowledgeSmsReport(int token, int messageRef, - @SmsImplBase.StatusReportResult int result) { - getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); - } - - private void onSmsReady() { - getSmsImplementation().onReady(); - } - - /** - * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default - * non-functional implementation is returned. - * - * @return an instance of {@link SmsImplBase} which should be implemented by the IMS Provider. - */ - protected SmsImplBase getSmsImplementation() { - return new SmsImplBase(); - } - - public String getSmsFormat() { - return getSmsImplementation().getSmsFormat(); - } - - @Override - public void onFeatureReady() { - - } - - /** - * {@inheritDoc} - */ - public void onFeatureRemoved() { - - } -} diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java similarity index 81% rename from telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java rename to telephony/java/android/telephony/ims/feature/MmTelFeature.java index 23e0302ab4f..83214b32fc3 100644 --- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open 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,17 +14,20 @@ * limitations under the License */ -package android.telephony.ims.internal.feature; +package android.telephony.ims.feature; import android.annotation.IntDef; +import android.net.Uri; +import android.os.Bundle; import android.os.Message; import android.os.RemoteException; import android.telecom.TelecomManager; -import android.telephony.ims.internal.aidl.IImsCapabilityCallback; -import android.telephony.ims.internal.aidl.IImsMmTelFeature; -import android.telephony.ims.internal.aidl.IImsMmTelListener; -import android.telephony.ims.internal.stub.SmsImplBase; +import android.telephony.ims.aidl.IImsCapabilityCallback; +import android.telephony.ims.aidl.IImsMmTelFeature; +import android.telephony.ims.aidl.IImsMmTelListener; +import android.telephony.ims.aidl.IImsSmsListener; import android.telephony.ims.stub.ImsRegistrationImplBase; +import android.telephony.ims.stub.ImsSmsImplBase; import android.telephony.ims.stub.ImsEcbmImplBase; import android.telephony.ims.stub.ImsMultiEndpointImplBase; import android.telephony.ims.stub.ImsUtImplBase; @@ -34,7 +37,6 @@ import com.android.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMultiEndpoint; -import com.android.ims.internal.IImsSmsListener; import com.android.ims.internal.IImsUt; import com.android.ims.internal.ImsCallSession; import com.android.internal.annotations.VisibleForTesting; @@ -87,6 +89,13 @@ public class MmTelFeature extends ImsFeature { } } + @Override + public int shouldProcessCall(String[] numbers) { + synchronized (mLock) { + return MmTelFeature.this.shouldProcessCall(numbers); + } + } + @Override public IImsUt getUtInterface() throws RemoteException { synchronized (mLock) { @@ -199,6 +208,10 @@ public class MmTelFeature extends ImsFeature { mCapabilities = c.mCapabilities; } + public MmTelCapabilities(int capabilities) { + mCapabilities = capabilities; + } + @IntDef(flag = true, value = { CAPABILITY_TYPE_VOICE, @@ -243,6 +256,21 @@ public class MmTelFeature extends ImsFeature { public final boolean isCapable(@MmTelCapability int capabilities) { return super.isCapable(capabilities); } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("MmTel Capabilities - ["); + builder.append("Voice: "); + builder.append(isCapable(CAPABILITY_TYPE_VOICE)); + builder.append(" Video: "); + builder.append(isCapable(CAPABILITY_TYPE_VIDEO)); + builder.append(" UT: "); + builder.append(isCapable(CAPABILITY_TYPE_UT)); + builder.append(" SMS: "); + builder.append(isCapable(CAPABILITY_TYPE_SMS)); + builder.append("]"); + return builder.toString(); + } } /** @@ -250,9 +278,13 @@ public class MmTelFeature extends ImsFeature { */ public static class Listener extends IImsMmTelListener.Stub { + /** + * Called when the IMS provider receives an incoming call. + * @param c The {@link ImsCallSession} associated with the new call. + */ @Override - public final void onIncomingCall(IImsCallSession c) { - onIncomingCall(new ImsCallSession(c)); + public void onIncomingCall(IImsCallSession c, Bundle extras) { + } /** @@ -263,15 +295,34 @@ public class MmTelFeature extends ImsFeature { public void onVoiceMessageCountUpdate(int count) { } - - /** - * Called when the IMS provider receives an incoming call. - * @param c The {@link ImsCallSession} associated with the new call. - */ - public void onIncomingCall(ImsCallSession c) { - } } + /** + * To be returned by {@link #shouldProcessCall(Uri[])} when the ImsService should process the + * outgoing call as IMS. + */ + public static final int PROCESS_CALL_IMS = 0; + /** + * To be returned by {@link #shouldProcessCall(Uri[])} when the telephony framework should not + * process the outgoing NON_EMERGENCY call as IMS and should instead use circuit switch. + */ + public static final int PROCESS_CALL_CSFB = 1; + /** + * To be returned by {@link #shouldProcessCall(Uri[])} when the telephony framework should not + * process the outgoing EMERGENCY call as IMS and should instead use circuit switch. + */ + public static final int PROCESS_CALL_EMERGENCY_CSFB = 2; + + @IntDef(flag = true, + value = { + PROCESS_CALL_IMS, + PROCESS_CALL_CSFB, + PROCESS_CALL_EMERGENCY_CSFB + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ProcessCallResult {} + + // Lock for feature synchronization private final Object mLock = new Object(); private IImsMmTelListener mListener; @@ -284,6 +335,9 @@ public class MmTelFeature extends ImsFeature { synchronized (mLock) { mListener = listener; } + if (mListener != null) { + onFeatureReady(); + } } private void queryCapabilityConfigurationInternal(int capability, int radioTech, @@ -332,12 +386,13 @@ public class MmTelFeature extends ImsFeature { * @throws RemoteException if the connection to the framework is not available. If this happens, * the call should be no longer considered active and should be cleaned up. * */ - protected final void notifyIncomingCall(ImsCallSession c) throws RemoteException { + protected final void notifyIncomingCall(ImsCallSession c, Bundle extras) + throws RemoteException { synchronized (mLock) { if (mListener == null) { throw new IllegalStateException("Session is not available."); } - mListener.onIncomingCall(c.getSession()); + mListener.onIncomingCall(c.getSession(), extras); } } @@ -408,6 +463,19 @@ public class MmTelFeature extends ImsFeature { return null; } + /** + * Called by the framework to determine if the outgoing call, designated by the outgoing + * {@link Uri}s, should be processed as an IMS call or CSFB call. + * @param numbers An array of {@link String}s that will be used for placing the call. There can + * be multiple {@link Strings}s listed in the case when we want to place an outgoing + * call as a conference. + * @return a {@link ProcessCallResult} to the framework, which will be used to determine if the + * call wil lbe placed over IMS or via CSFB. + */ + public @ProcessCallResult int shouldProcessCall(String[] numbers) { + return PROCESS_CALL_IMS; + } + /** * @return The Ut interface for the supplementary service configuration. */ @@ -445,22 +513,22 @@ public class MmTelFeature extends ImsFeature { // Base Implementation - Should be overridden } - public void setSmsListener(IImsSmsListener listener) { + private void setSmsListener(IImsSmsListener listener) { getSmsImplementation().registerSmsListener(listener); } - public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, + private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) { getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu); } - public void acknowledgeSms(int token, int messageRef, - @SmsImplBase.DeliverStatusResult int result) { + private void acknowledgeSms(int token, int messageRef, + @ImsSmsImplBase.DeliverStatusResult int result) { getSmsImplementation().acknowledgeSms(token, messageRef, result); } - public void acknowledgeSmsReport(int token, int messageRef, - @SmsImplBase.StatusReportResult int result) { + private void acknowledgeSmsReport(int token, int messageRef, + @ImsSmsImplBase.StatusReportResult int result) { getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); } @@ -468,11 +536,11 @@ public class MmTelFeature extends ImsFeature { * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default * non-functional implementation is returned. * - * @return an instance of {@link SmsImplBase} which should be implemented by the IMS + * @return an instance of {@link ImsSmsImplBase} which should be implemented by the IMS * Provider. */ - public SmsImplBase getSmsImplementation() { - return new SmsImplBase(); + protected ImsSmsImplBase getSmsImplementation() { + return new ImsSmsImplBase(); } private String getSmsFormat() { diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java index 40c5181d6bc..3b6f7e0f1a8 100644 --- a/telephony/java/android/telephony/ims/feature/RcsFeature.java +++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package android.telephony.ims.feature; -import com.android.ims.internal.IImsRcsFeature; +import android.telephony.ims.aidl.IImsRcsFeature; /** * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend @@ -36,8 +36,9 @@ public class RcsFeature extends ImsFeature { } @Override - public void onFeatureReady() { - + public void changeEnabledCapabilities(CapabilityChangeRequest request, + CapabilityCallbackProxy c) { + // Do nothing for base implementation. } @Override @@ -45,6 +46,12 @@ public class RcsFeature extends ImsFeature { } + /**{@inheritDoc}*/ + @Override + public void onFeatureReady() { + + } + @Override public final IImsRcsFeature getBinder() { return mImsRcsBinder; diff --git a/telephony/java/android/telephony/ims/internal/ImsService.java b/telephony/java/android/telephony/ims/internal/ImsService.java deleted file mode 100644 index afaf33294d8..00000000000 --- a/telephony/java/android/telephony/ims/internal/ImsService.java +++ /dev/null @@ -1,339 +0,0 @@ -/* - * 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.telephony.ims.internal; - -import android.app.Service; -import android.content.Intent; -import android.os.IBinder; -import android.os.RemoteException; -import android.telephony.CarrierConfigManager; -import android.telephony.ims.internal.aidl.IImsConfig; -import android.telephony.ims.internal.aidl.IImsMmTelFeature; -import android.telephony.ims.internal.aidl.IImsRcsFeature; -import android.telephony.ims.internal.aidl.IImsServiceController; -import android.telephony.ims.internal.aidl.IImsServiceControllerListener; -import android.telephony.ims.internal.feature.ImsFeature; -import android.telephony.ims.internal.feature.MmTelFeature; -import android.telephony.ims.internal.feature.RcsFeature; -import android.telephony.ims.internal.stub.ImsConfigImplBase; -import android.telephony.ims.internal.stub.ImsFeatureConfiguration; -import android.telephony.ims.stub.ImsRegistrationImplBase; -import android.util.Log; -import android.util.SparseArray; - -import com.android.ims.internal.IImsFeatureStatusCallback; -import com.android.ims.internal.IImsRegistration; -import com.android.internal.annotations.VisibleForTesting; - -/** - * Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend - * ImsService must register the service in their AndroidManifest to be detected by the framework. - * First, the application must declare that they use the "android.permission.BIND_IMS_SERVICE" - * permission. Then, the ImsService definition in the manifest must follow the following format: - * - * ... - * - * - * - * - * - * - * - * ... - * - * The telephony framework will then bind to the ImsService you have defined in your manifest - * if you are either: - * 1) Defined as the default ImsService for the device in the device overlay using - * "config_ims_package". - * 2) Defined as a Carrier Provided ImsService in the Carrier Configuration using - * {@link CarrierConfigManager#KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING}. - * - * The features that are currently supported in an ImsService are: - * - RCS_FEATURE: This ImsService implements the RcsFeature class. - * - MMTEL_FEATURE: This ImsService implements the MmTelFeature class. - * @hide - */ -public class ImsService extends Service { - - private static final String LOG_TAG = "ImsService"; - - /** - * The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService. - * @hide - */ - public static final String SERVICE_INTERFACE = "android.telephony.ims.ImsService"; - - // A map of slot Id -> map of features (indexed by ImsFeature feature id) corresponding to that - // slot. - // We keep track of this to facilitate cleanup of the IImsFeatureStatusCallback and - // call ImsFeature#onFeatureRemoved. - private final SparseArray> mFeaturesBySlot = new SparseArray<>(); - - private IImsServiceControllerListener mListener; - - - /** - * Listener that notifies the framework of ImsService changes. - */ - public static class Listener extends IImsServiceControllerListener.Stub { - /** - * The IMS features that this ImsService supports has changed. - * @param c a new {@link ImsFeatureConfiguration} containing {@link ImsFeature.FeatureType}s - * that this ImsService supports. This may trigger the addition/removal of feature - * in this service. - */ - public void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) { - } - } - - /** - * @hide - */ - protected final IBinder mImsServiceController = new IImsServiceController.Stub() { - @Override - public void setListener(IImsServiceControllerListener l) { - mListener = l; - } - - @Override - public IImsMmTelFeature createMmTelFeature(int slotId, IImsFeatureStatusCallback c) { - return createMmTelFeatureInternal(slotId, c); - } - - @Override - public IImsRcsFeature createRcsFeature(int slotId, IImsFeatureStatusCallback c) { - return createRcsFeatureInternal(slotId, c); - } - - @Override - public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) - throws RemoteException { - ImsService.this.removeImsFeature(slotId, featureType, c); - } - - @Override - public ImsFeatureConfiguration querySupportedImsFeatures() { - return ImsService.this.querySupportedImsFeatures(); - } - - @Override - public void notifyImsServiceReadyForFeatureCreation() { - ImsService.this.readyForFeatureCreation(); - } - - @Override - public void notifyImsFeatureReady(int slotId, int featureType) - throws RemoteException { - ImsService.this.notifyImsFeatureReady(slotId, featureType); - } - - @Override - public IImsConfig getConfig(int slotId) throws RemoteException { - ImsConfigImplBase c = ImsService.this.getConfig(slotId); - return c != null ? c.getBinder() : null; - } - - @Override - public IImsRegistration getRegistration(int slotId) throws RemoteException { - ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId); - return r != null ? r.getBinder() : null; - } - }; - - /** - * @hide - */ - @Override - public IBinder onBind(Intent intent) { - if(SERVICE_INTERFACE.equals(intent.getAction())) { - Log.i(LOG_TAG, "ImsService Bound."); - return mImsServiceController; - } - return null; - } - - /** - * @hide - */ - @VisibleForTesting - public SparseArray getFeatures(int slotId) { - return mFeaturesBySlot.get(slotId); - } - - private IImsMmTelFeature createMmTelFeatureInternal(int slotId, - IImsFeatureStatusCallback c) { - MmTelFeature f = createMmTelFeature(slotId); - if (f != null) { - setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL, c); - return f.getBinder(); - } else { - Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned."); - return null; - } - } - - private IImsRcsFeature createRcsFeatureInternal(int slotId, - IImsFeatureStatusCallback c) { - RcsFeature f = createRcsFeature(slotId); - if (f != null) { - setupFeature(f, slotId, ImsFeature.FEATURE_RCS, c); - return f.getBinder(); - } else { - Log.e(LOG_TAG, "createRcsFeatureInternal: null feature returned."); - return null; - } - } - - private void setupFeature(ImsFeature f, int slotId, int featureType, - IImsFeatureStatusCallback c) { - f.addImsFeatureStatusCallback(c); - f.initialize(this, slotId); - addImsFeature(slotId, featureType, f); - } - - private void addImsFeature(int slotId, int featureType, ImsFeature f) { - synchronized (mFeaturesBySlot) { - // Get SparseArray for Features, by querying slot Id - SparseArray features = mFeaturesBySlot.get(slotId); - if (features == null) { - // Populate new SparseArray of features if it doesn't exist for this slot yet. - features = new SparseArray<>(); - mFeaturesBySlot.put(slotId, features); - } - features.put(featureType, f); - } - } - - private void removeImsFeature(int slotId, int featureType, - IImsFeatureStatusCallback c) { - synchronized (mFeaturesBySlot) { - // get ImsFeature associated with the slot/feature - SparseArray features = mFeaturesBySlot.get(slotId); - if (features == null) { - Log.w(LOG_TAG, "Can not remove ImsFeature. No ImsFeatures exist on slot " - + slotId); - return; - } - ImsFeature f = features.get(featureType); - if (f == null) { - Log.w(LOG_TAG, "Can not remove ImsFeature. No feature with type " - + featureType + " exists on slot " + slotId); - return; - } - f.removeImsFeatureStatusCallback(c); - f.onFeatureRemoved(); - features.remove(featureType); - } - } - - private void notifyImsFeatureReady(int slotId, int featureType) { - synchronized (mFeaturesBySlot) { - // get ImsFeature associated with the slot/feature - SparseArray features = mFeaturesBySlot.get(slotId); - if (features == null) { - Log.w(LOG_TAG, "Can not notify ImsFeature ready. No ImsFeatures exist on " + - "slot " + slotId); - return; - } - ImsFeature f = features.get(featureType); - if (f == null) { - Log.w(LOG_TAG, "Can not notify ImsFeature ready. No feature with type " - + featureType + " exists on slot " + slotId); - return; - } - f.onFeatureReady(); - } - } - - /** - * When called, provide the {@link ImsFeatureConfiguration} that this ImsService currently - * supports. This will trigger the framework to set up the {@link ImsFeature}s that correspond - * to the {@link ImsFeature.FeatureType}s configured here. - * @return an {@link ImsFeatureConfiguration} containing Features this ImsService supports, - * defined in {@link ImsFeature.FeatureType}. - */ - public ImsFeatureConfiguration querySupportedImsFeatures() { - // Return empty for base implementation - return new ImsFeatureConfiguration(); - } - - /** - * Updates the framework with a new {@link ImsFeatureConfiguration} containing the updated - * features, defined in {@link ImsFeature.FeatureType} that this ImsService supports. This may - * trigger the framework to add/remove new ImsFeatures, depending on the configuration. - */ - public final void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) - throws RemoteException { - if (mListener == null) { - throw new IllegalStateException("Framework is not ready"); - } - mListener.onUpdateSupportedImsFeatures(c); - } - - /** - * The ImsService has been bound and is ready for ImsFeature creation based on the Features that - * the ImsService has registered for with the framework, either in the manifest or via - * The ImsService should use this signal instead of onCreate/onBind or similar to perform - * feature initialization because the framework may bind to this service multiple times to - * query the ImsService's {@link ImsFeatureConfiguration} via - * {@link #querySupportedImsFeatures()}before creating features. - */ - public void readyForFeatureCreation() { - } - - /** - * When called, the framework is requesting that a new MmTelFeature is created for the specified - * slot. - * - * @param slotId The slot ID that the MMTel Feature is being created for. - * @return The newly created MmTelFeature associated with the slot or null if the feature is not - * supported. - */ - public MmTelFeature createMmTelFeature(int slotId) { - return null; - } - - /** - * When called, the framework is requesting that a new RcsFeature is created for the specified - * slot - * - * @param slotId The slot ID that the RCS Feature is being created for. - * @return The newly created RcsFeature associated with the slot or null if the feature is not - * supported. - */ - public RcsFeature createRcsFeature(int slotId) { - return null; - } - - /** - * @param slotId The slot that the IMS configuration is associated with. - * @return ImsConfig implementation that is associated with the specified slot. - */ - public ImsConfigImplBase getConfig(int slotId) { - return new ImsConfigImplBase(); - } - - /** - * @param slotId The slot that is associated with the IMS Registration. - * @return the ImsRegistration implementation associated with the slot. - */ - public ImsRegistrationImplBase getRegistration(int slotId) { - return new ImsRegistrationImplBase(); - } -} diff --git a/telephony/java/android/telephony/ims/internal/feature/ImsFeature.java b/telephony/java/android/telephony/ims/internal/feature/ImsFeature.java deleted file mode 100644 index 9f82ad241ea..00000000000 --- a/telephony/java/android/telephony/ims/internal/feature/ImsFeature.java +++ /dev/null @@ -1,462 +0,0 @@ -/* - * 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.telephony.ims.internal.feature; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.content.Context; -import android.content.Intent; -import android.os.IInterface; -import android.os.RemoteCallbackList; -import android.os.RemoteException; -import android.telephony.SubscriptionManager; -import android.telephony.ims.internal.aidl.IImsCapabilityCallback; -import android.util.Log; - -import com.android.ims.internal.IImsFeatureStatusCallback; -import com.android.internal.annotations.VisibleForTesting; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Collections; -import java.util.Iterator; -import java.util.Set; -import java.util.WeakHashMap; - -/** - * Base class for all IMS features that are supported by the framework. - * - * @hide - */ -public abstract class ImsFeature { - - private static final String LOG_TAG = "ImsFeature"; - - /** - * Action to broadcast when ImsService is up. - * Internal use only. - * Only defined here separately for compatibility purposes with the old ImsService. - * - * @hide - */ - public static final String ACTION_IMS_SERVICE_UP = - "com.android.ims.IMS_SERVICE_UP"; - - /** - * Action to broadcast when ImsService is down. - * Internal use only. - * Only defined here separately for compatibility purposes with the old ImsService. - * - * @hide - */ - public static final String ACTION_IMS_SERVICE_DOWN = - "com.android.ims.IMS_SERVICE_DOWN"; - - /** - * Part of the ACTION_IMS_SERVICE_UP or _DOWN intents. - * A long value; the phone ID corresponding to the IMS service coming up or down. - * Only defined here separately for compatibility purposes with the old ImsService. - * - * @hide - */ - public static final String EXTRA_PHONE_ID = "android:phone_id"; - - // Invalid feature value - public static final int FEATURE_INVALID = -1; - // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously - // defined values in ImsServiceClass for compatibility purposes. - public static final int FEATURE_EMERGENCY_MMTEL = 0; - public static final int FEATURE_MMTEL = 1; - public static final int FEATURE_RCS = 2; - // Total number of features defined - public static final int FEATURE_MAX = 3; - - // Integer values defining IMS features that are supported in ImsFeature. - @IntDef(flag = true, - value = { - FEATURE_EMERGENCY_MMTEL, - FEATURE_MMTEL, - FEATURE_RCS - }) - @Retention(RetentionPolicy.SOURCE) - public @interface FeatureType {} - - // Integer values defining the state of the ImsFeature at any time. - @IntDef(flag = true, - value = { - STATE_UNAVAILABLE, - STATE_INITIALIZING, - STATE_READY, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ImsState {} - - public static final int STATE_UNAVAILABLE = 0; - public static final int STATE_INITIALIZING = 1; - public static final int STATE_READY = 2; - - // Integer values defining the result codes that should be returned from - // {@link changeEnabledCapabilities} when the framework tries to set a feature's capability. - @IntDef(flag = true, - value = { - CAPABILITY_ERROR_GENERIC, - CAPABILITY_SUCCESS - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ImsCapabilityError {} - - public static final int CAPABILITY_ERROR_GENERIC = -1; - public static final int CAPABILITY_SUCCESS = 0; - - - /** - * The framework implements this callback in order to register for Feature Capability status - * updates, via {@link #onCapabilitiesStatusChanged(Capabilities)}, query Capability - * configurations, via {@link #onQueryCapabilityConfiguration}, as well as to receive error - * callbacks when the ImsService can not change the capability as requested, via - * {@link #onChangeCapabilityConfigurationError}. - */ - public static class CapabilityCallback extends IImsCapabilityCallback.Stub { - - @Override - public final void onCapabilitiesStatusChanged(int config) throws RemoteException { - onCapabilitiesStatusChanged(new Capabilities(config)); - } - - /** - * Returns the result of a query for the capability configuration of a requested capability. - * - * @param capability The capability that was requested. - * @param radioTech The IMS radio technology associated with the capability. - * @param isEnabled true if the capability is enabled, false otherwise. - */ - @Override - public void onQueryCapabilityConfiguration(int capability, int radioTech, - boolean isEnabled) { - - } - - /** - * Called when a change to the capability configuration has returned an error. - * - * @param capability The capability that was requested to be changed. - * @param radioTech The IMS radio technology associated with the capability. - * @param reason error associated with the failure to change configuration. - */ - @Override - public void onChangeCapabilityConfigurationError(int capability, int radioTech, - int reason) { - } - - /** - * The status of the feature's capabilities has changed to either available or unavailable. - * If unavailable, the feature is not able to support the unavailable capability at this - * time. - * - * @param config The new availability of the capabilities. - */ - public void onCapabilitiesStatusChanged(Capabilities config) { - } - } - - /** - * Used by the ImsFeature to call back to the CapabilityCallback that the framework has - * provided. - */ - protected static class CapabilityCallbackProxy { - private final IImsCapabilityCallback mCallback; - - public CapabilityCallbackProxy(IImsCapabilityCallback c) { - mCallback = c; - } - - /** - * This method notifies the provided framework callback that the request to change the - * indicated capability has failed and has not changed. - * - * @param capability The Capability that will be notified to the framework. - * @param radioTech The radio tech that this capability failed for. - * @param reason The reason this capability was unable to be changed. - */ - public void onChangeCapabilityConfigurationError(int capability, int radioTech, - @ImsCapabilityError int reason) { - try { - mCallback.onChangeCapabilityConfigurationError(capability, radioTech, reason); - } catch (RemoteException e) { - Log.e(LOG_TAG, "onChangeCapabilityConfigurationError called on dead binder."); - } - } - - public void onQueryCapabilityConfiguration(int capability, int radioTech, - boolean isEnabled) { - try { - mCallback.onQueryCapabilityConfiguration(capability, radioTech, isEnabled); - } catch (RemoteException e) { - Log.e(LOG_TAG, "onQueryCapabilityConfiguration called on dead binder."); - } - } - } - - /** - * Contains the capabilities defined and supported by an ImsFeature in the form of a bit mask. - */ - public static class Capabilities { - protected int mCapabilities = 0; - - public Capabilities() { - } - - protected Capabilities(int capabilities) { - mCapabilities = capabilities; - } - - /** - * @param capabilities Capabilities to be added to the configuration in the form of a - * bit mask. - */ - public void addCapabilities(int capabilities) { - mCapabilities |= capabilities; - } - - /** - * @param capabilities Capabilities to be removed to the configuration in the form of a - * bit mask. - */ - public void removeCapabilities(int capabilities) { - mCapabilities &= ~capabilities; - } - - /** - * @return true if all of the capabilities specified are capable. - */ - public boolean isCapable(int capabilities) { - return (mCapabilities & capabilities) == capabilities; - } - - public Capabilities copy() { - return new Capabilities(mCapabilities); - } - - /** - * @return a bitmask containing the capability flags directly. - */ - public int getMask() { - return mCapabilities; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof Capabilities)) return false; - - Capabilities that = (Capabilities) o; - - return mCapabilities == that.mCapabilities; - } - - @Override - public int hashCode() { - return mCapabilities; - } - } - - private final Set mStatusCallbacks = Collections.newSetFromMap( - new WeakHashMap()); - private @ImsState int mState = STATE_UNAVAILABLE; - private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; - private Context mContext; - private final Object mLock = new Object(); - private final RemoteCallbackList mCapabilityCallbacks - = new RemoteCallbackList<>(); - private Capabilities mCapabilityStatus = new Capabilities(); - - public final void initialize(Context context, int slotId) { - mContext = context; - mSlotId = slotId; - } - - public final int getFeatureState() { - synchronized (mLock) { - return mState; - } - } - - protected final void setFeatureState(@ImsState int state) { - synchronized (mLock) { - if (mState != state) { - mState = state; - notifyFeatureState(state); - } - } - } - - // Not final for testing, but shouldn't be extended! - @VisibleForTesting - public void addImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { - try { - // If we have just connected, send queued status. - c.notifyImsFeatureStatus(getFeatureState()); - // Add the callback if the callback completes successfully without a RemoteException. - synchronized (mLock) { - mStatusCallbacks.add(c); - } - } catch (RemoteException e) { - Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); - } - } - - @VisibleForTesting - // Not final for testing, but should not be extended! - public void removeImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { - synchronized (mLock) { - mStatusCallbacks.remove(c); - } - } - - /** - * Internal method called by ImsFeature when setFeatureState has changed. - */ - private void notifyFeatureState(@ImsState int state) { - synchronized (mLock) { - for (Iterator iter = mStatusCallbacks.iterator(); - iter.hasNext(); ) { - IImsFeatureStatusCallback callback = iter.next(); - try { - Log.i(LOG_TAG, "notifying ImsFeatureState=" + state); - callback.notifyImsFeatureStatus(state); - } catch (RemoteException e) { - // remove if the callback is no longer alive. - iter.remove(); - Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage()); - } - } - } - sendImsServiceIntent(state); - } - - /** - * Provide backwards compatibility using deprecated service UP/DOWN intents. - */ - private void sendImsServiceIntent(@ImsState int state) { - if (mContext == null || mSlotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) { - return; - } - Intent intent; - switch (state) { - case ImsFeature.STATE_UNAVAILABLE: - case ImsFeature.STATE_INITIALIZING: - intent = new Intent(ACTION_IMS_SERVICE_DOWN); - break; - case ImsFeature.STATE_READY: - intent = new Intent(ACTION_IMS_SERVICE_UP); - break; - default: - intent = new Intent(ACTION_IMS_SERVICE_DOWN); - } - intent.putExtra(EXTRA_PHONE_ID, mSlotId); - mContext.sendBroadcast(intent); - } - - public final void addCapabilityCallback(IImsCapabilityCallback c) { - mCapabilityCallbacks.register(c); - } - - public final void removeCapabilityCallback(IImsCapabilityCallback c) { - mCapabilityCallbacks.unregister(c); - } - - /** - * @return the cached capabilities status for this feature. - */ - @VisibleForTesting - public Capabilities queryCapabilityStatus() { - synchronized (mLock) { - return mCapabilityStatus.copy(); - } - } - - // Called internally to request the change of enabled capabilities. - @VisibleForTesting - public final void requestChangeEnabledCapabilities(CapabilityChangeRequest request, - IImsCapabilityCallback c) throws RemoteException { - if (request == null) { - throw new IllegalArgumentException( - "ImsFeature#requestChangeEnabledCapabilities called with invalid params."); - } - changeEnabledCapabilities(request, new CapabilityCallbackProxy(c)); - } - - /** - * Called by the ImsFeature when the capabilities status has changed. - * - * @param c A {@link Capabilities} containing the new Capabilities status. - */ - protected final void notifyCapabilitiesStatusChanged(Capabilities c) { - synchronized (mLock) { - mCapabilityStatus = c.copy(); - } - int count = mCapabilityCallbacks.beginBroadcast(); - try { - for (int i = 0; i < count; i++) { - try { - mCapabilityCallbacks.getBroadcastItem(i).onCapabilitiesStatusChanged( - c.mCapabilities); - } catch (RemoteException e) { - Log.w(LOG_TAG, e + " " + "notifyCapabilitiesStatusChanged() - Skipping " + - "callback."); - } - } - } finally { - mCapabilityCallbacks.finishBroadcast(); - } - } - - /** - * Features should override this method to receive Capability preference change requests from - * the framework using the provided {@link CapabilityChangeRequest}. If any of the capabilities - * in the {@link CapabilityChangeRequest} are not able to be completed due to an error, - * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError} should be called for - * each failed capability. - * - * @param request A {@link CapabilityChangeRequest} containing requested capabilities to - * enable/disable. - * @param c A {@link CapabilityCallbackProxy}, which will be used to call back to the framework - * setting a subset of these capabilities fail, using - * {@link CapabilityCallbackProxy#onChangeCapabilityConfigurationError}. - */ - public abstract void changeEnabledCapabilities(CapabilityChangeRequest request, - CapabilityCallbackProxy c); - - /** - * Called when the framework is removing this feature and it needs to be cleaned up. - */ - public abstract void onFeatureRemoved(); - - /** - * Called when the feature has been initialized and communication with the framework is set up. - * Any attempt by this feature to access the framework before this method is called will return - * with an {@link IllegalStateException}. - * The IMS provider should use this method to trigger registration for this feature on the IMS - * network, if needed. - */ - public abstract void onFeatureReady(); - - /** - * @return Binder instance that the framework will use to communicate with this feature. - */ - protected abstract IInterface getBinder(); -} diff --git a/telephony/java/android/telephony/ims/internal/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/internal/stub/ImsConfigImplBase.java deleted file mode 100644 index 33aec5dfb8a..00000000000 --- a/telephony/java/android/telephony/ims/internal/stub/ImsConfigImplBase.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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.telephony.ims.internal.stub; - -import android.os.RemoteCallbackList; -import android.os.RemoteException; -import android.telephony.ims.internal.aidl.IImsConfig; -import android.telephony.ims.internal.aidl.IImsConfigCallback; - -import com.android.ims.ImsConfig; - -/** - * Controls the modification of IMS specific configurations. For more information on the supported - * IMS configuration constants, see {@link ImsConfig}. - * - * @hide - */ - -public class ImsConfigImplBase { - - //TODO: Implement the Binder logic to call base APIs. Need to finish other ImsService Config - // work first. - private final IImsConfig mBinder = new IImsConfig.Stub() { - - @Override - public void addImsConfigCallback(IImsConfigCallback c) throws RemoteException { - ImsConfigImplBase.this.addImsConfigCallback(c); - } - - @Override - public void removeImsConfigCallback(IImsConfigCallback c) throws RemoteException { - ImsConfigImplBase.this.removeImsConfigCallback(c); - } - - @Override - public int getConfigInt(int item) throws RemoteException { - return Integer.MIN_VALUE; - } - - @Override - public String getConfigString(int item) throws RemoteException { - return null; - } - - @Override - public int setConfigInt(int item, int value) throws RemoteException { - return Integer.MIN_VALUE; - } - - @Override - public int setConfigString(int item, String value) throws RemoteException { - return Integer.MIN_VALUE; - } - }; - - public class Callback extends IImsConfigCallback.Stub { - - @Override - public final void onIntConfigChanged(int item, int value) throws RemoteException { - onConfigChanged(item, value); - } - - @Override - public final void onStringConfigChanged(int item, String value) throws RemoteException { - onConfigChanged(item, value); - } - - /** - * Called when the IMS configuration has changed. - * @param item the IMS configuration key constant, as defined in ImsConfig. - * @param value the new integer value of the IMS configuration constant. - */ - public void onConfigChanged(int item, int value) { - // Base Implementation - } - - /** - * Called when the IMS configuration has changed. - * @param item the IMS configuration key constant, as defined in ImsConfig. - * @param value the new String value of the IMS configuration constant. - */ - public void onConfigChanged(int item, String value) { - // Base Implementation - } - } - - private final RemoteCallbackList mCallbacks = new RemoteCallbackList<>(); - - /** - * Adds a {@link Callback} to the list of callbacks notified when a value in the configuration - * changes. - * @param c callback to add. - */ - private void addImsConfigCallback(IImsConfigCallback c) { - mCallbacks.register(c); - } - /** - * Removes a {@link Callback} to the list of callbacks notified when a value in the - * configuration changes. - * - * @param c callback to remove. - */ - private void removeImsConfigCallback(IImsConfigCallback c) { - mCallbacks.unregister(c); - } - - public final IImsConfig getBinder() { - return mBinder; - } - - /** - * Sets the value for IMS service/capabilities parameters by the operator device - * management entity. It sets the config item value in the provisioned storage - * from which the master value is derived. - * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in Integer format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. - */ - public int setConfig(int item, int value) { - // Base Implementation - To be overridden. - return ImsConfig.OperationStatusConstants.FAILED; - } - - /** - * Sets the value for IMS service/capabilities parameters by the operator device - * management entity. It sets the config item value in the provisioned storage - * from which the master value is derived. - * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in String format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. - */ - public int setConfig(int item, String value) { - return ImsConfig.OperationStatusConstants.FAILED; - } - - /** - * Gets the value for ims service/capabilities parameters from the provisioned - * value storage. - * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @return value in Integer format. - */ - public int getConfigInt(int item) { - return ImsConfig.OperationStatusConstants.FAILED; - } - - /** - * Gets the value for ims service/capabilities parameters from the provisioned - * value storage. - * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @return value in String format. - */ - public String getConfigString(int item) { - return null; - } -} diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java index 1670e6b9ba5..9b73c0cef38 100644 --- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,29 +18,21 @@ package android.telephony.ims.stub; import android.content.Context; import android.content.Intent; +import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.telephony.ims.aidl.IImsConfig; +import android.telephony.ims.aidl.IImsConfigCallback; import android.util.Log; import com.android.ims.ImsConfig; -import com.android.ims.ImsConfigListener; -import com.android.ims.internal.IImsConfig; import com.android.internal.annotations.VisibleForTesting; import java.lang.ref.WeakReference; import java.util.HashMap; - /** - * Base implementation of ImsConfig. - * Override the methods that your implementation of ImsConfig supports. - * - * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you - * will break other implementations of ImsConfig maintained by other ImsServices. - * - * Provides APIs to get/set the IMS service feature/capability/parameters. - * The config items include: - * 1) Items provisioned by the operator. - * 2) Items configured by user. Mainly service feature class. + * Controls the modification of IMS specific configurations. For more information on the supported + * IMS configuration constants, see {@link ImsConfig}. * * The inner class {@link ImsConfigStub} implements methods of IImsConfig AIDL interface. * The IImsConfig AIDL interface is called by ImsConfig, which may exist in many other processes. @@ -56,138 +48,6 @@ public class ImsConfigImplBase { static final private String TAG = "ImsConfigImplBase"; - ImsConfigStub mImsConfigStub; - - public ImsConfigImplBase(Context context) { - mImsConfigStub = new ImsConfigStub(this, context); - } - - /** - * Gets the value for ims service/capabilities parameters from the provisioned - * value storage. Synchronous blocking call. - * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @return value in Integer format. - */ - public int getProvisionedValue(int item) throws RemoteException { - return -1; - } - - /** - * Gets the value for ims service/capabilities parameters from the provisioned - * value storage. Synchronous blocking call. - * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @return value in String format. - */ - public String getProvisionedStringValue(int item) throws RemoteException { - return null; - } - - /** - * Sets the value for IMS service/capabilities parameters by the operator device - * management entity. It sets the config item value in the provisioned storage - * from which the master value is derived. Synchronous blocking call. - * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in Integer format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. - */ - public int setProvisionedValue(int item, int value) throws RemoteException { - return ImsConfig.OperationStatusConstants.FAILED; - } - - /** - * Sets the value for IMS service/capabilities parameters by the operator device - * management entity. It sets the config item value in the provisioned storage - * from which the master value is derived. Synchronous blocking call. - * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in String format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. - */ - public int setProvisionedStringValue(int item, String value) throws RemoteException { - return ImsConfig.OperationStatusConstants.FAILED; - } - - /** - * Gets the value of the specified IMS feature item for specified network type. - * This operation gets the feature config value from the master storage (i.e. final - * value). Asynchronous non-blocking call. - * - * @param feature as defined in com.android.ims.ImsConfig#FeatureConstants. - * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. - * @param listener feature value returned asynchronously through listener. - */ - public void getFeatureValue(int feature, int network, ImsConfigListener listener) - throws RemoteException { - } - - /** - * Sets the value for IMS feature item for specified network type. - * This operation stores the user setting in setting db from which master db - * is derived. - * - * @param feature as defined in com.android.ims.ImsConfig#FeatureConstants. - * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. - * @param value as defined in com.android.ims.ImsConfig#FeatureValueConstants. - * @param listener, provided if caller needs to be notified for set result. - */ - public void setFeatureValue(int feature, int network, int value, ImsConfigListener listener) - throws RemoteException { - } - - /** - * Gets the value for IMS VoLTE provisioned. - * This should be the same as the operator provisioned value if applies. - */ - public boolean getVolteProvisioned() throws RemoteException { - return false; - } - - /** - * Gets the value for IMS feature item video quality. - * - * @param listener Video quality value returned asynchronously through listener. - */ - public void getVideoQuality(ImsConfigListener listener) throws RemoteException { - } - - /** - * Sets the value for IMS feature item video quality. - * - * @param quality, defines the value of video quality. - * @param listener, provided if caller needs to be notified for set result. - */ - public void setVideoQuality(int quality, ImsConfigListener listener) throws RemoteException { - } - - public IImsConfig getIImsConfig() { return mImsConfigStub; } - - /** - * Updates provisioning value and notifies the framework of the change. - * Doesn't call #setProvisionedValue and assumes the result succeeded. - * This should only be used by modem when they implicitly changed provisioned values. - * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in Integer format. - */ - public final void notifyProvisionedValueChanged(int item, int value) { - mImsConfigStub.updateCachedValue(item, value, true); - } - - /** - * Updates provisioning value and notifies the framework of the change. - * Doesn't call #setProvisionedValue and assumes the result succeeded. - * This should only be used by modem when they implicitly changed provisioned values. - * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in String format. - */ - public final void notifyProvisionedValueChanged(int item, String value) { - mImsConfigStub.updateCachedValue(item, value, true); - } - /** * Implements the IImsConfig AIDL interface, which is called by potentially many processes * in order to get/set configuration parameters. @@ -208,18 +68,26 @@ public class ImsConfigImplBase { */ @VisibleForTesting static public class ImsConfigStub extends IImsConfig.Stub { - Context mContext; WeakReference mImsConfigImplBaseWeakReference; private HashMap mProvisionedIntValue = new HashMap<>(); private HashMap mProvisionedStringValue = new HashMap<>(); @VisibleForTesting - public ImsConfigStub(ImsConfigImplBase imsConfigImplBase, Context context) { - mContext = context; + public ImsConfigStub(ImsConfigImplBase imsConfigImplBase) { mImsConfigImplBaseWeakReference = new WeakReference(imsConfigImplBase); } + @Override + public void addImsConfigCallback(IImsConfigCallback c) throws RemoteException { + getImsConfigImpl().addImsConfigCallback(c); + } + + @Override + public void removeImsConfigCallback(IImsConfigCallback c) throws RemoteException { + getImsConfigImpl().removeImsConfigCallback(c); + } + /** * Gets the value for ims service/capabilities parameters. It first checks its local cache, * if missed, it will call ImsConfigImplBase.getProvisionedValue. @@ -229,11 +97,11 @@ public class ImsConfigImplBase { * @return value in Integer format. */ @Override - public synchronized int getProvisionedValue(int item) throws RemoteException { + public synchronized int getConfigInt(int item) throws RemoteException { if (mProvisionedIntValue.containsKey(item)) { return mProvisionedIntValue.get(item); } else { - int retVal = getImsConfigImpl().getProvisionedValue(item); + int retVal = getImsConfigImpl().getConfigInt(item); if (retVal != ImsConfig.OperationStatusConstants.UNKNOWN) { updateCachedValue(item, retVal, false); } @@ -250,11 +118,11 @@ public class ImsConfigImplBase { * @return value in String format. */ @Override - public synchronized String getProvisionedStringValue(int item) throws RemoteException { + public synchronized String getConfigString(int item) throws RemoteException { if (mProvisionedIntValue.containsKey(item)) { return mProvisionedStringValue.get(item); } else { - String retVal = getImsConfigImpl().getProvisionedStringValue(item); + String retVal = getImsConfigImpl().getConfigString(item); if (retVal != null) { updateCachedValue(item, retVal, false); } @@ -273,9 +141,9 @@ public class ImsConfigImplBase { * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. */ @Override - public synchronized int setProvisionedValue(int item, int value) throws RemoteException { + public synchronized int setConfigInt(int item, int value) throws RemoteException { mProvisionedIntValue.remove(item); - int retVal = getImsConfigImpl().setProvisionedValue(item, value); + int retVal = getImsConfigImpl().setConfig(item, value); if (retVal == ImsConfig.OperationStatusConstants.SUCCESS) { updateCachedValue(item, retVal, true); } else { @@ -297,10 +165,10 @@ public class ImsConfigImplBase { * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. */ @Override - public synchronized int setProvisionedStringValue(int item, String value) + public synchronized int setConfigString(int item, String value) throws RemoteException { mProvisionedStringValue.remove(item); - int retVal = getImsConfigImpl().setProvisionedStringValue(item, value); + int retVal = getImsConfigImpl().setConfig(item, value); if (retVal == ImsConfig.OperationStatusConstants.SUCCESS) { updateCachedValue(item, retVal, true); } @@ -308,84 +176,211 @@ public class ImsConfigImplBase { return retVal; } - /** - * Wrapper function to call ImsConfigImplBase.getFeatureValue. - */ - @Override - public void getFeatureValue(int feature, int network, ImsConfigListener listener) - throws RemoteException { - getImsConfigImpl().getFeatureValue(feature, network, listener); + private ImsConfigImplBase getImsConfigImpl() throws RemoteException { + ImsConfigImplBase ref = mImsConfigImplBaseWeakReference.get(); + if (ref == null) { + throw new RemoteException("Fail to get ImsConfigImpl"); + } else { + return ref; + } } - /** - * Wrapper function to call ImsConfigImplBase.setFeatureValue. - */ + private void notifyImsConfigChanged(int item, int value) throws RemoteException { + getImsConfigImpl().notifyConfigChanged(item, value); + } + + private void notifyImsConfigChanged(int item, String value) throws RemoteException { + getImsConfigImpl().notifyConfigChanged(item, value); + } + + protected synchronized void updateCachedValue(int item, int value, boolean notifyChange) + throws RemoteException { + mProvisionedIntValue.put(item, value); + if (notifyChange) { + notifyImsConfigChanged(item, value); + } + } + + protected synchronized void updateCachedValue(int item, String value, + boolean notifyChange) throws RemoteException { + mProvisionedStringValue.put(item, value); + if (notifyChange) { + notifyImsConfigChanged(item, value); + } + } + } + + /** + * Callback that the framework uses for receiving Configuration change updates. + * {@hide} + */ + public static class Callback extends IImsConfigCallback.Stub { + @Override - public void setFeatureValue(int feature, int network, int value, ImsConfigListener listener) - throws RemoteException { - getImsConfigImpl().setFeatureValue(feature, network, value, listener); + public final void onIntConfigChanged(int item, int value) throws RemoteException { + onConfigChanged(item, value); } - /** - * Wrapper function to call ImsConfigImplBase.getVolteProvisioned. - */ @Override - public boolean getVolteProvisioned() throws RemoteException { - return getImsConfigImpl().getVolteProvisioned(); + public final void onStringConfigChanged(int item, String value) throws RemoteException { + onConfigChanged(item, value); } /** - * Wrapper function to call ImsConfigImplBase.getVideoQuality. + * Called when the IMS configuration has changed. + * @param item the IMS configuration key constant, as defined in ImsConfig. + * @param value the new integer value of the IMS configuration constant. */ - @Override - public void getVideoQuality(ImsConfigListener listener) throws RemoteException { - getImsConfigImpl().getVideoQuality(listener); + public void onConfigChanged(int item, int value) { + // Base Implementation } /** - * Wrapper function to call ImsConfigImplBase.setVideoQuality. + * Called when the IMS configuration has changed. + * @param item the IMS configuration key constant, as defined in ImsConfig. + * @param value the new String value of the IMS configuration constant. */ - @Override - public void setVideoQuality(int quality, ImsConfigListener listener) - throws RemoteException { - getImsConfigImpl().setVideoQuality(quality, listener); + public void onConfigChanged(int item, String value) { + // Base Implementation } + } - private ImsConfigImplBase getImsConfigImpl() throws RemoteException { - ImsConfigImplBase ref = mImsConfigImplBaseWeakReference.get(); - if (ref == null) { - throw new RemoteException("Fail to get ImsConfigImpl"); - } else { - return ref; - } - } + private final RemoteCallbackList mCallbacks = new RemoteCallbackList<>(); + ImsConfigStub mImsConfigStub; - private void sendImsConfigChangedIntent(int item, int value) { - sendImsConfigChangedIntent(item, Integer.toString(value)); - } + public ImsConfigImplBase(Context context) { + mImsConfigStub = new ImsConfigStub(this); + } + public ImsConfigImplBase() { + mImsConfigStub = new ImsConfigStub(this); + } - private void sendImsConfigChangedIntent(int item, String value) { - Intent configChangedIntent = new Intent(ImsConfig.ACTION_IMS_CONFIG_CHANGED); - configChangedIntent.putExtra(ImsConfig.EXTRA_CHANGED_ITEM, item); - configChangedIntent.putExtra(ImsConfig.EXTRA_NEW_VALUE, value); - if (mContext != null) { - mContext.sendBroadcast(configChangedIntent); - } + /** + * Adds a {@link Callback} to the list of callbacks notified when a value in the configuration + * changes. + * @param c callback to add. + */ + private void addImsConfigCallback(IImsConfigCallback c) { + mCallbacks.register(c); + } + /** + * Removes a {@link Callback} to the list of callbacks notified when a value in the + * configuration changes. + * + * @param c callback to remove. + */ + private void removeImsConfigCallback(IImsConfigCallback c) { + mCallbacks.unregister(c); + } + + void notifyConfigChanged(int item, int value) { + // can be null in testing + if (mCallbacks == null) { + return; } + mCallbacks.broadcast(c -> { + try { + c.onIntConfigChanged(item, value); + } catch (RemoteException e) { + Log.w(TAG, "notifyConfigChanged(int): dead binder in notify, skipping."); + } + }); + } - protected synchronized void updateCachedValue(int item, int value, boolean notifyChange) { - mProvisionedIntValue.put(item, value); - if (notifyChange) { - sendImsConfigChangedIntent(item, value); + private void notifyConfigChanged(int item, String value) { + // can be null in testing + if (mCallbacks == null) { + return; + } + mCallbacks.broadcast(c -> { + try { + c.onStringConfigChanged(item, value); + } catch (RemoteException e) { + Log.w(TAG, "notifyConfigChanged(string): dead binder in notify, skipping."); } + }); + } + + public IImsConfig getIImsConfig() { return mImsConfigStub; } + + /** + * Updates provisioning value and notifies the framework of the change. + * Doesn't call #setProvisionedValue and assumes the result succeeded. + * This should only be used by modem when they implicitly changed provisioned values. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in Integer format. + */ + public final void notifyProvisionedValueChanged(int item, int value) { + try { + mImsConfigStub.updateCachedValue(item, value, true); + } catch (RemoteException e) { + Log.w(TAG, "notifyProvisionedValueChanged(int): Framework connection is dead."); } + } - protected synchronized void updateCachedValue( - int item, String value, boolean notifyChange) { - mProvisionedStringValue.put(item, value); - if (notifyChange) { - sendImsConfigChangedIntent(item, value); - } + /** + * Updates provisioning value and notifies the framework of the change. + * Doesn't call #setProvisionedValue and assumes the result succeeded. + * This should only be used by modem when they implicitly changed provisioned values. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in String format. + */ + public final void notifyProvisionedValueChanged(int item, String value) { + try { + mImsConfigStub.updateCachedValue(item, value, true); + } catch (RemoteException e) { + Log.w(TAG, "notifyProvisionedValueChanged(string): Framework connection is dead."); } } + + /** + * Sets the value for IMS service/capabilities parameters by the operator device + * management entity. It sets the config item value in the provisioned storage + * from which the master value is derived. + * + * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in Integer format. + * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + */ + public int setConfig(int item, int value) { + // Base Implementation - To be overridden. + return ImsConfig.OperationStatusConstants.FAILED; + } + + /** + * Sets the value for IMS service/capabilities parameters by the operator device + * management entity. It sets the config item value in the provisioned storage + * from which the master value is derived. + * + * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in String format. + * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + */ + public int setConfig(int item, String value) { + return ImsConfig.OperationStatusConstants.FAILED; + } + + /** + * Gets the value for ims service/capabilities parameters from the provisioned + * value storage. + * + * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. + * @return value in Integer format. + */ + public int getConfigInt(int item) { + return ImsConfig.OperationStatusConstants.FAILED; + } + + /** + * Gets the value for ims service/capabilities parameters from the provisioned + * value storage. + * + * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. + * @return value in String format. + */ + public String getConfigString(int item) { + return null; + } } diff --git a/telephony/java/android/telephony/ims/internal/stub/ImsFeatureConfiguration.aidl b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.aidl similarity index 93% rename from telephony/java/android/telephony/ims/internal/stub/ImsFeatureConfiguration.aidl rename to telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.aidl index e890cf8756f..e2ae0e8f677 100644 --- a/telephony/java/android/telephony/ims/internal/stub/ImsFeatureConfiguration.aidl +++ b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.aidl @@ -14,6 +14,6 @@ * limitations under the License */ -package android.telephony.ims.internal.stub; +package android.telephony.ims.stub; parcelable ImsFeatureConfiguration; diff --git a/telephony/java/android/telephony/ims/internal/stub/ImsFeatureConfiguration.java b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java similarity index 94% rename from telephony/java/android/telephony/ims/internal/stub/ImsFeatureConfiguration.java rename to telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java index 244c9578f6b..c8989e059ee 100644 --- a/telephony/java/android/telephony/ims/internal/stub/ImsFeatureConfiguration.java +++ b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open 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,14 +14,13 @@ * limitations under the License */ -package android.telephony.ims.internal.stub; +package android.telephony.ims.stub; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.ims.internal.feature.ImsFeature; +import android.telephony.ims.feature.ImsFeature; import android.util.ArraySet; -import java.util.Arrays; import java.util.Set; /** @@ -135,7 +134,8 @@ public class ImsFeatureConfiguration implements Parcelable { if (this == o) return true; if (!(o instanceof ImsFeatureConfiguration)) return false; - ImsFeatureConfiguration that = (ImsFeatureConfiguration) o; + ImsFeatureConfiguration + that = (ImsFeatureConfiguration) o; return mFeatures.equals(that.mFeatures); } diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java index 42af08365f6..dfb9b63f7b1 100644 --- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java @@ -18,14 +18,13 @@ package android.telephony.ims.stub; import android.annotation.IntDef; import android.net.Uri; -import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.telephony.ims.aidl.IImsRegistration; +import android.telephony.ims.aidl.IImsRegistrationCallback; import android.util.Log; import com.android.ims.ImsReasonInfo; -import com.android.ims.internal.IImsRegistration; -import com.android.ims.internal.IImsRegistrationCallback; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; diff --git a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java similarity index 98% rename from telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java rename to telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java index 89acc80a9d3..7fe1c8cd005 100644 --- a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java @@ -14,17 +14,16 @@ * limitations under the License */ -package android.telephony.ims.internal.stub; +package android.telephony.ims.stub; import android.annotation.IntDef; import android.annotation.SystemApi; import android.os.RemoteException; import android.telephony.SmsManager; import android.telephony.SmsMessage; +import android.telephony.ims.aidl.IImsSmsListener; import android.util.Log; -import com.android.ims.internal.IImsSmsListener; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -37,7 +36,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ @SystemApi -public class SmsImplBase { +public class ImsSmsImplBase { private static final String LOG_TAG = "SmsImplBase"; /** @hide */ diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index cd0c4b115c0..e670341ffe7 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -19,8 +19,8 @@ package com.android.ims; import android.content.Context; import android.os.RemoteException; import android.telephony.Rlog; - -import com.android.ims.internal.IImsConfig; +import android.telephony.ims.aidl.IImsConfig; +import android.telephony.ims.stub.ImsConfigImplBase; /** * Provides APIs to get/set the IMS service feature/capability/parameters. @@ -46,7 +46,7 @@ public class ImsConfig { /** * Broadcast action: the configuration was changed - * + * @deprecated Use {@link ImsConfig#addConfigCallback(ImsConfigImplBase.Callback)} instead. * @hide */ public static final String ACTION_IMS_CONFIG_CHANGED = @@ -70,6 +70,8 @@ public class ImsConfig { /** * Defines IMS service/capability feature constants. + * @deprecated Use + * {@link android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability} instead. */ public static class FeatureConstants { public static final int FEATURE_TYPE_UNKNOWN = -1; @@ -539,164 +541,166 @@ public class ImsConfig { } public ImsConfig(IImsConfig iconfig, Context context) { - if (DBG) Rlog.d(TAG, "ImsConfig creates"); + if (DBG) Rlog.d(TAG, "ImsConfig created"); miConfig = iconfig; mContext = context; } /** - * Gets the provisioned value for IMS service/capabilities parameters used by IMS stack. - * This function should not be called from the mainthread as it could block the - * mainthread. + * @deprecated see {@link #getInt(int)} instead. + */ + public int getProvisionedValue(int item) throws ImsException { + return getConfigInt(item); + } + + /** + * Gets the configuration value for IMS service/capabilities parameters used by IMS stack. * * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. * @return the value in Integer format. - * - * @throws ImsException if calling the IMS service results in an error. + * @throws ImsException if the ImsService is unavailable. */ - public int getProvisionedValue(int item) throws ImsException { + public int getConfigInt(int item) throws ImsException { int ret = 0; try { - ret = miConfig.getProvisionedValue(item); + ret = miConfig.getConfigInt(item); } catch (RemoteException e) { - throw new ImsException("getValue()", e, + throw new ImsException("getInt()", e, ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); } - if (DBG) Rlog.d(TAG, "getProvisionedValue(): item = " + item + ", ret =" + ret); + if (DBG) Rlog.d(TAG, "getInt(): item = " + item + ", ret =" + ret); return ret; } /** - * Gets the provisioned value for IMS service/capabilities parameters used by IMS stack. - * This function should not be called from the mainthread as it could block the - * mainthread. + * @deprecated see {@link #getConfigString(int)} instead + */ + public String getProvisionedStringValue(int item) throws ImsException { + return getConfigString(item); + } + + /** + * Gets the configuration value for IMS service/capabilities parameters used by IMS stack. * * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. * @return value in String format. * - * @throws ImsException if calling the IMS service results in an error. + * @throws ImsException if the ImsService is unavailable. */ - public String getProvisionedStringValue(int item) throws ImsException { + public String getConfigString(int item) throws ImsException { String ret = "Unknown"; try { - ret = miConfig.getProvisionedStringValue(item); + ret = miConfig.getConfigString(item); } catch (RemoteException e) { - throw new ImsException("getProvisionedStringValue()", e, + throw new ImsException("getConfigString()", e, ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); } - if (DBG) Rlog.d(TAG, "getProvisionedStringValue(): item = " + item + ", ret =" + ret); + if (DBG) Rlog.d(TAG, "getConfigString(): item = " + item + ", ret =" + ret); return ret; } /** - * Sets the value for IMS service/capabilities parameters by - * the operator device management entity. - * This function should not be called from main thread as it could block - * mainthread. + * @deprecated see {@link #setConfig(int, int)} instead. + */ + public int setProvisionedValue(int item, int value) throws ImsException { + return setConfig(item, value); + } + + /** + * @deprecated see {@link #setConfig(int, String)} instead. + */ + public int setProvisionedStringValue(int item, String value) throws ImsException { + return setConfig(item, value); + } + + /** + * Sets the value for ImsService configuration item. * * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. * @param value in Integer format. * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants * - * @throws ImsException if calling the IMS service results in an error. + * @throws ImsException if the ImsService is unavailable. */ - public int setProvisionedValue(int item, int value) - throws ImsException { + public int setConfig(int item, int value) throws ImsException { int ret = OperationStatusConstants.UNKNOWN; if (DBG) { - Rlog.d(TAG, "setProvisionedValue(): item = " + item + + Rlog.d(TAG, "setConfig(): item = " + item + "value = " + value); } try { - ret = miConfig.setProvisionedValue(item, value); + ret = miConfig.setConfigInt(item, value); } catch (RemoteException e) { - throw new ImsException("setProvisionedValue()", e, + throw new ImsException("setConfig()", e, ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); } if (DBG) { - Rlog.d(TAG, "setProvisionedValue(): item = " + item + + Rlog.d(TAG, "setConfig(): item = " + item + " value = " + value + " ret = " + ret); } return ret; + } /** - * Sets the value for IMS service/capabilities parameters by - * the operator device management entity. - * This function should not be called from main thread as it could block - * mainthread. + * Sets the value for ImsService configuration item. * * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in String format. + * @param value in Integer format. * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants * - * @throws ImsException if calling the IMS service results in an error. + * @throws ImsException if the ImsService is unavailable. */ - public int setProvisionedStringValue(int item, String value) - throws ImsException { + public int setConfig(int item, String value) throws ImsException { int ret = OperationStatusConstants.UNKNOWN; + if (DBG) { + Rlog.d(TAG, "setConfig(): item = " + item + + "value = " + value); + } try { - ret = miConfig.setProvisionedStringValue(item, value); + ret = miConfig.setConfigString(item, value); } catch (RemoteException e) { - throw new ImsException("setProvisionedStringValue()", e, + throw new ImsException("setConfig()", e, ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); } if (DBG) { - Rlog.d(TAG, "setProvisionedStringValue(): item = " + item + - ", value =" + value); + Rlog.d(TAG, "setConfig(): item = " + item + + " value = " + value + " ret = " + ret); } return ret; } /** - * Gets the value for IMS feature item for specified network type. + * Adds a {@link ImsConfigImplBase.Callback} to the ImsService to notify when a Configuration + * item has changed. * - * @param feature, defined as in FeatureConstants. - * @param network, defined as in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. - * @param listener, provided to be notified for the feature on/off status. - * @return void - * - * @throws ImsException if calling the IMS service results in an error. + * Make sure to call {@link #removeConfigCallback(ImsConfigImplBase.Callback)} when finished + * using this callback. */ - public void getFeatureValue(int feature, int network, - ImsConfigListener listener) throws ImsException { - if (DBG) { - Rlog.d(TAG, "getFeatureValue: feature = " + feature + ", network =" + network + - ", listener =" + listener); - } + public void addConfigCallback(ImsConfigImplBase.Callback callback) throws ImsException { + if (DBG) Rlog.d(TAG, "addConfigCallback: " + callback); try { - miConfig.getFeatureValue(feature, network, listener); - } catch (RemoteException e) { - throw new ImsException("getFeatureValue()", e, + miConfig.addImsConfigCallback(callback); + } catch (RemoteException e) { + throw new ImsException("addConfigCallback()", e, ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); } } /** - * Sets the value for IMS feature item for specified network type. - * - * @param feature, as defined in FeatureConstants. - * @param network, as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. - * @param value, as defined in FeatureValueConstants. - * @param listener, provided if caller needs to be notified for set result. - * @return void - * - * @throws ImsException if calling the IMS service results in an error. + * Removes a {@link ImsConfigImplBase.Callback} from the ImsService that was previously added + * by {@link #addConfigCallback(ImsConfigImplBase.Callback)}. */ - public void setFeatureValue(int feature, int network, int value, - ImsConfigListener listener) throws ImsException { - if (DBG) { - Rlog.d(TAG, "setFeatureValue: feature = " + feature + ", network =" + network + - ", value =" + value + ", listener =" + listener); - } + public void removeConfigCallback(ImsConfigImplBase.Callback callback) throws ImsException { + if (DBG) Rlog.d(TAG, "removeConfigCallback: " + callback); try { - miConfig.setFeatureValue(feature, network, value, listener); - } catch (RemoteException e) { - throw new ImsException("setFeatureValue()", e, + miConfig.removeImsConfigCallback(callback); + } catch (RemoteException e) { + throw new ImsException("removeConfigCallback()", e, ImsReasonInfo.CODE_LOCAL_SERVICE_UNAVAILABLE); } } diff --git a/telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl b/telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl deleted file mode 100644 index 41b1042e9a8..00000000000 --- a/telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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.ims.internal; - -/** -* Interface from ImsFeature in the ImsService to ImsServiceController. - * {@hide} - */ -oneway interface IImsFeatureStatusCallback { - void notifyImsFeatureStatus(int featureStatus); -} \ No newline at end of file diff --git a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl index 290a6824025..d43077bf10b 100644 --- a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl +++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl @@ -24,7 +24,6 @@ import com.android.ims.internal.IImsConfig; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsRegistrationListener; -import com.android.ims.internal.IImsSmsListener; import com.android.ims.internal.IImsUt; import android.os.Message; @@ -52,12 +51,4 @@ interface IImsMMTelFeature { IImsEcbm getEcbmInterface(); void setUiTTYMode(int uiTtyMode, in Message onComplete); IImsMultiEndpoint getMultiEndpointInterface(); - // SMS APIs - void setSmsListener(IImsSmsListener l); - oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry, - in byte[] pdu); - oneway void acknowledgeSms(int token, int messageRef, int result); - oneway void acknowledgeSmsReport(int token, int messageRef, int result); - String getSmsFormat(); - oneway void onSmsReady(); } diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl index 7ac25ac13fb..857089fac33 100644 --- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl +++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl @@ -18,7 +18,6 @@ package com.android.ims.internal; import com.android.ims.internal.IImsFeatureStatusCallback; import com.android.ims.internal.IImsMMTelFeature; -import com.android.ims.internal.IImsRegistration; import com.android.ims.internal.IImsRcsFeature; /** @@ -30,5 +29,4 @@ interface IImsServiceController { IImsMMTelFeature createMMTelFeature(int slotId, in IImsFeatureStatusCallback c); IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c); void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c); - IImsRegistration getRegistration(int slotId); } diff --git a/telephony/java/com/android/internal/telephony/ExponentialBackoff.java b/telephony/java/com/android/internal/telephony/ExponentialBackoff.java index 80958c077d6..f323a0cc620 100644 --- a/telephony/java/com/android/internal/telephony/ExponentialBackoff.java +++ b/telephony/java/com/android/internal/telephony/ExponentialBackoff.java @@ -20,6 +20,8 @@ import android.annotation.NonNull; import android.os.Handler; import android.os.Looper; +import com.android.internal.annotations.VisibleForTesting; + /** The implementation of exponential backoff with jitter applied. */ public class ExponentialBackoff { private int mRetryCounter; @@ -27,8 +29,31 @@ public class ExponentialBackoff { private long mMaximumDelayMs; private long mCurrentDelayMs; private int mMultiplier; - private Runnable mRunnable; - private Handler mHandler; + private final Runnable mRunnable; + private final Handler mHandler; + + /** + * Implementation of Handler methods, Adapter for testing (can't spy on final methods). + */ + private HandlerAdapter mHandlerAdapter = new HandlerAdapter() { + @Override + public boolean postDelayed(Runnable runnable, long delayMillis) { + return mHandler.postDelayed(runnable, delayMillis); + } + + @Override + public void removeCallbacks(Runnable runnable) { + mHandler.removeCallbacks(runnable); + } + }; + + /** + * Need to spy final methods for testing. + */ + public interface HandlerAdapter { + boolean postDelayed(Runnable runnable, long delayMillis); + void removeCallbacks(Runnable runnable); + } public ExponentialBackoff( long initialDelayMs, @@ -57,14 +82,14 @@ public class ExponentialBackoff { public void start() { mRetryCounter = 0; mCurrentDelayMs = mStartDelayMs; - mHandler.removeCallbacks(mRunnable); - mHandler.postDelayed(mRunnable, mCurrentDelayMs); + mHandlerAdapter.removeCallbacks(mRunnable); + mHandlerAdapter.postDelayed(mRunnable, mCurrentDelayMs); } /** Stops the backoff, all pending messages will be removed from the message queue. */ public void stop() { mRetryCounter = 0; - mHandler.removeCallbacks(mRunnable); + mHandlerAdapter.removeCallbacks(mRunnable); } /** Should call when the retry action has failed and we want to retry after a longer delay. */ @@ -73,12 +98,17 @@ public class ExponentialBackoff { long temp = Math.min( mMaximumDelayMs, (long) (mStartDelayMs * Math.pow(mMultiplier, mRetryCounter))); mCurrentDelayMs = (long) (((1 + Math.random()) / 2) * temp); - mHandler.removeCallbacks(mRunnable); - mHandler.postDelayed(mRunnable, mCurrentDelayMs); + mHandlerAdapter.removeCallbacks(mRunnable); + mHandlerAdapter.postDelayed(mRunnable, mCurrentDelayMs); } /** Returns the delay for the most recently posted message. */ public long getCurrentDelay() { return mCurrentDelayMs; } + + @VisibleForTesting + public void setHandlerAdapter(HandlerAdapter a) { + mHandlerAdapter = a; + } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index dfb3c344cb9..24c497c7d19 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -38,9 +38,10 @@ import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyHistogram; import android.telephony.VisualVoicemailSmsFilterSettings; -import com.android.ims.internal.IImsMMTelFeature; -import com.android.ims.internal.IImsRcsFeature; -import com.android.ims.internal.IImsRegistration; +import android.telephony.ims.aidl.IImsConfig; +import android.telephony.ims.aidl.IImsMmTelFeature; +import android.telephony.ims.aidl.IImsRcsFeature; +import android.telephony.ims.aidl.IImsRegistration; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.OperatorInfo; @@ -787,20 +788,21 @@ interface ITelephony { int getTetherApnRequired(); /** - * Get IImsMMTelFeature binder from ImsResolver that corresponds to the subId and MMTel feature - * as well as registering the MMTelFeature for callbacks using the IImsServiceFeatureCallback - * interface. - */ - IImsMMTelFeature getMMTelFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback); + * Enables framework IMS and triggers IMS Registration. + */ + void enableIms(int slotId); /** - * Get IImsMMTelFeature binder from ImsResolver that corresponds to the subId and MMTel feature - * as well as registering the MMTelFeature for callbacks using the IImsServiceFeatureCallback + * Disables framework IMS and triggers IMS deregistration. + */ + void disableIms(int slotId); + + /** + * Get IImsMmTelFeature binder from ImsResolver that corresponds to the subId and MMTel feature + * as well as registering the MmTelFeature for callbacks using the IImsServiceFeatureCallback * interface. - * Used for emergency calling only. */ - IImsMMTelFeature getEmergencyMMTelFeatureAndListen(int slotId, - in IImsServiceFeatureCallback callback); + IImsMmTelFeature getMmTelFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback); /** * Get IImsRcsFeature binder from ImsResolver that corresponds to the subId and RCS feature @@ -814,6 +816,11 @@ interface ITelephony { */ IImsRegistration getImsRegistration(int slotId, int feature); + /** + * Returns the IImsConfig associated with the slot and feature specified. + */ + IImsConfig getImsConfig(int slotId, int feature); + /** * Set the network selection mode to automatic. * -- GitLab From f8c3cd8b3530a27f7534c32bac84c464ad5f66ae Mon Sep 17 00:00:00 2001 From: Brad Ebinger Date: Mon, 22 Jan 2018 13:51:52 -0800 Subject: [PATCH 016/416] Make ImsService API @SystemApi Marks the ImsService API as @SystemAPI. Bug: 63987047 Test: Build, Telephony unit tests Change-Id: I10f8a09950be87cb166b718d1dcc2954fba872cb --- api/system-current.txt | 680 +++++++++++++++++- .../telephony/CarrierConfigManager.java | 4 +- .../telephony}/ims/ImsCallForwardInfo.aidl | 2 +- .../telephony}/ims/ImsCallForwardInfo.java | 42 +- .../telephony}/ims/ImsCallProfile.aidl | 2 +- .../telephony}/ims/ImsCallProfile.java | 118 ++- .../telephony/ims}/ImsCallSession.java | 47 +- .../telephony/ims/ImsCallSessionListener.java | 537 +++++++++----- .../telephony}/ims/ImsConferenceState.aidl | 2 +- .../telephony}/ims/ImsConferenceState.java | 15 +- .../telephony}/ims/ImsExternalCallState.aidl | 2 +- .../telephony}/ims/ImsExternalCallState.java | 13 +- .../telephony}/ims/ImsReasonInfo.aidl | 2 +- .../telephony}/ims/ImsReasonInfo.java | 35 +- .../android/telephony/ims/ImsService.java | 90 ++- .../telephony}/ims/ImsSsData.aidl | 2 +- .../telephony}/ims/ImsSsData.java | 38 +- .../telephony}/ims/ImsSsInfo.aidl | 2 +- .../telephony}/ims/ImsSsInfo.java | 22 +- .../telephony}/ims/ImsStreamMediaProfile.aidl | 2 +- .../telephony}/ims/ImsStreamMediaProfile.java | 38 +- .../ims/ImsSuppServiceNotification.aidl | 2 +- .../ims/ImsSuppServiceNotification.java | 42 +- .../android/telephony/ims/ImsUtListener.java | 108 +++ .../telephony/ims}/ImsVideoCallProvider.java | 9 +- .../ims/aidl/IImsCallSessionListener.aidl | 10 +- .../telephony/ims/aidl/IImsMmTelFeature.aidl | 3 +- .../ims/aidl/IImsRegistrationCallback.aidl | 2 +- .../ims/compat/feature/MMTelFeature.java | 4 +- .../compat/stub/ImsCallSessionImplBase.java | 12 +- .../stub/ImsUtListenerImplBase.java | 12 +- .../ims/feature/CapabilityChangeRequest.java | 45 +- .../telephony/ims/feature/ImsFeature.java | 143 +++- .../telephony/ims/feature/MmTelFeature.java | 75 +- .../telephony/ims/feature/RcsFeature.java | 11 +- .../ims/stub/ImsCallSessionImplBase.java | 364 ++++++++-- .../telephony/ims/stub/ImsConfigImplBase.java | 110 +-- .../telephony/ims/stub/ImsEcbmImplBase.java | 61 +- .../ims/stub/ImsFeatureConfiguration.java | 28 +- .../ims/stub/ImsMultiEndpointImplBase.java | 51 +- .../ims/stub/ImsRegistrationImplBase.java | 37 +- .../telephony/ims/stub/ImsSmsImplBase.java | 6 +- .../telephony/ims/stub/ImsUtImplBase.java | 253 +++++-- telephony/java/com/android/ims/ImsConfig.java | 1 + .../java/com/android/ims/ImsException.java | 2 + .../java/com/android/ims/ImsUtInterface.java | 2 + .../android/ims/internal/IImsCallSession.aidl | 4 +- .../ims/internal/IImsCallSessionListener.aidl | 10 +- .../IImsExternalCallStateListener.aidl | 2 +- .../internal/IImsFeatureStatusCallback.aidl | 25 + .../ims/internal/IImsMMTelFeature.aidl | 2 +- .../internal/IImsRegistrationListener.aidl | 2 +- .../com/android/ims/internal/IImsService.aidl | 2 +- .../android/ims/internal/IImsUtListener.aidl | 8 +- 54 files changed, 2524 insertions(+), 619 deletions(-) rename telephony/java/{com/android => android/telephony}/ims/ImsCallForwardInfo.aidl (95%) rename telephony/java/{com/android => android/telephony}/ims/ImsCallForwardInfo.java (77%) rename telephony/java/{com/android => android/telephony}/ims/ImsCallProfile.aidl (95%) rename telephony/java/{com/android => android/telephony}/ims/ImsCallProfile.java (90%) rename telephony/java/{com/android/ims/internal => android/telephony/ims}/ImsCallSession.java (97%) rename telephony/java/{com/android => android/telephony}/ims/ImsConferenceState.aidl (95%) rename telephony/java/{com/android => android/telephony}/ims/ImsConferenceState.java (95%) rename telephony/java/{com/android => android/telephony}/ims/ImsExternalCallState.aidl (95%) rename telephony/java/{com/android => android/telephony}/ims/ImsExternalCallState.java (93%) rename telephony/java/{com/android => android/telephony}/ims/ImsReasonInfo.aidl (95%) rename telephony/java/{com/android => android/telephony}/ims/ImsReasonInfo.java (97%) rename telephony/java/{com/android => android/telephony}/ims/ImsSsData.aidl (95%) rename telephony/java/{com/android => android/telephony}/ims/ImsSsData.java (89%) rename telephony/java/{com/android => android/telephony}/ims/ImsSsInfo.aidl (95%) rename telephony/java/{com/android => android/telephony}/ims/ImsSsInfo.java (82%) rename telephony/java/{com/android => android/telephony}/ims/ImsStreamMediaProfile.aidl (95%) rename telephony/java/{com/android => android/telephony}/ims/ImsStreamMediaProfile.java (88%) rename telephony/java/{com/android => android/telephony}/ims/ImsSuppServiceNotification.aidl (95%) rename telephony/java/{com/android => android/telephony}/ims/ImsSuppServiceNotification.java (84%) create mode 100644 telephony/java/android/telephony/ims/ImsUtListener.java rename telephony/java/{com/android/ims/internal => android/telephony/ims}/ImsVideoCallProvider.java (97%) rename telephony/java/android/telephony/ims/{ => compat}/stub/ImsUtListenerImplBase.java (91%) create mode 100644 telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl diff --git a/api/system-current.txt b/api/system-current.txt index 0ad0dd46754..6fa5d8abce9 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5062,16 +5062,667 @@ package android.telephony.data { package android.telephony.ims { + public final class ImsCallForwardInfo implements android.os.Parcelable { + method public int describeContents(); + method public int getCondition(); + method public java.lang.String getNumber(); + method public int getServiceClass(); + method public int getStatus(); + method public int getTimeSeconds(); + method public int getToA(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public final class ImsCallProfile implements android.os.Parcelable { + method public int describeContents(); + method public java.lang.String getCallExtra(java.lang.String); + method public java.lang.String getCallExtra(java.lang.String, java.lang.String); + method public boolean getCallExtraBoolean(java.lang.String); + method public boolean getCallExtraBoolean(java.lang.String, boolean); + method public int getCallExtraInt(java.lang.String); + method public int getCallExtraInt(java.lang.String, int); + method public android.os.Bundle getCallExtras(); + method public int getCallType(); + method public static int getCallTypeFromVideoState(int); + method public android.telephony.ims.ImsStreamMediaProfile getMediaProfile(); + method public int getRestrictCause(); + method public int getServiceType(); + method public static int getVideoStateFromCallType(int); + method public static int getVideoStateFromImsCallProfile(android.telephony.ims.ImsCallProfile); + method public boolean isVideoCall(); + method public boolean isVideoPaused(); + method public static int presentationToOir(int); + method public void setCallExtra(java.lang.String, java.lang.String); + method public void setCallExtraBoolean(java.lang.String, boolean); + method public void setCallExtraInt(java.lang.String, int); + method public void updateCallExtras(android.telephony.ims.ImsCallProfile); + method public void updateCallType(android.telephony.ims.ImsCallProfile); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CALL_RESTRICT_CAUSE_DISABLED = 2; // 0x2 + field public static final int CALL_RESTRICT_CAUSE_HD = 3; // 0x3 + field public static final int CALL_RESTRICT_CAUSE_NONE = 0; // 0x0 + field public static final int CALL_RESTRICT_CAUSE_RAT = 1; // 0x1 + field public static final int CALL_TYPE_VIDEO_N_VOICE = 3; // 0x3 + field public static final int CALL_TYPE_VOICE = 2; // 0x2 + field public static final int CALL_TYPE_VOICE_N_VIDEO = 1; // 0x1 + field public static final int CALL_TYPE_VS = 8; // 0x8 + field public static final int CALL_TYPE_VS_RX = 10; // 0xa + field public static final int CALL_TYPE_VS_TX = 9; // 0x9 + field public static final int CALL_TYPE_VT = 4; // 0x4 + field public static final int CALL_TYPE_VT_NODIR = 7; // 0x7 + field public static final int CALL_TYPE_VT_RX = 6; // 0x6 + field public static final int CALL_TYPE_VT_TX = 5; // 0x5 + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int DIALSTRING_NORMAL = 0; // 0x0 + field public static final int DIALSTRING_SS_CONF = 1; // 0x1 + field public static final int DIALSTRING_USSD = 2; // 0x2 + field public static final java.lang.String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo"; + field public static final java.lang.String EXTRA_CALL_RAT_TYPE = "CallRadioTech"; + field public static final java.lang.String EXTRA_CHILD_NUMBER = "ChildNum"; + field public static final java.lang.String EXTRA_CNA = "cna"; + field public static final java.lang.String EXTRA_CNAP = "cnap"; + field public static final java.lang.String EXTRA_CODEC = "Codec"; + field public static final java.lang.String EXTRA_DIALSTRING = "dialstring"; + field public static final java.lang.String EXTRA_DISPLAY_TEXT = "DisplayText"; + field public static final java.lang.String EXTRA_IS_CALL_PULL = "CallPull"; + field public static final java.lang.String EXTRA_OI = "oi"; + field public static final java.lang.String EXTRA_OIR = "oir"; + field public static final java.lang.String EXTRA_REMOTE_URI = "remote_uri"; + field public static final java.lang.String EXTRA_USSD = "ussd"; + field public static final int OIR_DEFAULT = 0; // 0x0 + field public static final int OIR_PRESENTATION_NOT_RESTRICTED = 2; // 0x2 + field public static final int OIR_PRESENTATION_PAYPHONE = 4; // 0x4 + field public static final int OIR_PRESENTATION_RESTRICTED = 1; // 0x1 + field public static final int OIR_PRESENTATION_UNKNOWN = 3; // 0x3 + field public static final int SERVICE_TYPE_EMERGENCY = 2; // 0x2 + field public static final int SERVICE_TYPE_NONE = 0; // 0x0 + field public static final int SERVICE_TYPE_NORMAL = 1; // 0x1 + } + + public class ImsCallSessionListener { + method public void callSessionConferenceExtendFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionConferenceExtendReceived(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile); + method public void callSessionConferenceExtended(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile); + method public void callSessionConferenceStateUpdated(android.telephony.ims.ImsConferenceState); + method public void callSessionHandover(int, int, android.telephony.ims.ImsReasonInfo); + method public void callSessionHandoverFailed(int, int, android.telephony.ims.ImsReasonInfo); + method public void callSessionHeld(android.telephony.ims.ImsCallProfile); + method public void callSessionHoldFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionHoldReceived(android.telephony.ims.ImsCallProfile); + method public void callSessionInitiated(android.telephony.ims.ImsCallProfile); + method public void callSessionInitiatedFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionInviteParticipantsRequestDelivered(); + method public void callSessionInviteParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionMayHandover(int, int); + method public void callSessionMergeComplete(android.telephony.ims.stub.ImsCallSessionImplBase); + method public void callSessionMergeFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionMergeStarted(android.telephony.ims.stub.ImsCallSessionImplBase, android.telephony.ims.ImsCallProfile); + method public void callSessionMultipartyStateChanged(boolean); + method public void callSessionProgressing(android.telephony.ims.ImsStreamMediaProfile); + method public void callSessionRemoveParticipantsRequestDelivered(); + method public void callSessionRemoveParticipantsRequestFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionResumeFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionResumeReceived(android.telephony.ims.ImsCallProfile); + method public void callSessionResumed(android.telephony.ims.ImsCallProfile); + method public void callSessionRttMessageReceived(java.lang.String); + method public void callSessionRttModifyRequestReceived(android.telephony.ims.ImsCallProfile); + method public void callSessionRttModifyResponseReceived(int); + method public void callSessionSuppServiceReceived(android.telephony.ims.ImsSuppServiceNotification); + method public void callSessionTerminated(android.telephony.ims.ImsReasonInfo); + method public void callSessionTtyModeReceived(int); + method public void callSessionUpdateFailed(android.telephony.ims.ImsReasonInfo); + method public void callSessionUpdateReceived(android.telephony.ims.ImsCallProfile); + method public void callSessionUpdated(android.telephony.ims.ImsCallProfile); + method public void callSessionUssdMessageReceived(int, java.lang.String); + } + + public final class ImsConferenceState implements android.os.Parcelable { + method public int describeContents(); + method public static int getConnectionStateForStatus(java.lang.String); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final java.lang.String DISPLAY_TEXT = "display-text"; + field public static final java.lang.String ENDPOINT = "endpoint"; + field public static final java.lang.String SIP_STATUS_CODE = "sipstatuscode"; + field public static final java.lang.String STATUS = "status"; + field public static final java.lang.String STATUS_ALERTING = "alerting"; + field public static final java.lang.String STATUS_CONNECTED = "connected"; + field public static final java.lang.String STATUS_CONNECT_FAIL = "connect-fail"; + field public static final java.lang.String STATUS_DIALING_IN = "dialing-in"; + field public static final java.lang.String STATUS_DIALING_OUT = "dialing-out"; + field public static final java.lang.String STATUS_DISCONNECTED = "disconnected"; + field public static final java.lang.String STATUS_DISCONNECTING = "disconnecting"; + field public static final java.lang.String STATUS_MUTED_VIA_FOCUS = "muted-via-focus"; + field public static final java.lang.String STATUS_ON_HOLD = "on-hold"; + field public static final java.lang.String STATUS_PENDING = "pending"; + field public static final java.lang.String STATUS_SEND_ONLY = "sendonly"; + field public static final java.lang.String STATUS_SEND_RECV = "sendrecv"; + field public static final java.lang.String USER = "user"; + field public final java.util.HashMap mParticipants; + } + + public final class ImsExternalCallState implements android.os.Parcelable { + method public int describeContents(); + method public android.net.Uri getAddress(); + method public int getCallId(); + method public int getCallState(); + method public int getCallType(); + method public boolean isCallHeld(); + method public boolean isCallPullable(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CALL_STATE_CONFIRMED = 1; // 0x1 + field public static final int CALL_STATE_TERMINATED = 2; // 0x2 + field public static final android.os.Parcelable.Creator CREATOR; + } + + public final class ImsReasonInfo implements android.os.Parcelable { + method public int describeContents(); + method public int getCode(); + method public int getExtraCode(); + method public java.lang.String getExtraMessage(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CODE_ACCESS_CLASS_BLOCKED = 1512; // 0x5e8 + field public static final int CODE_ANSWERED_ELSEWHERE = 1014; // 0x3f6 + field public static final int CODE_BLACKLISTED_CALL_ID = 506; // 0x1fa + field public static final int CODE_CALL_BARRED = 240; // 0xf0 + field public static final int CODE_CALL_DROP_IWLAN_TO_LTE_UNAVAILABLE = 1100; // 0x44c + field public static final int CODE_CALL_END_CAUSE_CALL_PULL = 1016; // 0x3f8 + field public static final int CODE_CALL_PULL_OUT_OF_SYNC = 1015; // 0x3f7 + field public static final int CODE_DATA_DISABLED = 1406; // 0x57e + field public static final int CODE_DATA_LIMIT_REACHED = 1405; // 0x57d + field public static final int CODE_DIAL_MODIFIED_TO_DIAL = 246; // 0xf6 + field public static final int CODE_DIAL_MODIFIED_TO_DIAL_VIDEO = 247; // 0xf7 + field public static final int CODE_DIAL_MODIFIED_TO_SS = 245; // 0xf5 + field public static final int CODE_DIAL_MODIFIED_TO_USSD = 244; // 0xf4 + field public static final int CODE_DIAL_VIDEO_MODIFIED_TO_DIAL = 248; // 0xf8 + field public static final int CODE_DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO = 249; // 0xf9 + field public static final int CODE_DIAL_VIDEO_MODIFIED_TO_SS = 250; // 0xfa + field public static final int CODE_DIAL_VIDEO_MODIFIED_TO_USSD = 251; // 0xfb + field public static final int CODE_ECBM_NOT_SUPPORTED = 901; // 0x385 + field public static final int CODE_EMERGENCY_PERM_FAILURE = 364; // 0x16c + field public static final int CODE_EMERGENCY_TEMP_FAILURE = 363; // 0x16b + field public static final int CODE_EPDG_TUNNEL_ESTABLISH_FAILURE = 1400; // 0x578 + field public static final int CODE_EPDG_TUNNEL_LOST_CONNECTION = 1402; // 0x57a + field public static final int CODE_EPDG_TUNNEL_REKEY_FAILURE = 1401; // 0x579 + field public static final int CODE_FDN_BLOCKED = 241; // 0xf1 + field public static final int CODE_IKEV2_AUTH_FAILURE = 1408; // 0x580 + field public static final int CODE_IMEI_NOT_ACCEPTED = 243; // 0xf3 + field public static final int CODE_IWLAN_DPD_FAILURE = 1300; // 0x514 + field public static final int CODE_LOCAL_CALL_BUSY = 142; // 0x8e + field public static final int CODE_LOCAL_CALL_CS_RETRY_REQUIRED = 146; // 0x92 + field public static final int CODE_LOCAL_CALL_DECLINE = 143; // 0x8f + field public static final int CODE_LOCAL_CALL_EXCEEDED = 141; // 0x8d + field public static final int CODE_LOCAL_CALL_RESOURCE_RESERVATION_FAILED = 145; // 0x91 + field public static final int CODE_LOCAL_CALL_TERMINATED = 148; // 0x94 + field public static final int CODE_LOCAL_CALL_VCC_ON_PROGRESSING = 144; // 0x90 + field public static final int CODE_LOCAL_CALL_VOLTE_RETRY_REQUIRED = 147; // 0x93 + field public static final int CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE = 108; // 0x6c + field public static final int CODE_LOCAL_HO_NOT_FEASIBLE = 149; // 0x95 + field public static final int CODE_LOCAL_ILLEGAL_ARGUMENT = 101; // 0x65 + field public static final int CODE_LOCAL_ILLEGAL_STATE = 102; // 0x66 + field public static final int CODE_LOCAL_IMS_SERVICE_DOWN = 106; // 0x6a + field public static final int CODE_LOCAL_INTERNAL_ERROR = 103; // 0x67 + field public static final int CODE_LOCAL_LOW_BATTERY = 112; // 0x70 + field public static final int CODE_LOCAL_NETWORK_IP_CHANGED = 124; // 0x7c + field public static final int CODE_LOCAL_NETWORK_NO_LTE_COVERAGE = 122; // 0x7a + field public static final int CODE_LOCAL_NETWORK_NO_SERVICE = 121; // 0x79 + field public static final int CODE_LOCAL_NETWORK_ROAMING = 123; // 0x7b + field public static final int CODE_LOCAL_NOT_REGISTERED = 132; // 0x84 + field public static final int CODE_LOCAL_NO_PENDING_CALL = 107; // 0x6b + field public static final int CODE_LOCAL_POWER_OFF = 111; // 0x6f + field public static final int CODE_LOCAL_SERVICE_UNAVAILABLE = 131; // 0x83 + field public static final int CODE_LOW_BATTERY = 505; // 0x1f9 + field public static final int CODE_MAXIMUM_NUMBER_OF_CALLS_REACHED = 1403; // 0x57b + field public static final int CODE_MEDIA_INIT_FAILED = 401; // 0x191 + field public static final int CODE_MEDIA_NOT_ACCEPTABLE = 403; // 0x193 + field public static final int CODE_MEDIA_NO_DATA = 402; // 0x192 + field public static final int CODE_MEDIA_UNSPECIFIED = 404; // 0x194 + field public static final int CODE_MULTIENDPOINT_NOT_SUPPORTED = 902; // 0x386 + field public static final int CODE_NETWORK_DETACH = 1513; // 0x5e9 + field public static final int CODE_NETWORK_REJECT = 1504; // 0x5e0 + field public static final int CODE_NETWORK_RESP_TIMEOUT = 1503; // 0x5df + field public static final int CODE_NO_VALID_SIM = 1501; // 0x5dd + field public static final int CODE_OEM_CAUSE_1 = 61441; // 0xf001 + field public static final int CODE_OEM_CAUSE_10 = 61450; // 0xf00a + field public static final int CODE_OEM_CAUSE_11 = 61451; // 0xf00b + field public static final int CODE_OEM_CAUSE_12 = 61452; // 0xf00c + field public static final int CODE_OEM_CAUSE_13 = 61453; // 0xf00d + field public static final int CODE_OEM_CAUSE_14 = 61454; // 0xf00e + field public static final int CODE_OEM_CAUSE_15 = 61455; // 0xf00f + field public static final int CODE_OEM_CAUSE_2 = 61442; // 0xf002 + field public static final int CODE_OEM_CAUSE_3 = 61443; // 0xf003 + field public static final int CODE_OEM_CAUSE_4 = 61444; // 0xf004 + field public static final int CODE_OEM_CAUSE_5 = 61445; // 0xf005 + field public static final int CODE_OEM_CAUSE_6 = 61446; // 0xf006 + field public static final int CODE_OEM_CAUSE_7 = 61447; // 0xf007 + field public static final int CODE_OEM_CAUSE_8 = 61448; // 0xf008 + field public static final int CODE_OEM_CAUSE_9 = 61449; // 0xf009 + field public static final int CODE_RADIO_ACCESS_FAILURE = 1505; // 0x5e1 + field public static final int CODE_RADIO_INTERNAL_ERROR = 1502; // 0x5de + field public static final int CODE_RADIO_LINK_FAILURE = 1506; // 0x5e2 + field public static final int CODE_RADIO_LINK_LOST = 1507; // 0x5e3 + field public static final int CODE_RADIO_OFF = 1500; // 0x5dc + field public static final int CODE_RADIO_RELEASE_ABNORMAL = 1511; // 0x5e7 + field public static final int CODE_RADIO_RELEASE_NORMAL = 1510; // 0x5e6 + field public static final int CODE_RADIO_SETUP_FAILURE = 1509; // 0x5e5 + field public static final int CODE_RADIO_UPLINK_FAILURE = 1508; // 0x5e4 + field public static final int CODE_REGISTRATION_ERROR = 1000; // 0x3e8 + field public static final int CODE_REMOTE_CALL_DECLINE = 1404; // 0x57c + field public static final int CODE_SIP_ALTERNATE_EMERGENCY_CALL = 1514; // 0x5ea + field public static final int CODE_SIP_BAD_ADDRESS = 337; // 0x151 + field public static final int CODE_SIP_BAD_REQUEST = 331; // 0x14b + field public static final int CODE_SIP_BUSY = 338; // 0x152 + field public static final int CODE_SIP_CLIENT_ERROR = 342; // 0x156 + field public static final int CODE_SIP_FORBIDDEN = 332; // 0x14c + field public static final int CODE_SIP_GLOBAL_ERROR = 362; // 0x16a + field public static final int CODE_SIP_NOT_ACCEPTABLE = 340; // 0x154 + field public static final int CODE_SIP_NOT_FOUND = 333; // 0x14d + field public static final int CODE_SIP_NOT_REACHABLE = 341; // 0x155 + field public static final int CODE_SIP_NOT_SUPPORTED = 334; // 0x14e + field public static final int CODE_SIP_REDIRECTED = 321; // 0x141 + field public static final int CODE_SIP_REQUEST_CANCELLED = 339; // 0x153 + field public static final int CODE_SIP_REQUEST_TIMEOUT = 335; // 0x14f + field public static final int CODE_SIP_SERVER_ERROR = 354; // 0x162 + field public static final int CODE_SIP_SERVER_INTERNAL_ERROR = 351; // 0x15f + field public static final int CODE_SIP_SERVER_TIMEOUT = 353; // 0x161 + field public static final int CODE_SIP_SERVICE_UNAVAILABLE = 352; // 0x160 + field public static final int CODE_SIP_TEMPRARILY_UNAVAILABLE = 336; // 0x150 + field public static final int CODE_SIP_USER_REJECTED = 361; // 0x169 + field public static final int CODE_SUPP_SVC_CANCELLED = 1202; // 0x4b2 + field public static final int CODE_SUPP_SVC_FAILED = 1201; // 0x4b1 + field public static final int CODE_SUPP_SVC_REINVITE_COLLISION = 1203; // 0x4b3 + field public static final int CODE_TIMEOUT_1XX_WAITING = 201; // 0xc9 + field public static final int CODE_TIMEOUT_NO_ANSWER = 202; // 0xca + field public static final int CODE_TIMEOUT_NO_ANSWER_CALL_UPDATE = 203; // 0xcb + field public static final int CODE_UNSPECIFIED = 0; // 0x0 + field public static final int CODE_USER_DECLINE = 504; // 0x1f8 + field public static final int CODE_USER_IGNORE = 503; // 0x1f7 + field public static final int CODE_USER_NOANSWER = 502; // 0x1f6 + field public static final int CODE_USER_TERMINATED = 501; // 0x1f5 + field public static final int CODE_USER_TERMINATED_BY_REMOTE = 510; // 0x1fe + field public static final int CODE_UT_CB_PASSWORD_MISMATCH = 821; // 0x335 + field public static final int CODE_UT_NETWORK_ERROR = 804; // 0x324 + field public static final int CODE_UT_NOT_SUPPORTED = 801; // 0x321 + field public static final int CODE_UT_OPERATION_NOT_ALLOWED = 803; // 0x323 + field public static final int CODE_UT_SERVICE_UNAVAILABLE = 802; // 0x322 + field public static final int CODE_UT_SS_MODIFIED_TO_DIAL = 822; // 0x336 + field public static final int CODE_UT_SS_MODIFIED_TO_DIAL_VIDEO = 825; // 0x339 + field public static final int CODE_UT_SS_MODIFIED_TO_SS = 824; // 0x338 + field public static final int CODE_UT_SS_MODIFIED_TO_USSD = 823; // 0x337 + field public static final int CODE_WIFI_LOST = 1407; // 0x57f + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int EXTRA_CODE_CALL_RETRY_BY_SETTINGS = 3; // 0x3 + field public static final int EXTRA_CODE_CALL_RETRY_NORMAL = 1; // 0x1 + field public static final int EXTRA_CODE_CALL_RETRY_SILENT_REDIAL = 2; // 0x2 + field public static final java.lang.String EXTRA_MSG_SERVICE_NOT_AUTHORIZED = "Forbidden. Not Authorized for Service"; + } + public class ImsService extends android.app.Service { ctor public ImsService(); + method public android.telephony.ims.feature.MmTelFeature createMmTelFeature(int); + method public android.telephony.ims.feature.RcsFeature createRcsFeature(int); + method public void disableIms(int); + method public void enableIms(int); + method public android.telephony.ims.stub.ImsConfigImplBase getConfig(int); + method public android.telephony.ims.stub.ImsRegistrationImplBase getRegistration(int); + method public final void onUpdateSupportedImsFeatures(android.telephony.ims.stub.ImsFeatureConfiguration) throws android.os.RemoteException; + method public android.telephony.ims.stub.ImsFeatureConfiguration querySupportedImsFeatures(); + method public void readyForFeatureCreation(); + } + + public final class ImsSsData implements android.os.Parcelable { + ctor public ImsSsData(); + method public int describeContents(); + method public boolean isTypeBarring(); + method public boolean isTypeCf(); + method public boolean isTypeClip(); + method public boolean isTypeClir(); + method public boolean isTypeColp(); + method public boolean isTypeColr(); + method public boolean isTypeCw(); + method public boolean isTypeIcb(); + method public boolean isTypeInterrogation(); + method public boolean isTypeUnConditional(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int SS_ACTIVATION = 0; // 0x0 + field public static final int SS_ALL_BARRING = 18; // 0x12 + field public static final int SS_ALL_DATA_TELESERVICES = 3; // 0x3 + field public static final int SS_ALL_TELESERVICES_EXCEPT_SMS = 5; // 0x5 + field public static final int SS_ALL_TELESEVICES = 1; // 0x1 + field public static final int SS_ALL_TELE_AND_BEARER_SERVICES = 0; // 0x0 + field public static final int SS_BAIC = 16; // 0x10 + field public static final int SS_BAIC_ROAMING = 17; // 0x11 + field public static final int SS_BAOC = 13; // 0xd + field public static final int SS_BAOIC = 14; // 0xe + field public static final int SS_BAOIC_EXC_HOME = 15; // 0xf + field public static final int SS_CFU = 0; // 0x0 + field public static final int SS_CFUT = 6; // 0x6 + field public static final int SS_CF_ALL = 4; // 0x4 + field public static final int SS_CF_ALL_CONDITIONAL = 5; // 0x5 + field public static final int SS_CF_BUSY = 1; // 0x1 + field public static final int SS_CF_NOT_REACHABLE = 3; // 0x3 + field public static final int SS_CF_NO_REPLY = 2; // 0x2 + field public static final int SS_CLIP = 7; // 0x7 + field public static final int SS_CLIR = 8; // 0x8 + field public static final int SS_CNAP = 11; // 0xb + field public static final int SS_COLP = 9; // 0x9 + field public static final int SS_COLR = 10; // 0xa + field public static final int SS_DEACTIVATION = 1; // 0x1 + field public static final int SS_ERASURE = 4; // 0x4 + field public static final int SS_INCOMING_BARRING = 20; // 0x14 + field public static final int SS_INCOMING_BARRING_ANONYMOUS = 22; // 0x16 + field public static final int SS_INCOMING_BARRING_DN = 21; // 0x15 + field public static final int SS_INTERROGATION = 2; // 0x2 + field public static final int SS_OUTGOING_BARRING = 19; // 0x13 + field public static final int SS_REGISTRATION = 3; // 0x3 + field public static final int SS_SMS_SERVICES = 4; // 0x4 + field public static final int SS_TELEPHONY = 2; // 0x2 + field public static final int SS_WAIT = 12; // 0xc + } + + public final class ImsSsInfo implements android.os.Parcelable { + ctor public ImsSsInfo(); + method public int describeContents(); + method public java.lang.String getIcbNum(); + method public int getStatus(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int DISABLED = 0; // 0x0 + field public static final int ENABLED = 1; // 0x1 + field public static final int NOT_REGISTERED = -1; // 0xffffffff + } + + public final class ImsStreamMediaProfile implements android.os.Parcelable { + method public void copyFrom(android.telephony.ims.ImsStreamMediaProfile); + method public int describeContents(); + method public int getAudioDirection(); + method public int getAudioQuality(); + method public int getRttMode(); + method public int getVideoDirection(); + method public int getVideoQuality(); + method public boolean isRttCall(); + method public void setRttMode(int); + method public void writeToParcel(android.os.Parcel, int); + field public static final int AUDIO_QUALITY_AMR = 1; // 0x1 + field public static final int AUDIO_QUALITY_AMR_WB = 2; // 0x2 + field public static final int AUDIO_QUALITY_EVRC = 4; // 0x4 + field public static final int AUDIO_QUALITY_EVRC_B = 5; // 0x5 + field public static final int AUDIO_QUALITY_EVRC_NW = 7; // 0x7 + field public static final int AUDIO_QUALITY_EVRC_WB = 6; // 0x6 + field public static final int AUDIO_QUALITY_EVS_FB = 20; // 0x14 + field public static final int AUDIO_QUALITY_EVS_NB = 17; // 0x11 + field public static final int AUDIO_QUALITY_EVS_SWB = 19; // 0x13 + field public static final int AUDIO_QUALITY_EVS_WB = 18; // 0x12 + field public static final int AUDIO_QUALITY_G711A = 13; // 0xd + field public static final int AUDIO_QUALITY_G711AB = 15; // 0xf + field public static final int AUDIO_QUALITY_G711U = 11; // 0xb + field public static final int AUDIO_QUALITY_G722 = 14; // 0xe + field public static final int AUDIO_QUALITY_G723 = 12; // 0xc + field public static final int AUDIO_QUALITY_G729 = 16; // 0x10 + field public static final int AUDIO_QUALITY_GSM_EFR = 8; // 0x8 + field public static final int AUDIO_QUALITY_GSM_FR = 9; // 0x9 + field public static final int AUDIO_QUALITY_GSM_HR = 10; // 0xa + field public static final int AUDIO_QUALITY_NONE = 0; // 0x0 + field public static final int AUDIO_QUALITY_QCELP13K = 3; // 0x3 + field public static final android.os.Parcelable.Creator CREATOR; + field public static final int DIRECTION_INACTIVE = 0; // 0x0 + field public static final int DIRECTION_INVALID = -1; // 0xffffffff + field public static final int DIRECTION_RECEIVE = 1; // 0x1 + field public static final int DIRECTION_SEND = 2; // 0x2 + field public static final int DIRECTION_SEND_RECEIVE = 3; // 0x3 + field public static final int RTT_MODE_DISABLED = 0; // 0x0 + field public static final int RTT_MODE_FULL = 1; // 0x1 + field public static final int VIDEO_QUALITY_NONE = 0; // 0x0 + field public static final int VIDEO_QUALITY_QCIF = 1; // 0x1 + field public static final int VIDEO_QUALITY_QVGA_LANDSCAPE = 2; // 0x2 + field public static final int VIDEO_QUALITY_QVGA_PORTRAIT = 4; // 0x4 + field public static final int VIDEO_QUALITY_VGA_LANDSCAPE = 8; // 0x8 + field public static final int VIDEO_QUALITY_VGA_PORTRAIT = 16; // 0x10 + } + + public final class ImsSuppServiceNotification implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public final int code; + field public final java.lang.String[] history; + field public final int index; + field public final int notificationType; + field public final java.lang.String number; + field public final int type; + } + + public class ImsUtListener { + method public void onSupplementaryServiceIndication(android.telephony.ims.ImsSsData); + method public void onUtConfigurationCallBarringQueried(int, android.telephony.ims.ImsSsInfo[]); + method public void onUtConfigurationCallForwardQueried(int, android.telephony.ims.ImsCallForwardInfo[]); + method public void onUtConfigurationCallWaitingQueried(int, android.telephony.ims.ImsSsInfo[]); + method public void onUtConfigurationQueried(int, android.os.Bundle); + method public void onUtConfigurationQueryFailed(int, android.telephony.ims.ImsReasonInfo); + method public void onUtConfigurationUpdateFailed(int, android.telephony.ims.ImsReasonInfo); + method public void onUtConfigurationUpdated(int); + } + + public abstract class ImsVideoCallProvider { + ctor public ImsVideoCallProvider(); + method public void changeCallDataUsage(long); + method public void changeCameraCapabilities(android.telecom.VideoProfile.CameraCapabilities); + method public void changePeerDimensions(int, int); + method public void changeVideoQuality(int); + method public void handleCallSessionEvent(int); + method public abstract void onRequestCallDataUsage(); + method public abstract void onRequestCameraCapabilities(); + method public abstract void onSendSessionModifyRequest(android.telecom.VideoProfile, android.telecom.VideoProfile); + method public abstract void onSendSessionModifyResponse(android.telecom.VideoProfile); + method public abstract void onSetCamera(java.lang.String); + method public void onSetCamera(java.lang.String, int); + method public abstract void onSetDeviceOrientation(int); + method public abstract void onSetDisplaySurface(android.view.Surface); + method public abstract void onSetPauseImage(android.net.Uri); + method public abstract void onSetPreviewSurface(android.view.Surface); + method public abstract void onSetZoom(float); + method public void receiveSessionModifyRequest(android.telecom.VideoProfile); + method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile); + } + +} + +package android.telephony.ims.feature { + + public final class CapabilityChangeRequest implements android.os.Parcelable { + method public void addCapabilitiesToDisableForTech(int, int); + method public void addCapabilitiesToEnableForTech(int, int); + method public int describeContents(); + method public java.util.List getCapabilitiesToDisable(); + method public java.util.List getCapabilitiesToEnable(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static class CapabilityChangeRequest.CapabilityPair { + ctor public CapabilityChangeRequest.CapabilityPair(int, int); + method public int getCapability(); + method public int getRadioTech(); + } + + public abstract class ImsFeature { + ctor public ImsFeature(); + method public abstract void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy); + method public final int getFeatureState(); + method public abstract void onFeatureReady(); + method public abstract void onFeatureRemoved(); + method public final void setFeatureState(int); + field public static final int CAPABILITY_ERROR_GENERIC = -1; // 0xffffffff + field public static final int CAPABILITY_SUCCESS = 0; // 0x0 + field public static final int FEATURE_EMERGENCY_MMTEL = 0; // 0x0 + field public static final int FEATURE_MMTEL = 1; // 0x1 + field public static final int FEATURE_RCS = 2; // 0x2 + field public static final int STATE_INITIALIZING = 1; // 0x1 + field public static final int STATE_READY = 2; // 0x2 + field public static final int STATE_UNAVAILABLE = 0; // 0x0 + } + + protected static class ImsFeature.CapabilityCallbackProxy { + method public void onChangeCapabilityConfigurationError(int, int, int); + } + + public class MmTelFeature extends android.telephony.ims.feature.ImsFeature { + ctor public MmTelFeature(); + method public void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy); + method public android.telephony.ims.ImsCallProfile createCallProfile(int, int); + method public android.telephony.ims.stub.ImsCallSessionImplBase createCallSession(android.telephony.ims.ImsCallProfile); + method public android.telephony.ims.stub.ImsEcbmImplBase getEcbm(); + method public android.telephony.ims.stub.ImsMultiEndpointImplBase getMultiEndpoint(); + method public android.telephony.ims.stub.ImsSmsImplBase getSmsImplementation(); + method public android.telephony.ims.stub.ImsUtImplBase getUt(); + method public final void notifyCapabilitiesStatusChanged(android.telephony.ims.feature.MmTelFeature.MmTelCapabilities); + method public final void notifyIncomingCall(android.telephony.ims.stub.ImsCallSessionImplBase, android.os.Bundle); + method public void onFeatureReady(); + method public void onFeatureRemoved(); + method public boolean queryCapabilityConfiguration(int, int); + method public final android.telephony.ims.feature.MmTelFeature.MmTelCapabilities queryCapabilityStatus(); + method public int shouldProcessCall(java.lang.String[]); + field public static final int PROCESS_CALL_CSFB = 1; // 0x1 + field public static final int PROCESS_CALL_EMERGENCY_CSFB = 2; // 0x2 + field public static final int PROCESS_CALL_IMS = 0; // 0x0 + } + + public static class MmTelFeature.MmTelCapabilities { + ctor public MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities); + ctor public MmTelFeature.MmTelCapabilities(int); + method public final void addCapabilities(int); + method public final boolean isCapable(int); + method public final void removeCapabilities(int); + field public static final int CAPABILITY_TYPE_SMS = 8; // 0x8 + field public static final int CAPABILITY_TYPE_UT = 4; // 0x4 + field public static final int CAPABILITY_TYPE_VIDEO = 2; // 0x2 + field public static final int CAPABILITY_TYPE_VOICE = 1; // 0x1 + } + + public static abstract class MmTelFeature.MmTelCapabilities.MmTelCapability implements java.lang.annotation.Annotation { + } + + public static abstract class MmTelFeature.ProcessCallResult implements java.lang.annotation.Annotation { + } + + public class RcsFeature extends android.telephony.ims.feature.ImsFeature { + ctor public RcsFeature(); + method public void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy); + method public void onFeatureReady(); + method public void onFeatureRemoved(); } } -package android.telephony.ims.internal.stub { +package android.telephony.ims.stub { + + public class ImsCallSessionImplBase implements java.lang.AutoCloseable { + ctor public ImsCallSessionImplBase(); + method public void accept(int, android.telephony.ims.ImsStreamMediaProfile); + method public void close(); + method public void extendToConference(java.lang.String[]); + method public java.lang.String getCallId(); + method public android.telephony.ims.ImsCallProfile getCallProfile(); + method public android.telephony.ims.ImsVideoCallProvider getImsVideoCallProvider(); + method public android.telephony.ims.ImsCallProfile getLocalCallProfile(); + method public java.lang.String getProperty(java.lang.String); + method public android.telephony.ims.ImsCallProfile getRemoteCallProfile(); + method public int getState(); + method public void hold(android.telephony.ims.ImsStreamMediaProfile); + method public void inviteParticipants(java.lang.String[]); + method public boolean isInCall(); + method public boolean isMultiparty(); + method public void merge(); + method public void reject(int); + method public void removeParticipants(java.lang.String[]); + method public void resume(android.telephony.ims.ImsStreamMediaProfile); + method public void sendDtmf(char, android.os.Message); + method public void sendRttMessage(java.lang.String); + method public void sendRttModifyRequest(android.telephony.ims.ImsCallProfile); + method public void sendRttModifyResponse(boolean); + method public void sendUssd(java.lang.String); + method public void setListener(android.telephony.ims.ImsCallSessionListener); + method public void setMute(boolean); + method public void start(java.lang.String, android.telephony.ims.ImsCallProfile); + method public void startConference(java.lang.String[], android.telephony.ims.ImsCallProfile); + method public void startDtmf(char); + method public void stopDtmf(); + method public void terminate(int); + method public void update(int, android.telephony.ims.ImsStreamMediaProfile); + field public static final int USSD_MODE_NOTIFY = 0; // 0x0 + field public static final int USSD_MODE_REQUEST = 1; // 0x1 + } + + public static class ImsCallSessionImplBase.State { + method public static java.lang.String toString(int); + field public static final int ESTABLISHED = 4; // 0x4 + field public static final int ESTABLISHING = 3; // 0x3 + field public static final int IDLE = 0; // 0x0 + field public static final int INITIATED = 1; // 0x1 + field public static final int INVALID = -1; // 0xffffffff + field public static final int NEGOTIATING = 2; // 0x2 + field public static final int REESTABLISHING = 6; // 0x6 + field public static final int RENEGOTIATING = 5; // 0x5 + field public static final int TERMINATED = 8; // 0x8 + field public static final int TERMINATING = 7; // 0x7 + } + + public class ImsConfigImplBase { + ctor public ImsConfigImplBase(); + method public int getConfigInt(int); + method public java.lang.String getConfigString(int); + method public final void notifyProvisionedValueChanged(int, int); + method public final void notifyProvisionedValueChanged(int, java.lang.String); + method public int setConfig(int, int); + method public int setConfig(int, java.lang.String); + field public static final int CONFIG_RESULT_FAILED = 1; // 0x1 + field public static final int CONFIG_RESULT_SUCCESS = 0; // 0x0 + field public static final int CONFIG_RESULT_UNKNOWN = -1; // 0xffffffff + } + + public class ImsEcbmImplBase { + ctor public ImsEcbmImplBase(); + method public final void enteredEcbm(); + method public void exitEmergencyCallbackMode(); + method public final void exitedEcbm(); + } + + public final class ImsFeatureConfiguration implements android.os.Parcelable { + ctor public ImsFeatureConfiguration(); + method public int describeContents(); + method public int[] getServiceFeatures(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public static class ImsFeatureConfiguration.Builder { + ctor public ImsFeatureConfiguration.Builder(); + method public android.telephony.ims.stub.ImsFeatureConfiguration.Builder addFeature(int); + method public android.telephony.ims.stub.ImsFeatureConfiguration build(); + } - public class SmsImplBase { - ctor public SmsImplBase(); + public class ImsMultiEndpointImplBase { + ctor public ImsMultiEndpointImplBase(); + method public final void onImsExternalCallStateUpdate(java.util.List); + method public void requestImsExternalCallStateInfo(); + } + + public class ImsRegistrationImplBase { + ctor public ImsRegistrationImplBase(); + method public final void onDeregistered(android.telephony.ims.ImsReasonInfo); + method public final void onRegistered(int); + method public final void onRegistering(int); + method public final void onSubscriberAssociatedUriChanged(android.net.Uri[]); + method public final void onTechnologyChangeFailed(int, android.telephony.ims.ImsReasonInfo); + field public static final int REGISTRATION_TECH_IWLAN = 1; // 0x1 + field public static final int REGISTRATION_TECH_LTE = 0; // 0x0 + field public static final int REGISTRATION_TECH_NONE = -1; // 0xffffffff + } + + public class ImsSmsImplBase { + ctor public ImsSmsImplBase(); method public void acknowledgeSms(int, int, int); method public void acknowledgeSmsReport(int, int, int); method public java.lang.String getSmsFormat(); @@ -5090,6 +5741,29 @@ package android.telephony.ims.internal.stub { field public static final int STATUS_REPORT_STATUS_OK = 1; // 0x1 } + public class ImsUtImplBase { + ctor public ImsUtImplBase(); + method public void close(); + method public int queryCallBarring(int); + method public int queryCallBarringForServiceClass(int, int); + method public int queryCallForward(int, java.lang.String); + method public int queryCallWaiting(); + method public int queryClip(); + method public int queryClir(); + method public int queryColp(); + method public int queryColr(); + method public void setListener(android.telephony.ims.ImsUtListener); + method public int transact(android.os.Bundle); + method public int updateCallBarring(int, int, java.lang.String[]); + method public int updateCallBarringForServiceClass(int, int, java.lang.String[], int); + method public int updateCallForward(int, int, java.lang.String, int, int); + method public int updateCallWaiting(boolean, int); + method public int updateClip(boolean); + method public int updateClir(int); + method public int updateColp(boolean); + method public int updateColr(int); + } + } package android.telephony.mbms { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 91d86c698f8..a34e9f9481f 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -27,8 +27,8 @@ import android.os.PersistableBundle; import android.os.RemoteException; import android.os.ServiceManager; import android.service.carrier.CarrierService; +import android.telephony.ims.ImsReasonInfo; -import com.android.ims.ImsReasonInfo; import com.android.internal.telephony.ICarrierConfigLoader; /** @@ -1390,7 +1390,7 @@ public class CarrierConfigManager { "allow_video_calling_fallback_bool"; /** - * Defines operator-specific {@link com.android.ims.ImsReasonInfo} mappings. + * Defines operator-specific {@link ImsReasonInfo} mappings. * * Format: "ORIGINAL_CODE|MESSAGE|NEW_CODE" * Where {@code ORIGINAL_CODE} corresponds to a {@link ImsReasonInfo#getCode()} code, diff --git a/telephony/java/com/android/ims/ImsCallForwardInfo.aidl b/telephony/java/android/telephony/ims/ImsCallForwardInfo.aidl similarity index 95% rename from telephony/java/com/android/ims/ImsCallForwardInfo.aidl rename to telephony/java/android/telephony/ims/ImsCallForwardInfo.aidl index a7c3f9a5f72..b322b39be7e 100644 --- a/telephony/java/com/android/ims/ImsCallForwardInfo.aidl +++ b/telephony/java/android/telephony/ims/ImsCallForwardInfo.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsCallForwardInfo; diff --git a/telephony/java/com/android/ims/ImsCallForwardInfo.java b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java similarity index 77% rename from telephony/java/com/android/ims/ImsCallForwardInfo.java rename to telephony/java/android/telephony/ims/ImsCallForwardInfo.java index eeee0fc938b..6d721817906 100644 --- a/telephony/java/com/android/ims/ImsCallForwardInfo.java +++ b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -24,23 +25,32 @@ import android.os.Parcelable; * * @hide */ -public class ImsCallForwardInfo implements Parcelable { +@SystemApi +public final class ImsCallForwardInfo implements Parcelable { // Refer to ImsUtInterface#CDIV_CF_XXX + /** @hide */ public int mCondition; // 0: disabled, 1: enabled + /** @hide */ public int mStatus; // 0x91: International, 0x81: Unknown + /** @hide */ public int mToA; // Service class + /** @hide */ public int mServiceClass; // Number (it will not include the "sip" or "tel" URI scheme) + /** @hide */ public String mNumber; // No reply timer for CF + /** @hide */ public int mTimeSeconds; + /** @hide */ public ImsCallForwardInfo() { } + /** @hide */ public ImsCallForwardInfo(Parcel in) { readFromParcel(in); } @@ -91,4 +101,28 @@ public class ImsCallForwardInfo implements Parcelable { return new ImsCallForwardInfo[size]; } }; + + public int getCondition() { + return mCondition; + } + + public int getStatus() { + return mStatus; + } + + public int getToA() { + return mToA; + } + + public int getServiceClass() { + return mServiceClass; + } + + public String getNumber() { + return mNumber; + } + + public int getTimeSeconds() { + return mTimeSeconds; + } } diff --git a/telephony/java/com/android/ims/ImsCallProfile.aidl b/telephony/java/android/telephony/ims/ImsCallProfile.aidl similarity index 95% rename from telephony/java/com/android/ims/ImsCallProfile.aidl rename to telephony/java/android/telephony/ims/ImsCallProfile.aidl index a356d1352eb..e24e1453091 100644 --- a/telephony/java/com/android/ims/ImsCallProfile.aidl +++ b/telephony/java/android/telephony/ims/ImsCallProfile.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsCallProfile; diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java similarity index 90% rename from telephony/java/com/android/ims/ImsCallProfile.java rename to telephony/java/android/telephony/ims/ImsCallProfile.java index 693aaff8ce0..27e5f943982 100644 --- a/telephony/java/com/android/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -32,7 +33,8 @@ import com.android.internal.telephony.PhoneConstants; * * @hide */ -public class ImsCallProfile implements Parcelable { +@SystemApi +public final class ImsCallProfile implements Parcelable { private static final String TAG = "ImsCallProfile"; /** @@ -110,52 +112,92 @@ public class ImsCallProfile implements Parcelable { * the video during voice call. * conference_avail : Indicates if the session can be extended to the conference. */ + /** + * @hide + */ public static final String EXTRA_CONFERENCE = "conference"; + /** + * @hide + */ public static final String EXTRA_E_CALL = "e_call"; + /** + * @hide + */ public static final String EXTRA_VMS = "vms"; + /** + * @hide + */ public static final String EXTRA_CALL_MODE_CHANGEABLE = "call_mode_changeable"; + /** + * @hide + */ public static final String EXTRA_CONFERENCE_AVAIL = "conference_avail"; // Extra string for internal use only. OEMs should not use // this for packing extras. + /** + * @hide + */ public static final String EXTRA_OEM_EXTRAS = "OemCallExtras"; /** - * Integer extra properties - * oir : Rule for originating identity (number) presentation, MO/MT. + * Rule for originating identity (number) presentation, MO/MT. * {@link ImsCallProfile#OIR_DEFAULT} * {@link ImsCallProfile#OIR_PRESENTATION_RESTRICTED} * {@link ImsCallProfile#OIR_PRESENTATION_NOT_RESTRICTED} - * cnap : Rule for calling name presentation + */ + public static final String EXTRA_OIR = "oir"; + /** + * Rule for calling name presentation * {@link ImsCallProfile#OIR_DEFAULT} * {@link ImsCallProfile#OIR_PRESENTATION_RESTRICTED} * {@link ImsCallProfile#OIR_PRESENTATION_NOT_RESTRICTED} - * dialstring : To identify the Ims call type, MO - * {@link ImsCallProfile#DIALSTRING_NORMAL_CALL} + */ + public static final String EXTRA_CNAP = "cnap"; + /** + * To identify the Ims call type, MO + * {@link ImsCallProfile#DIALSTRING_NORMAL} * {@link ImsCallProfile#DIALSTRING_SS_CONF} * {@link ImsCallProfile#DIALSTRING_USSD} */ - public static final String EXTRA_OIR = "oir"; - public static final String EXTRA_CNAP = "cnap"; public static final String EXTRA_DIALSTRING = "dialstring"; /** * Values for EXTRA_OIR / EXTRA_CNAP */ + /** + * Default presentation for Originating Identity. + */ public static final int OIR_DEFAULT = 0; // "user subscription default value" + /** + * Restricted presentation for Originating Identity. + */ public static final int OIR_PRESENTATION_RESTRICTED = 1; + /** + * Not restricted presentation for Originating Identity. + */ public static final int OIR_PRESENTATION_NOT_RESTRICTED = 2; + /** + * Presentation unknown for Originating Identity. + */ public static final int OIR_PRESENTATION_UNKNOWN = 3; + /** + * Payphone presentation for Originating Identity. + */ public static final int OIR_PRESENTATION_PAYPHONE = 4; + //Values for EXTRA_DIALSTRING /** - * Values for EXTRA_DIALSTRING + * A default or normal normal call. */ - // default (normal call) public static final int DIALSTRING_NORMAL = 0; - // Call for SIP-based user configuration + /** + * Call for SIP-based user configuration + */ public static final int DIALSTRING_SS_CONF = 1; - // Call for USSD message + /** + * Call for USSD message + */ public static final int DIALSTRING_USSD = 2; /** @@ -215,8 +257,11 @@ public class ImsCallProfile implements Parcelable { */ public static final String EXTRA_CALL_RAT_TYPE_ALT = "callRadioTech"; + /** @hide */ public int mServiceType; + /** @hide */ public int mCallType; + /** @hide */ public int mRestrictCause = CALL_RESTRICT_CAUSE_NONE; /** @@ -241,13 +286,17 @@ public class ImsCallProfile implements Parcelable { * Invalid types will be removed when the {@link ImsCallProfile} is parceled for transmit across * a {@link android.os.Binder}. */ + /** @hide */ public Bundle mCallExtras; + /** @hide */ public ImsStreamMediaProfile mMediaProfile; + /** @hide */ public ImsCallProfile(Parcel in) { readFromParcel(in); } + /** @hide */ public ImsCallProfile() { mServiceType = SERVICE_TYPE_NORMAL; mCallType = CALL_TYPE_VOICE_N_VIDEO; @@ -255,6 +304,7 @@ public class ImsCallProfile implements Parcelable { mMediaProfile = new ImsStreamMediaProfile(); } + /** @hide */ public ImsCallProfile(int serviceType, int callType) { mServiceType = serviceType; mCallType = callType; @@ -366,8 +416,28 @@ public class ImsCallProfile implements Parcelable { } }; + public int getServiceType() { + return mServiceType; + } + + public int getCallType() { + return mCallType; + } + + public int getRestrictCause() { + return mRestrictCause; + } + + public Bundle getCallExtras() { + return mCallExtras; + } + + public ImsStreamMediaProfile getMediaProfile() { + return mMediaProfile; + } + /** - * Converts from the call types defined in {@link com.android.ims.ImsCallProfile} to the + * Converts from the call types defined in {@link ImsCallProfile} to the * video state values defined in {@link VideoProfile}. * * @param callProfile The call profile. @@ -434,9 +504,9 @@ public class ImsCallProfile implements Parcelable { } /** - * Translate presentation value to OIR value - * @param presentation - * @return OIR valuse + * Badly named old method, kept for compatibility. + * See {@link #presentationToOir(int)}. + * @hide */ public static int presentationToOIR(int presentation) { switch (presentation) { @@ -453,10 +523,20 @@ public class ImsCallProfile implements Parcelable { } } + /** + * Translate presentation value to OIR value + * @param presentation + * @return OIR values + */ + public static int presentationToOir(int presentation) { + return presentationToOIR(presentation); + } + /** * Translate OIR value to presentation value * @param oir value * @return presentation value + * @hide */ public static int OIRToPresentation(int oir) { switch(oir) { diff --git a/telephony/java/com/android/ims/internal/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java similarity index 97% rename from telephony/java/com/android/ims/internal/ImsCallSession.java rename to telephony/java/android/telephony/ims/ImsCallSession.java index e914f484fc7..c3d103f88db 100644 --- a/telephony/java/com/android/ims/internal/ImsCallSession.java +++ b/telephony/java/android/telephony/ims/ImsCallSession.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,23 +11,26 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims.internal; +package android.telephony.ims; +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Message; import android.os.RemoteException; import android.telephony.ims.aidl.IImsCallSessionListener; import java.util.Objects; +import java.util.concurrent.Executor; +import android.telephony.ims.stub.ImsCallSessionImplBase; import android.util.Log; -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsConferenceState; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.ImsSuppServiceNotification; + +import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsVideoCallProvider; /** * Provides the call initiation/termination, and media exchange between two IMS endpoints. @@ -39,7 +42,8 @@ public class ImsCallSession { private static final String TAG = "ImsCallSession"; /** - * Defines IMS call session state. + * Defines IMS call session state. Please use {@link ImsCallSessionImplBase.State} definition. + * This is kept around for capability reasons. */ public static class State { public static final int IDLE = 0; @@ -92,6 +96,7 @@ public class ImsCallSession { * Listener for events relating to an IMS session, such as when a session is being * recieved ("on ringing") or a call is outgoing ("on calling"). *

Many of these events are also received by {@link ImsCall.Listener}.

+ * @hide */ public static class Listener { /** @@ -449,6 +454,7 @@ public class ImsCallSession { private boolean mClosed = false; private Listener mListener; + /** @hide */ public ImsCallSession(IImsCallSession iSession) { miSession = iSession; @@ -462,6 +468,7 @@ public class ImsCallSession { } } + /** @hide */ public ImsCallSession(IImsCallSession iSession, Listener listener) { this(iSession); setListener(listener); @@ -470,15 +477,17 @@ public class ImsCallSession { /** * Closes this object. This object is not usable after being closed. */ - public synchronized void close() { - if (mClosed) { - return; - } + public void close() { + synchronized (this) { + if (mClosed) { + return; + } - try { - miSession.close(); - mClosed = true; - } catch (RemoteException e) { + try { + miSession.close(); + mClosed = true; + } catch (RemoteException e) { + } } } @@ -554,6 +563,7 @@ public class ImsCallSession { * Gets the video call provider for the session. * * @return The video call provider. + * @hide */ public IImsVideoCallProvider getVideoCallProvider() { if (mClosed) { @@ -659,6 +669,7 @@ public class ImsCallSession { * override the previous listener. * * @param listener to listen to the session events of this object + * @hide */ public void setListener(Listener listener) { mListener = listener; @@ -1274,7 +1285,7 @@ public class ImsCallSession { } /** - * Notifies of a case where a {@link com.android.ims.internal.ImsCallSession} may + * Notifies of a case where a {@link ImsCallSession} may * potentially handover from one radio technology to another. * @param srcAccessTech The source radio access technology; one of the access technology * constants defined in {@link android.telephony.ServiceState}. For diff --git a/telephony/java/android/telephony/ims/ImsCallSessionListener.java b/telephony/java/android/telephony/ims/ImsCallSessionListener.java index 96c7af6bd03..a7f124a5b79 100644 --- a/telephony/java/android/telephony/ims/ImsCallSessionListener.java +++ b/telephony/java/android/telephony/ims/ImsCallSessionListener.java @@ -16,343 +16,508 @@ package android.telephony.ims; +import android.annotation.SystemApi; import android.os.RemoteException; import android.telephony.ims.aidl.IImsCallSessionListener; +import android.telephony.ims.stub.ImsCallSessionImplBase; -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsConferenceState; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.ImsSuppServiceNotification; import com.android.ims.internal.IImsCallSession; -import com.android.ims.internal.ImsCallSession; /** - * Proxy class for interfacing with the framework's Call session for an ongoing IMS call. - * - * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you - * will break other implementations of ImsCallSessionListener maintained by other ImsServices. + * Listener interface for notifying the Framework's {@link ImsCallSession} for updates to an ongoing + * IMS call. * * @hide */ +// DO NOT remove or change the existing APIs, only add new ones to this implementation or you +// will break other implementations of ImsCallSessionListener maintained by other ImsServices. +// TODO: APIs in here do not conform to API guidelines yet. This can be changed if +// ImsCallSessionListenerConverter is also changed. +@SystemApi public class ImsCallSessionListener { private final IImsCallSessionListener mListener; + /** @hide */ public ImsCallSessionListener(IImsCallSessionListener l) { mListener = l; } /** - * Called when a request is sent out to initiate a new session - * and 1xx response is received from the network. + * A request has been sent out to initiate a new IMS call session and a 1xx response has been + * received from the network. */ - public void callSessionProgressing(ImsStreamMediaProfile profile) - throws RemoteException { - mListener.callSessionProgressing(profile); + public void callSessionProgressing(ImsStreamMediaProfile profile) { + try { + mListener.callSessionProgressing(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session is initiated. + * The IMS call session has been initiated. * - * @param profile the associated {@link ImsCallSession}. + * @param profile the associated {@link ImsCallProfile}. */ - public void callSessionInitiated(ImsCallProfile profile) throws RemoteException { - mListener.callSessionInitiated(profile); + public void callSessionInitiated(ImsCallProfile profile) { + try { + mListener.callSessionInitiated(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session establishment has failed. + * The IMS call session establishment has failed. * - * @param reasonInfo detailed reason of the session establishment failure + * @param reasonInfo {@link ImsReasonInfo} detailing the reason of the IMS call session + * establishment failure. */ - public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionInitiatedFailed(reasonInfo); + public void callSessionInitiatedFailed(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionInitiatedFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session is terminated. + * The IMS call session has been terminated. * - * @param reasonInfo detailed reason of the session termination + * @param reasonInfo {@link ImsReasonInfo} detailing the reason of the session termination. */ - public void callSessionTerminated(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionTerminated(reasonInfo); + public void callSessionTerminated(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionTerminated(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session is on hold. + * The IMS call session has started the process of holding the call. If it fails, + * {@link #callSessionHoldFailed(ImsReasonInfo)} should be called. + * + * If the IMS call session is resumed, call {@link #callSessionResumed(ImsCallProfile)}. + * + * @param profile The associated {@link ImsCallProfile} of the call session that has been put + * on hold. */ - public void callSessionHeld(ImsCallProfile profile) throws RemoteException { - mListener.callSessionHeld(profile); + public void callSessionHeld(ImsCallProfile profile) { + try { + mListener.callSessionHeld(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session hold has failed. + * The IMS call session has failed to be held. * - * @param reasonInfo detailed reason of the session hold failure + * @param reasonInfo {@link ImsReasonInfo} detailing the reason of the session hold failure. */ - public void callSessionHoldFailed(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionHoldFailed(reasonInfo); + public void callSessionHoldFailed(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionHoldFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session hold is received from the remote user. + * This IMS Call session has been put on hold by the remote party. + * + * @param profile The {@link ImsCallProfile} associated with this IMS call session. */ - public void callSessionHoldReceived(ImsCallProfile profile) throws RemoteException { - mListener.callSessionHoldReceived(profile); + public void callSessionHoldReceived(ImsCallProfile profile) { + try { + mListener.callSessionHoldReceived(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session resume is done. + * The IMS call session has started the process of resuming the call. If the process of resuming + * the call fails, call {@link #callSessionResumeFailed(ImsReasonInfo)}. + * + * @param profile The {@link ImsCallProfile} associated with this IMS call session. */ - public void callSessionResumed(ImsCallProfile profile) throws RemoteException { - mListener.callSessionResumed(profile); + public void callSessionResumed(ImsCallProfile profile) { + try { + mListener.callSessionResumed(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session resume has failed. + * The IMS call session resume has failed. * - * @param reasonInfo detailed reason of the session resume failure + * @param reasonInfo {@link ImsReasonInfo} containing the detailed reason of the session resume + * failure. */ - public void callSessionResumeFailed(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionResumeFailed(reasonInfo); + public void callSessionResumeFailed(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionResumeFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session resume is received from the remote user. + * The remote party has resumed this IMS call session. + * + * @param profile {@link ImsCallProfile} associated with the IMS call session. */ - public void callSessionResumeReceived(ImsCallProfile profile) throws RemoteException { - mListener.callSessionResumeReceived(profile); + public void callSessionResumeReceived(ImsCallProfile profile) { + try { + mListener.callSessionResumeReceived(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session merge has been started. At this point, the {@code newSession} - * represents the session which has been initiated to the IMS conference server for the - * new merged conference. + * The IMS call session merge has been started. At this point, the {@code newSession} + * represents the IMS call session which represents the new merged conference and has been + * initiated to the IMS conference server. * - * @param newSession the session object that is merged with an active & hold session + * @param newSession the {@link ImsCallSessionImplBase} that represents the merged active & held + * sessions. + * @param profile The {@link ImsCallProfile} associated with this IMS call session. */ - public void callSessionMergeStarted(ImsCallSession newSession, ImsCallProfile profile) - throws RemoteException { - mListener.callSessionMergeStarted(newSession != null ? newSession.getSession() : null, - profile); + public void callSessionMergeStarted(ImsCallSessionImplBase newSession, ImsCallProfile profile) + { + try { + mListener.callSessionMergeStarted(newSession != null ? + newSession.getServiceImpl() : null, profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session merge has been started. At this point, the {@code newSession} - * represents the session which has been initiated to the IMS conference server for the - * new merged conference. - * - * @param newSession the session object that is merged with an active & hold session + * Compatibility method for older implementations. + * See {@link #callSessionMergeStarted(ImsCallSessionImplBase, ImsCallProfile)}. * * @hide */ public void callSessionMergeStarted(IImsCallSession newSession, ImsCallProfile profile) - throws RemoteException { - mListener.callSessionMergeStarted(newSession, profile); + { + try { + mListener.callSessionMergeStarted(newSession, profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session merge is successful and the merged session is active. + * The session merge is successful and the merged {@link ImsCallSession} is active. * - * @param newSession the new session object that is used for the conference + * @param newSession the new {@link ImsCallSessionImplBase} + * that represents the conference IMS call + * session. */ - public void callSessionMergeComplete(ImsCallSession newSession) throws RemoteException { - mListener.callSessionMergeComplete(newSession != null ? newSession.getSession() : null); + public void callSessionMergeComplete(ImsCallSessionImplBase newSession) { + try { + mListener.callSessionMergeComplete(newSession != null ? + newSession.getServiceImpl() : null); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session merge is successful and the merged session is active. + * Compatibility method for older implementations of ImsService. * - * @param newSession the new session object that is used for the conference + * See {@link #callSessionMergeComplete(ImsCallSessionImplBase)}}. * * @hide */ - public void callSessionMergeComplete(IImsCallSession newSession) throws RemoteException { - mListener.callSessionMergeComplete(newSession); + public void callSessionMergeComplete(IImsCallSession newSession) { + try { + mListener.callSessionMergeComplete(newSession); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session merge has failed. + * The IMS call session merge has failed. * - * @param reasonInfo detailed reason of the call merge failure + * @param reasonInfo {@link ImsReasonInfo} contining the reason for the call merge failure. */ - public void callSessionMergeFailed(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionMergeFailed(reasonInfo); + public void callSessionMergeFailed(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionMergeFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session is updated (except for hold/unhold). + * The IMS call session profile has been updated. Does not include holding or resuming a call. + * + * @param profile The {@link ImsCallProfile} associated with the updated IMS call session. */ - public void callSessionUpdated(ImsCallProfile profile) throws RemoteException { - mListener.callSessionUpdated(profile); + public void callSessionUpdated(ImsCallProfile profile) { + try { + mListener.callSessionUpdated(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session update has failed. + * The IMS call session profile update has failed. * - * @param reasonInfo detailed reason of the session update failure + * @param reasonInfo {@link ImsReasonInfo} containing a reason for the session update failure. */ - public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionUpdateFailed(reasonInfo); + public void callSessionUpdateFailed(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionUpdateFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session update is received from the remote user. + * The IMS call session profile has received an update from the remote user. + * + * @param profile The new {@link ImsCallProfile} associated with the update. */ - public void callSessionUpdateReceived(ImsCallProfile profile) throws RemoteException { - mListener.callSessionUpdateReceived(profile); + public void callSessionUpdateReceived(ImsCallProfile profile) { + try { + mListener.callSessionUpdateReceived(profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** * Called when the session has been extended to a conference session. * - * @param newSession the session object that is extended to the conference - * from the active session + * If the conference extension fails, call + * {@link #callSessionConferenceExtendFailed(ImsReasonInfo)}. + * + * @param newSession the session object that is extended to the conference from the active + * IMS Call session. + * @param profile The {@link ImsCallProfile} associated with the IMS call session. */ - public void callSessionConferenceExtended(ImsCallSession newSession, ImsCallProfile profile) - throws RemoteException { - mListener.callSessionConferenceExtended(newSession != null ? newSession.getSession() : null, - profile); + public void callSessionConferenceExtended(ImsCallSessionImplBase newSession, + ImsCallProfile profile) { + try { + mListener.callSessionConferenceExtended( + newSession != null ? newSession.getServiceImpl() : null, profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the session has been extended to a conference session. + * Compatibility method to interface with older versions of ImsService. + * See {@link #callSessionConferenceExtended(ImsCallSessionImplBase, ImsCallProfile)}. * - * @param newSession the session object that is extended to the conference - * from the active session * @hide */ - public void callSessionConferenceExtended(IImsCallSession newSession, ImsCallProfile profile) - throws RemoteException { - mListener.callSessionConferenceExtended(newSession, profile); + public void callSessionConferenceExtended(IImsCallSession newSession, ImsCallProfile profile) { + try { + mListener.callSessionConferenceExtended(newSession, profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the conference extension has failed. + * The previous conference extension has failed. * - * @param reasonInfo detailed reason of the conference extension failure + * @param reasonInfo {@link ImsReasonInfo} containing the detailed reason of the conference + * extension failure. */ - public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionConferenceExtendFailed(reasonInfo); + public void callSessionConferenceExtendFailed(ImsReasonInfo reasonInfo) { + try { + mListener.callSessionConferenceExtendFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the conference extension is received from the remote user. + * A conference extension has been received received from the remote party. + * + * @param newSession An {@link ImsCallSessionImplBase} + * representing the extended IMS call session. + * @param profile The {@link ImsCallProfile} associated with the new IMS call session. */ - public void callSessionConferenceExtendReceived(ImsCallSession newSession, - ImsCallProfile profile) throws RemoteException { - mListener.callSessionConferenceExtendReceived(newSession != null - ? newSession.getSession() : null, profile); + public void callSessionConferenceExtendReceived(ImsCallSessionImplBase newSession, + ImsCallProfile profile) { + try { + mListener.callSessionConferenceExtendReceived(newSession != null + ? newSession.getServiceImpl() : null, profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the conference extension is received from the remote user. + * Compatibility method to interface with older versions of ImsService. + * See {@link #callSessionConferenceExtendReceived(ImsCallSessionImplBase, ImsCallProfile)}. * * @hide */ public void callSessionConferenceExtendReceived(IImsCallSession newSession, - ImsCallProfile profile) throws RemoteException { - mListener.callSessionConferenceExtendReceived(newSession, profile); + ImsCallProfile profile) { + try { + mListener.callSessionConferenceExtendReceived(newSession, profile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the invitation request of the participants is delivered to the conference + * The request to invite participants to the conference has been delivered to the conference * server. */ - public void callSessionInviteParticipantsRequestDelivered() throws RemoteException { - mListener.callSessionInviteParticipantsRequestDelivered(); + public void callSessionInviteParticipantsRequestDelivered() { + try { + mListener.callSessionInviteParticipantsRequestDelivered(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the invitation request of the participants has failed. + * The previous request to invite participants to the conference (see + * {@link #callSessionInviteParticipantsRequestDelivered()}) has failed. * - * @param reasonInfo detailed reason of the conference invitation failure + * @param reasonInfo {@link ImsReasonInfo} detailing the reason forthe conference invitation + * failure. */ public void callSessionInviteParticipantsRequestFailed(ImsReasonInfo reasonInfo) - throws RemoteException { - mListener.callSessionInviteParticipantsRequestFailed(reasonInfo); + { + try { + mListener.callSessionInviteParticipantsRequestFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the removal request of the participants is delivered to the conference + * The request to remove participants from the conference has been delivered to the conference * server. */ - public void callSessionRemoveParticipantsRequestDelivered() throws RemoteException { - mListener.callSessionRemoveParticipantsRequestDelivered(); + public void callSessionRemoveParticipantsRequestDelivered() { + try { + mListener.callSessionRemoveParticipantsRequestDelivered(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the removal request of the participants has failed. + * The previous request to remove participants from the conference (see + * {@link #callSessionRemoveParticipantsRequestDelivered()}) has failed. * - * @param reasonInfo detailed reason of the conference removal failure + * @param reasonInfo {@link ImsReasonInfo} detailing the reason for the conference removal + * failure. */ public void callSessionRemoveParticipantsRequestFailed(ImsReasonInfo reasonInfo) - throws RemoteException { - mListener.callSessionInviteParticipantsRequestFailed(reasonInfo); + { + try { + mListener.callSessionInviteParticipantsRequestFailed(reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Notifies the framework of the updated Call session conference state. + * The IMS call session's conference state has changed. * - * @param state the new {@link ImsConferenceState} associated with the conference. + * @param state The new {@link ImsConferenceState} associated with the conference. */ - public void callSessionConferenceStateUpdated(ImsConferenceState state) throws RemoteException { - mListener.callSessionConferenceStateUpdated(state); + public void callSessionConferenceStateUpdated(ImsConferenceState state) { + try { + mListener.callSessionConferenceStateUpdated(state); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Notifies the incoming USSD message. + * The IMS call session has received a Ussd message. + * + * @param mode The mode of the USSD message, either + * {@link ImsCallSessionImplBase#USSD_MODE_NOTIFY} or + * {@link ImsCallSessionImplBase#USSD_MODE_REQUEST}. + * @param ussdMessage The USSD message. */ public void callSessionUssdMessageReceived(int mode, String ussdMessage) - throws RemoteException { - mListener.callSessionUssdMessageReceived(mode, ussdMessage); + { + try { + mListener.callSessionUssdMessageReceived(mode, ussdMessage); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Notifies of a case where a {@link com.android.ims.internal.ImsCallSession} may potentially - * handover from one radio technology to another. + * An {@link ImsCallSession} may potentially handover from one radio + * technology to another. * - * @param srcAccessTech The source radio access technology; one of the access technology - * constants defined in {@link android.telephony.ServiceState}. For - * example - * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. + * @param srcAccessTech The source radio access technology; one of the access technology + * constants defined in {@link android.telephony.ServiceState}. For example + * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. * @param targetAccessTech The target radio access technology; one of the access technology - * constants defined in {@link android.telephony.ServiceState}. For - * example - * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. + * constants defined in {@link android.telephony.ServiceState}. For example + * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE}. */ public void callSessionMayHandover(int srcAccessTech, int targetAccessTech) - throws RemoteException { - mListener.callSessionMayHandover(srcAccessTech, targetAccessTech); + { + try { + mListener.callSessionMayHandover(srcAccessTech, targetAccessTech); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when session access technology changes. + * The IMS call session's access technology has changed. * - * @param srcAccessTech original access technology - * @param targetAccessTech new access technology - * @param reasonInfo + * @param srcAccessTech original access technology, defined in + * {@link android.telephony.ServiceState}. + * @param targetAccessTech new access technology, defined in + * {@link android.telephony.ServiceState}. + * @param reasonInfo The {@link ImsReasonInfo} associated with this handover. */ public void callSessionHandover(int srcAccessTech, int targetAccessTech, - ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionHandover(srcAccessTech, targetAccessTech, reasonInfo); + ImsReasonInfo reasonInfo) { + try { + mListener.callSessionHandover(srcAccessTech, targetAccessTech, reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when session access technology change fails. + * The IMS call session's access technology change has failed.. * * @param srcAccessTech original access technology * @param targetAccessTech new access technology - * @param reasonInfo handover failure reason + * @param reasonInfo An {@link ImsReasonInfo} detailing the reason for the failure. */ public void callSessionHandoverFailed(int srcAccessTech, int targetAccessTech, - ImsReasonInfo reasonInfo) throws RemoteException { - mListener.callSessionHandoverFailed(srcAccessTech, targetAccessTech, reasonInfo); + ImsReasonInfo reasonInfo) { + try { + mListener.callSessionHandoverFailed(srcAccessTech, targetAccessTech, reasonInfo); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the TTY mode is changed by the remote party. + * The TTY mode has been changed by the remote party. * * @param mode one of the following: - * {@link com.android.internal.telephony.Phone#TTY_MODE_OFF} - @@ -360,53 +525,79 @@ public class ImsCallSessionListener { * {@link com.android.internal.telephony.Phone#TTY_MODE_HCO} - * {@link com.android.internal.telephony.Phone#TTY_MODE_VCO} */ - public void callSessionTtyModeReceived(int mode) throws RemoteException { - mListener.callSessionTtyModeReceived(mode); + public void callSessionTtyModeReceived(int mode) { + try { + mListener.callSessionTtyModeReceived(mode); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the multiparty state is changed for this {@code ImsCallSession}. + * The multiparty state has been changed for this {@code ImsCallSession}. * - * @param isMultiParty {@code true} if the session became multiparty, - * {@code false} otherwise. + * @param isMultiParty {@code true} if the session became multiparty, {@code false} otherwise. */ - - public void callSessionMultipartyStateChanged(boolean isMultiParty) throws RemoteException { - mListener.callSessionMultipartyStateChanged(isMultiParty); + public void callSessionMultipartyStateChanged(boolean isMultiParty) { + try { + mListener.callSessionMultipartyStateChanged(isMultiParty); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Called when the supplementary service information is received for the current session. + * Supplementary service information has been received for the current IMS call session. + * + * @param suppSrvNotification The {@link ImsSuppServiceNotification} containing the change. */ public void callSessionSuppServiceReceived(ImsSuppServiceNotification suppSrvNotification) - throws RemoteException { - mListener.callSessionSuppServiceReceived(suppSrvNotification); + { + try { + mListener.callSessionSuppServiceReceived(suppSrvNotification); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Received RTT modify request from the remote party. + * An RTT modify request has been received from the remote party. * - * @param callProfile ImsCallProfile with updated attributes + * @param callProfile An {@link ImsCallProfile} with the updated attributes */ public void callSessionRttModifyRequestReceived(ImsCallProfile callProfile) - throws RemoteException { - mListener.callSessionRttModifyRequestReceived(callProfile); + { + try { + mListener.callSessionRttModifyRequestReceived(callProfile); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** + * An RTT modify response has been received. + * * @param status the received response for RTT modify request. */ - public void callSessionRttModifyResponseReceived(int status) throws RemoteException { - mListener.callSessionRttModifyResponseReceived(status); + public void callSessionRttModifyResponseReceived(int status) { + try { + mListener.callSessionRttModifyResponseReceived(status); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } /** - * Device received RTT message from Remote UE. + * An RTT message has been received from the remote party. * - * @param rttMessage RTT message received - */ - public void callSessionRttMessageReceived(String rttMessage) throws RemoteException { - mListener.callSessionRttMessageReceived(rttMessage); + * @param rttMessage The RTT message that has been received. + */ + public void callSessionRttMessageReceived(String rttMessage) { + try { + mListener.callSessionRttMessageReceived(rttMessage); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } } diff --git a/telephony/java/com/android/ims/ImsConferenceState.aidl b/telephony/java/android/telephony/ims/ImsConferenceState.aidl similarity index 95% rename from telephony/java/com/android/ims/ImsConferenceState.aidl rename to telephony/java/android/telephony/ims/ImsConferenceState.aidl index 2fc029f5790..e2b371c1440 100644 --- a/telephony/java/com/android/ims/ImsConferenceState.aidl +++ b/telephony/java/android/telephony/ims/ImsConferenceState.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsConferenceState; diff --git a/telephony/java/com/android/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java similarity index 95% rename from telephony/java/com/android/ims/ImsConferenceState.java rename to telephony/java/android/telephony/ims/ImsConferenceState.java index 0afde88b891..66d2f8d929d 100644 --- a/telephony/java/com/android/ims/ImsConferenceState.java +++ b/telephony/java/android/telephony/ims/ImsConferenceState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,16 +11,17 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import java.util.Set; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -32,7 +33,8 @@ import android.telecom.Connection; * * @hide */ -public class ImsConferenceState implements Parcelable { +@SystemApi +public final class ImsConferenceState implements Parcelable { /** * conference-info : user */ @@ -87,12 +89,13 @@ public class ImsConferenceState implements Parcelable { */ public static final String SIP_STATUS_CODE = "sipstatuscode"; - public HashMap mParticipants = new HashMap(); + public final HashMap mParticipants = new HashMap(); + /** @hide */ public ImsConferenceState() { } - public ImsConferenceState(Parcel in) { + private ImsConferenceState(Parcel in) { readFromParcel(in); } diff --git a/telephony/java/com/android/ims/ImsExternalCallState.aidl b/telephony/java/android/telephony/ims/ImsExternalCallState.aidl similarity index 95% rename from telephony/java/com/android/ims/ImsExternalCallState.aidl rename to telephony/java/android/telephony/ims/ImsExternalCallState.aidl index c208702a8c3..99d29356680 100644 --- a/telephony/java/com/android/ims/ImsExternalCallState.aidl +++ b/telephony/java/android/telephony/ims/ImsExternalCallState.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsExternalCallState; diff --git a/telephony/java/com/android/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java similarity index 93% rename from telephony/java/com/android/ims/ImsExternalCallState.java rename to telephony/java/android/telephony/ims/ImsExternalCallState.java index da2607356d6..e82c115cb4b 100644 --- a/telephony/java/com/android/ims/ImsExternalCallState.java +++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -32,7 +33,8 @@ import android.telephony.Rlog; * Parcelable object to handle MultiEndpoint Dialog Information * @hide */ -public class ImsExternalCallState implements Parcelable { +@SystemApi +public final class ImsExternalCallState implements Parcelable { private static final String TAG = "ImsExternalCallState"; @@ -50,9 +52,11 @@ public class ImsExternalCallState implements Parcelable { private int mCallType; private boolean mIsHeld; + /** @hide */ public ImsExternalCallState() { } + /** @hide */ public ImsExternalCallState(int callId, Uri address, boolean isPullable, int callState, int callType, boolean isCallheld) { mCallId = callId; @@ -64,6 +68,7 @@ public class ImsExternalCallState implements Parcelable { Rlog.d(TAG, "ImsExternalCallState = " + this); } + /** @hide */ public ImsExternalCallState(Parcel in) { mCallId = in.readInt(); ClassLoader classLoader = ImsExternalCallState.class.getClassLoader(); diff --git a/telephony/java/com/android/ims/ImsReasonInfo.aidl b/telephony/java/android/telephony/ims/ImsReasonInfo.aidl similarity index 95% rename from telephony/java/com/android/ims/ImsReasonInfo.aidl rename to telephony/java/android/telephony/ims/ImsReasonInfo.aidl index 17e6d3a729e..604b323d129 100644 --- a/telephony/java/com/android/ims/ImsReasonInfo.aidl +++ b/telephony/java/android/telephony/ims/ImsReasonInfo.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsReasonInfo; diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java similarity index 97% rename from telephony/java/com/android/ims/ImsReasonInfo.java rename to telephony/java/android/telephony/ims/ImsReasonInfo.java index 83d9bd94013..52a6ab902d9 100644 --- a/telephony/java/com/android/ims/ImsReasonInfo.java +++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -24,7 +25,8 @@ import android.os.Parcelable; * * @hide */ -public class ImsReasonInfo implements Parcelable { +@SystemApi +public final class ImsReasonInfo implements Parcelable { /** * Specific code of each types @@ -418,27 +420,36 @@ public class ImsReasonInfo implements Parcelable { // For main reason code - public int mCode; + /** @hide */ + public final int mCode; // For the extra code value; it depends on the code value. - public int mExtraCode; + /** @hide */ + public final int mExtraCode; // For the additional message of the reason info. - public String mExtraMessage; + /** @hide */ + public final String mExtraMessage; + + /** @hide */ public ImsReasonInfo() { mCode = CODE_UNSPECIFIED; mExtraCode = CODE_UNSPECIFIED; mExtraMessage = null; } - public ImsReasonInfo(Parcel in) { - readFromParcel(in); + private ImsReasonInfo(Parcel in) { + mCode = in.readInt(); + mExtraCode = in.readInt(); + mExtraMessage = in.readString(); } + /** @hide */ public ImsReasonInfo(int code, int extraCode) { mCode = code; mExtraCode = extraCode; mExtraMessage = null; } + /** @hide */ public ImsReasonInfo(int code, int extraCode, String extraMessage) { mCode = code; mExtraCode = extraCode; @@ -487,12 +498,6 @@ public class ImsReasonInfo implements Parcelable { out.writeString(mExtraMessage); } - private void readFromParcel(Parcel in) { - mCode = in.readInt(); - mExtraCode = in.readInt(); - mExtraMessage = in.readString(); - } - public static final Creator CREATOR = new Creator() { @Override public ImsReasonInfo createFromParcel(Parcel in) { diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java index 2f32bae88f0..2748cb5470b 100644 --- a/telephony/java/android/telephony/ims/ImsService.java +++ b/telephony/java/android/telephony/ims/ImsService.java @@ -51,9 +51,7 @@ import static android.Manifest.permission.MODIFY_PHONE_STATE; * ... * - * - * + * ... * * * @@ -65,12 +63,33 @@ import static android.Manifest.permission.MODIFY_PHONE_STATE; * 1) Defined as the default ImsService for the device in the device overlay using * "config_ims_package". * 2) Defined as a Carrier Provided ImsService in the Carrier Configuration using - * {@link CarrierConfigManager#KEY_CTONFIG_IMS_PACKAGE_OVERRIDE_STRING}. + * {@link CarrierConfigManager#KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING}. + * + * There are two ways to define to the platform which {@link ImsFeature}s this {@link ImsService} + * supports, dynamic or static definitions. + * + * In the static definition, the {@link ImsFeature}s that are supported are defined in the service + * definition of the AndroidManifest.xml file as metadata: + * + * * * The features that are currently supported in an ImsService are: * - RCS_FEATURE: This ImsService implements the RcsFeature class. * - MMTEL_FEATURE: This ImsService implements the MmTelFeature class. - * @hide + * - EMERGENCY_MMTEL_FEATURE: This ImsService supports Emergency Calling for MMTEL, must be + * declared along with the MMTEL_FEATURE. If this is not specified, the framework will use + * circuit switch for emergency calling. + * + * In the dynamic definition, the supported features are not specified in the service definition + * of the AndroidManifest. Instead, the framework binds to this service and calls + * {@link #querySupportedImsFeatures()}. The {@link ImsService} then returns an + * {@link ImsFeatureConfiguration}, which the framework uses to initialize the supported + * {@link ImsFeature}s. If at any time, the list of supported {@link ImsFeature}s changes, + * {@link #onUpdateSupportedImsFeatures(ImsFeatureConfiguration)} can be called to tell the + * framework of the changes. + * + * @hide */ @SystemApi public class ImsService extends Service { @@ -127,8 +146,7 @@ public class ImsService extends Service { } @Override - public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) - throws RemoteException { + public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c) { ImsService.this.removeImsFeature(slotId, featureType, c); } @@ -143,19 +161,18 @@ public class ImsService extends Service { } @Override - public void notifyImsFeatureReady(int slotId, int featureType) - throws RemoteException { + public void notifyImsFeatureReady(int slotId, int featureType) { ImsService.this.notifyImsFeatureReady(slotId, featureType); } @Override - public IImsConfig getConfig(int slotId) throws RemoteException { + public IImsConfig getConfig(int slotId) { ImsConfigImplBase c = ImsService.this.getConfig(slotId); return c != null ? c.getIImsConfig() : null; } @Override - public IImsRegistration getRegistration(int slotId) throws RemoteException { + public IImsRegistration getRegistration(int slotId) { ImsRegistrationImplBase r = ImsService.this.getRegistration(slotId); return r != null ? r.getBinder() : null; } @@ -277,12 +294,14 @@ public class ImsService extends Service { } /** - * When called, provide the {@link ImsFeatureConfiguration} that this ImsService currently - * supports. This will trigger the framework to set up the {@link ImsFeature}s that correspond - * to the {@link ImsFeature.FeatureType}s configured here. - * @return an {@link ImsFeatureConfiguration} containing Features this ImsService supports, - * defined in {@link ImsFeature.FeatureType}. - * @hide + * When called, provide the {@link ImsFeatureConfiguration} that this {@link ImsService} + * currently supports. This will trigger the framework to set up the {@link ImsFeature}s that + * correspond to the {@link ImsFeature}s configured here. + * + * Use {@link #onUpdateSupportedImsFeatures(ImsFeatureConfiguration)} to change the supported + * {@link ImsFeature}s. + * + * @return an {@link ImsFeatureConfiguration} containing Features this ImsService supports. */ public ImsFeatureConfiguration querySupportedImsFeatures() { // Return empty for base implementation @@ -291,9 +310,8 @@ public class ImsService extends Service { /** * Updates the framework with a new {@link ImsFeatureConfiguration} containing the updated - * features, defined in {@link ImsFeature.FeatureType} that this ImsService supports. This may - * trigger the framework to add/remove new ImsFeatures, depending on the configuration. - * @hide + * features, that this {@link ImsService} supports. This may trigger the framework to add/remove + * new ImsFeatures, depending on the configuration. */ public final void onUpdateSupportedImsFeatures(ImsFeatureConfiguration c) throws RemoteException { @@ -306,11 +324,12 @@ public class ImsService extends Service { /** * The ImsService has been bound and is ready for ImsFeature creation based on the Features that * the ImsService has registered for with the framework, either in the manifest or via + * {@link #querySupportedImsFeatures()}. + * * The ImsService should use this signal instead of onCreate/onBind or similar to perform * feature initialization because the framework may bind to this service multiple times to * query the ImsService's {@link ImsFeatureConfiguration} via * {@link #querySupportedImsFeatures()}before creating features. - * @hide */ public void readyForFeatureCreation() { } @@ -318,7 +337,6 @@ public class ImsService extends Service { /** * The framework has enabled IMS for the slot specified, the ImsService should register for IMS * and perform all appropriate initialization to bring up all ImsFeatures. - * @hide */ public void enableIms(int slotId) { } @@ -326,50 +344,50 @@ public class ImsService extends Service { /** * The framework has disabled IMS for the slot specified. The ImsService must deregister for IMS * and set capability status to false for all ImsFeatures. - * @hide */ public void disableIms(int slotId) { } /** - * When called, the framework is requesting that a new MmTelFeature is created for the specified - * slot. + * When called, the framework is requesting that a new {@link MmTelFeature} is created for the + * specified slot. * - * @param slotId The slot ID that the MMTel Feature is being created for. - * @return The newly created MmTelFeature associated with the slot or null if the feature is not - * supported. - * @hide + * @param slotId The slot ID that the MMTEL Feature is being created for. + * @return The newly created {@link MmTelFeature} associated with the slot or null if the + * feature is not supported. */ public MmTelFeature createMmTelFeature(int slotId) { return null; } /** - * When called, the framework is requesting that a new RcsFeature is created for the specified - * slot + * When called, the framework is requesting that a new {@link RcsFeature} is created for the + * specified slot. * * @param slotId The slot ID that the RCS Feature is being created for. - * @return The newly created RcsFeature associated with the slot or null if the feature is not - * supported. - * @hide + * @return The newly created {@link RcsFeature} associated with the slot or null if the feature + * is not supported. */ public RcsFeature createRcsFeature(int slotId) { return null; } /** + * Return the {@link ImsConfigImplBase} implementation associated with the provided slot. This + * will be used by the platform to get/set specific IMS related configurations. + * * @param slotId The slot that the IMS configuration is associated with. * @return ImsConfig implementation that is associated with the specified slot. - * @hide */ public ImsConfigImplBase getConfig(int slotId) { return new ImsConfigImplBase(); } /** + * Return the {@link ImsRegistrationImplBase} implementation associated with the provided slot. + * * @param slotId The slot that is associated with the IMS Registration. * @return the ImsRegistration implementation associated with the slot. - * @hide */ public ImsRegistrationImplBase getRegistration(int slotId) { return new ImsRegistrationImplBase(); diff --git a/telephony/java/com/android/ims/ImsSsData.aidl b/telephony/java/android/telephony/ims/ImsSsData.aidl similarity index 95% rename from telephony/java/com/android/ims/ImsSsData.aidl rename to telephony/java/android/telephony/ims/ImsSsData.aidl index 33f83067a4d..eff3a6b0acc 100644 --- a/telephony/java/com/android/ims/ImsSsData.aidl +++ b/telephony/java/android/telephony/ims/ImsSsData.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsSsData; diff --git a/telephony/java/com/android/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java similarity index 89% rename from telephony/java/com/android/ims/ImsSsData.java rename to telephony/java/android/telephony/ims/ImsSsData.java index 7336c133af9..1ddf1994f26 100644 --- a/telephony/java/com/android/ims/ImsSsData.java +++ b/telephony/java/android/telephony/ims/ImsSsData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,21 +11,21 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; -import java.util.ArrayList; - /** * Provided STK Call Control Suplementary Service information * * {@hide} */ -public class ImsSsData implements Parcelable { +@SystemApi +public final class ImsSsData implements Parcelable { //ServiceType public static final int SS_CFU = 0; @@ -68,30 +68,38 @@ public class ImsSsData implements Parcelable { public static final int SS_ALL_TELESERVICES_EXCEPT_SMS = 5; // Refer to ServiceType + /** @hide */ public int serviceType; // Refere to SSRequestType + /** @hide */ public int requestType; // Refer to TeleserviceType + /** @hide */ public int teleserviceType; // Service Class + /** @hide */ public int serviceClass; // Error information + /** @hide */ public int result; + /** @hide */ public int[] ssInfo; /* Valid for all supplementary services. This field will be empty for RequestType SS_INTERROGATION and ServiceType SS_CF_*, SS_INCOMING_BARRING_DN, SS_INCOMING_BARRING_ANONYMOUS.*/ + /** @hide */ public ImsCallForwardInfo[] cfInfo; /* Valid only for supplementary services ServiceType SS_CF_* and RequestType SS_INTERROGATION */ + /** @hide */ public ImsSsInfo[] imsSsInfo; /* Valid only for ServiceType SS_INCOMING_BARRING_DN and ServiceType SS_INCOMING_BARRING_ANONYMOUS */ public ImsSsData() {} - public ImsSsData(Parcel in) { + private ImsSsData(Parcel in) { readFromParcel(in); } @@ -133,20 +141,36 @@ public class ImsSsData implements Parcelable { return 0; } + /** + * Old method, kept for compatibility. See {@link #isTypeCf()} + * @hide + */ public boolean isTypeCF() { return (serviceType == SS_CFU || serviceType == SS_CF_BUSY || serviceType == SS_CF_NO_REPLY || serviceType == SS_CF_NOT_REACHABLE || serviceType == SS_CF_ALL || serviceType == SS_CF_ALL_CONDITIONAL); } + public boolean isTypeCf() { + return isTypeCF(); + } + public boolean isTypeUnConditional() { return (serviceType == SS_CFU || serviceType == SS_CF_ALL); } + /** + * Old method, kept for compatibility. See {@link #isTypeCf()} + * @hide + */ public boolean isTypeCW() { return (serviceType == SS_WAIT); } + public boolean isTypeCw() { + return isTypeCW(); + } + public boolean isTypeClip() { return (serviceType == SS_CLIP); } diff --git a/telephony/java/com/android/ims/ImsSsInfo.aidl b/telephony/java/android/telephony/ims/ImsSsInfo.aidl similarity index 95% rename from telephony/java/com/android/ims/ImsSsInfo.aidl rename to telephony/java/android/telephony/ims/ImsSsInfo.aidl index 0ac598b7021..66d49507c12 100644 --- a/telephony/java/com/android/ims/ImsSsInfo.aidl +++ b/telephony/java/android/telephony/ims/ImsSsInfo.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsSsInfo; diff --git a/telephony/java/com/android/ims/ImsSsInfo.java b/telephony/java/android/telephony/ims/ImsSsInfo.java similarity index 82% rename from telephony/java/com/android/ims/ImsSsInfo.java rename to telephony/java/android/telephony/ims/ImsSsInfo.java index 7acc3bfd45c..1d1292fb9f7 100644 --- a/telephony/java/com/android/ims/ImsSsInfo.java +++ b/telephony/java/android/telephony/ims/ImsSsInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -24,7 +25,8 @@ import android.os.Parcelable; * * @hide */ -public class ImsSsInfo implements Parcelable { +@SystemApi +public final class ImsSsInfo implements Parcelable { /** * For the status of service registration or activation/deactivation. */ @@ -33,13 +35,15 @@ public class ImsSsInfo implements Parcelable { public static final int ENABLED = 1; // 0: disabled, 1: enabled + /** @hide */ public int mStatus; + /** @hide */ public String mIcbNum; public ImsSsInfo() { } - public ImsSsInfo(Parcel in) { + private ImsSsInfo(Parcel in) { readFromParcel(in); } @@ -76,4 +80,12 @@ public class ImsSsInfo implements Parcelable { return new ImsSsInfo[size]; } }; + + public int getStatus() { + return mStatus; + } + + public String getIcbNum() { + return mIcbNum; + } } diff --git a/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.aidl similarity index 95% rename from telephony/java/com/android/ims/ImsStreamMediaProfile.aidl rename to telephony/java/android/telephony/ims/ImsStreamMediaProfile.aidl index d648a3569c1..ee321aec29d 100644 --- a/telephony/java/com/android/ims/ImsStreamMediaProfile.aidl +++ b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.aidl @@ -14,6 +14,6 @@ * limitations under the License. */ -package com.android.ims; +package android.telephony.ims; parcelable ImsStreamMediaProfile; diff --git a/telephony/java/com/android/ims/ImsStreamMediaProfile.java b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java similarity index 88% rename from telephony/java/com/android/ims/ImsStreamMediaProfile.java rename to telephony/java/android/telephony/ims/ImsStreamMediaProfile.java index cfe37b52434..243352bdd18 100644 --- a/telephony/java/com/android/ims/ImsStreamMediaProfile.java +++ b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,11 +11,12 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -25,7 +26,8 @@ import android.os.Parcelable; * * @hide */ -public class ImsStreamMediaProfile implements Parcelable { +@SystemApi +public final class ImsStreamMediaProfile implements Parcelable { private static final String TAG = "ImsStreamMediaProfile"; /** @@ -79,18 +81,25 @@ public class ImsStreamMediaProfile implements Parcelable { public static final int RTT_MODE_FULL = 1; // Audio related information + /** @hide */ public int mAudioQuality; + /** @hide */ public int mAudioDirection; // Video related information + /** @hide */ public int mVideoQuality; + /** @hide */ public int mVideoDirection; // Rtt related information + /** @hide */ public int mRttMode; + /** @hide */ public ImsStreamMediaProfile(Parcel in) { readFromParcel(in); } + /** @hide */ public ImsStreamMediaProfile() { mAudioQuality = AUDIO_QUALITY_NONE; mAudioDirection = DIRECTION_SEND_RECEIVE; @@ -99,6 +108,7 @@ public class ImsStreamMediaProfile implements Parcelable { mRttMode = RTT_MODE_DISABLED; } + /** @hide */ public ImsStreamMediaProfile(int audioQuality, int audioDirection, int videoQuality, int videoDirection) { mAudioQuality = audioQuality; @@ -107,6 +117,7 @@ public class ImsStreamMediaProfile implements Parcelable { mVideoDirection = videoDirection; } + /** @hide */ public ImsStreamMediaProfile(int rttMode) { mRttMode = rttMode; } @@ -178,4 +189,23 @@ public class ImsStreamMediaProfile implements Parcelable { mRttMode = rttMode; } + public int getAudioQuality() { + return mAudioQuality; + } + + public int getAudioDirection() { + return mAudioDirection; + } + + public int getVideoQuality() { + return mVideoQuality; + } + + public int getVideoDirection() { + return mVideoDirection; + } + + public int getRttMode() { + return mRttMode; + } } diff --git a/telephony/java/com/android/ims/ImsSuppServiceNotification.aidl b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.aidl similarity index 95% rename from telephony/java/com/android/ims/ImsSuppServiceNotification.aidl rename to telephony/java/android/telephony/ims/ImsSuppServiceNotification.aidl index 6b4479f467c..0552780c7dd 100644 --- a/telephony/java/com/android/ims/ImsSuppServiceNotification.aidl +++ b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.aidl @@ -15,6 +15,6 @@ */ -package com.android.ims; +package android.telephony.ims; parcelable ImsSuppServiceNotification; diff --git a/telephony/java/com/android/ims/ImsSuppServiceNotification.java b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java similarity index 84% rename from telephony/java/com/android/ims/ImsSuppServiceNotification.java rename to telephony/java/android/telephony/ims/ImsSuppServiceNotification.java index faf749972b9..e6f6f1b021f 100644 --- a/telephony/java/com/android/ims/ImsSuppServiceNotification.java +++ b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 The Android Open Source Project + * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,12 +11,13 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License */ -package com.android.ims; +package android.telephony.ims; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; @@ -28,27 +29,31 @@ import java.util.Arrays; * * @hide */ -public class ImsSuppServiceNotification implements Parcelable { +@SystemApi +public final class ImsSuppServiceNotification implements Parcelable { private static final String TAG = "ImsSuppServiceNotification"; /** Type of notification: 0 = MO; 1 = MT */ - public int notificationType; + public final int notificationType; /** TS 27.007 7.17 "code1" or "code2" */ - public int code; + public final int code; /** TS 27.007 7.17 "index" - Not used currently*/ - public int index; + public final int index; /** TS 27.007 7.17 "type" (MT only) - Not used currently */ - public int type; + public final int type; /** TS 27.007 7.17 "number" (MT only) */ - public String number; + public final String number; /** List of forwarded numbers, if any */ - public String[] history; - - public ImsSuppServiceNotification() { - } + public final String[] history; + /** @hide */ public ImsSuppServiceNotification(Parcel in) { - readFromParcel(in); + notificationType = in.readInt(); + code = in.readInt(); + index = in.readInt(); + type = in.readInt(); + number = in.readString(); + history = in.createStringArray(); } @Override @@ -77,15 +82,6 @@ public class ImsSuppServiceNotification implements Parcelable { out.writeStringArray(history); } - private void readFromParcel(Parcel in) { - notificationType = in.readInt(); - code = in.readInt(); - index = in.readInt(); - type = in.readInt(); - number = in.readString(); - history = in.createStringArray(); - } - public static final Creator CREATOR = new Creator() { @Override diff --git a/telephony/java/android/telephony/ims/ImsUtListener.java b/telephony/java/android/telephony/ims/ImsUtListener.java new file mode 100644 index 00000000000..d50a0f738b2 --- /dev/null +++ b/telephony/java/android/telephony/ims/ImsUtListener.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telephony.ims; + +import android.annotation.SystemApi; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +import com.android.ims.internal.IImsUtListener; + +/** + * Base implementation of the IMS UT listener interface, which implements stubs. + * Override these methods to implement functionality. + * @hide + */ +// DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you +// will break other implementations of ImsUt maintained by other ImsServices. +@SystemApi +public class ImsUtListener { + private IImsUtListener mServiceInterface; + private static final String LOG_TAG = "ImsUtListener"; + + public void onUtConfigurationUpdated(int id) { + try { + mServiceInterface.utConfigurationUpdated(null, id); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationUpdated: remote exception"); + } + } + + public void onUtConfigurationUpdateFailed(int id, ImsReasonInfo error) { + try { + mServiceInterface.utConfigurationUpdateFailed(null, id, error); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationUpdateFailed: remote exception"); + } + } + + public void onUtConfigurationQueried(int id, Bundle ssInfo) { + try { + mServiceInterface.utConfigurationQueried(null, id, ssInfo); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationQueried: remote exception"); + } + } + + public void onUtConfigurationQueryFailed(int id, ImsReasonInfo error) { + try { + mServiceInterface.utConfigurationQueryFailed(null, id, error); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationQueryFailed: remote exception"); + } + } + + public void onUtConfigurationCallBarringQueried(int id, ImsSsInfo[] cbInfo) { + try { + mServiceInterface.utConfigurationCallBarringQueried(null, id, cbInfo); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationCallBarringQueried: remote exception"); + } + } + + public void onUtConfigurationCallForwardQueried(int id, ImsCallForwardInfo[] cfInfo) { + try { + mServiceInterface.utConfigurationCallForwardQueried(null, id, cfInfo); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationCallForwardQueried: remote exception"); + } + } + + public void onUtConfigurationCallWaitingQueried(int id, ImsSsInfo[] cwInfo) { + try { + mServiceInterface.utConfigurationCallWaitingQueried(null, id, cwInfo); + } catch (RemoteException e) { + Log.w(LOG_TAG, "utConfigurationCallWaitingQueried: remote exception"); + } + } + + public void onSupplementaryServiceIndication(ImsSsData ssData) { + try { + mServiceInterface.onSupplementaryServiceIndication(ssData); + } catch (RemoteException e) { + Log.w(LOG_TAG, "onSupplementaryServiceIndication: remote exception"); + } + } + + /** + * @hide + */ + public ImsUtListener(IImsUtListener serviceInterface) { + mServiceInterface = serviceInterface; + } +} diff --git a/telephony/java/com/android/ims/internal/ImsVideoCallProvider.java b/telephony/java/android/telephony/ims/ImsVideoCallProvider.java similarity index 97% rename from telephony/java/com/android/ims/internal/ImsVideoCallProvider.java rename to telephony/java/android/telephony/ims/ImsVideoCallProvider.java index 432dc390573..b4f60b952a0 100644 --- a/telephony/java/com/android/ims/internal/ImsVideoCallProvider.java +++ b/telephony/java/android/telephony/ims/ImsVideoCallProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open 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,8 +14,9 @@ * limitations under the License */ -package com.android.ims.internal; +package android.telephony.ims; +import android.annotation.SystemApi; import android.net.Uri; import android.os.Handler; import android.os.Looper; @@ -26,11 +27,14 @@ import android.telecom.VideoProfile; import android.telecom.VideoProfile.CameraCapabilities; import android.view.Surface; +import com.android.ims.internal.IImsVideoCallCallback; +import com.android.ims.internal.IImsVideoCallProvider; import com.android.internal.os.SomeArgs; /** * @hide */ +@SystemApi public abstract class ImsVideoCallProvider { private static final int MSG_SET_CALLBACK = 1; private static final int MSG_SET_CAMERA = 2; @@ -173,6 +177,7 @@ public abstract class ImsVideoCallProvider { /** * Returns binder object which can be used across IPC methods. + * @hide */ public final IImsVideoCallProvider getInterface() { return mBinder; diff --git a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl index e6a18d027d6..f25b4b14860 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsCallSessionListener.aidl @@ -16,12 +16,12 @@ package android.telephony.ims.aidl; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsConferenceState; +import android.telephony.ims.ImsStreamMediaProfile; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsConferenceState; import com.android.ims.internal.IImsCallSession; -import com.android.ims.ImsSuppServiceNotification; +import android.telephony.ims.ImsSuppServiceNotification; /** * A listener type for receiving notification on IMS call session events. diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl index f9b15c0b161..b9a6b3c38a9 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl @@ -22,7 +22,7 @@ import android.telephony.ims.aidl.IImsSmsListener; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.feature.CapabilityChangeRequest; -import com.android.ims.ImsCallProfile; +import android.telephony.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMultiEndpoint; @@ -57,4 +57,5 @@ interface IImsMmTelFeature { oneway void acknowledgeSms(int token, int messageRef, int result); oneway void acknowledgeSmsReport(int token, int messageRef, int result); String getSmsFormat(); + oneway void onSmsReady(); } diff --git a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl index a745a3819f4..4f37caa3368 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl @@ -20,7 +20,7 @@ package android.telephony.ims.aidl; import android.net.Uri; import android.telephony.ims.stub.ImsFeatureConfiguration; -import com.android.ims.ImsReasonInfo; +import android.telephony.ims.ImsReasonInfo; /** * See ImsRegistrationImplBase.Callback for more information. diff --git a/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java index f5c5857b7ab..7a1b1b218fa 100644 --- a/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java +++ b/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java @@ -20,7 +20,7 @@ import android.app.PendingIntent; import android.os.Message; import android.os.RemoteException; -import com.android.ims.ImsCallProfile; +import android.telephony.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsConfig; import com.android.ims.internal.IImsEcbm; @@ -28,7 +28,7 @@ import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsRegistrationListener; import com.android.ims.internal.IImsUt; -import com.android.ims.internal.ImsCallSession; +import android.telephony.ims.ImsCallSession; /** * Base implementation for MMTel. diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java index 6725d2937a3..48a5a75ad03 100644 --- a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java +++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java @@ -19,14 +19,14 @@ package android.telephony.ims.compat.stub; import android.os.RemoteException; import android.telephony.ims.ImsCallSessionListener; -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsConferenceState; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.ImsSuppServiceNotification; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsConferenceState; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsStreamMediaProfile; +import android.telephony.ims.ImsSuppServiceNotification; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsCallSessionListener; -import com.android.ims.internal.ImsCallSession; +import android.telephony.ims.ImsCallSession; /** * Compat implementation of ImsCallSessionImplBase for older implementations. diff --git a/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsUtListenerImplBase.java similarity index 91% rename from telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java rename to telephony/java/android/telephony/ims/compat/stub/ImsUtListenerImplBase.java index daa74c8f6f8..b2aa08015d1 100644 --- a/telephony/java/android/telephony/ims/stub/ImsUtListenerImplBase.java +++ b/telephony/java/android/telephony/ims/compat/stub/ImsUtListenerImplBase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * Copyright (C) 2018 The Android Open 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,15 +14,15 @@ * limitations under the License */ -package android.telephony.ims.stub; +package android.telephony.ims.compat.stub; import android.os.Bundle; import android.os.RemoteException; -import com.android.ims.ImsCallForwardInfo; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsSsData; -import com.android.ims.ImsSsInfo; +import android.telephony.ims.ImsCallForwardInfo; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsSsData; +import android.telephony.ims.ImsSsInfo; import com.android.ims.internal.IImsUt; import com.android.ims.internal.IImsUtListener; diff --git a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java index efb6ffe9904..7c793a5c18a 100644 --- a/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java +++ b/telephony/java/android/telephony/ims/feature/CapabilityChangeRequest.java @@ -16,6 +16,7 @@ package android.telephony.ims.feature; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.telephony.ims.stub.ImsRegistrationImplBase; @@ -30,17 +31,32 @@ import java.util.Set; * the request. * {@hide} */ -public class CapabilityChangeRequest implements Parcelable { +@SystemApi +public final class CapabilityChangeRequest implements Parcelable { + /** + * Contains a feature capability, defined as + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT}, or + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS}, + * along with an associated technology, defined as + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} or + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + */ public static class CapabilityPair { private final int mCapability; private final int radioTech; - public CapabilityPair(int capability, int radioTech) { + public CapabilityPair(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int radioTech) { this.mCapability = capability; this.radioTech = radioTech; } + /** + * @hide + */ @Override public boolean equals(Object o) { if (this == o) return true; @@ -52,6 +68,9 @@ public class CapabilityChangeRequest implements Parcelable { return getRadioTech() == that.getRadioTech(); } + /** + * @hide + */ @Override public int hashCode() { int result = getCapability(); @@ -59,10 +78,22 @@ public class CapabilityChangeRequest implements Parcelable { return result; } + /** + * @return The stored capability, defined as + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT}, or + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS} + */ public @MmTelFeature.MmTelCapabilities.MmTelCapability int getCapability() { return mCapability; } + /** + * @return the stored radio technology, defined as + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} or + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + */ public @ImsRegistrationImplBase.ImsRegistrationTech int getRadioTech() { return radioTech; } @@ -73,6 +104,7 @@ public class CapabilityChangeRequest implements Parcelable { // Pair contains private final Set mCapabilitiesToDisable; + /** @hide */ public CapabilityChangeRequest() { mCapabilitiesToEnable = new ArraySet<>(); mCapabilitiesToDisable = new ArraySet<>(); @@ -130,6 +162,9 @@ public class CapabilityChangeRequest implements Parcelable { } } + /** + * @hide + */ protected CapabilityChangeRequest(Parcel in) { int enableSize = in.readInt(); mCapabilitiesToEnable = new ArraySet<>(enableSize); @@ -177,6 +212,9 @@ public class CapabilityChangeRequest implements Parcelable { } } + /** + * @hide + */ @Override public boolean equals(Object o) { if (this == o) return true; @@ -189,6 +227,9 @@ public class CapabilityChangeRequest implements Parcelable { return mCapabilitiesToDisable.equals(that.mCapabilitiesToDisable); } + /** + * @hide + */ @Override public int hashCode() { int result = mCapabilitiesToEnable.hashCode(); diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java index 2cc0a8a2275..a23ce6316a9 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -18,6 +18,7 @@ package android.telephony.ims.feature; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.content.Context; import android.content.Intent; import android.os.IInterface; @@ -25,6 +26,7 @@ import android.os.RemoteCallbackList; import android.os.RemoteException; import android.telephony.SubscriptionManager; import android.telephony.ims.aidl.IImsCapabilityCallback; +import android.telephony.ims.stub.ImsRegistrationImplBase; import android.util.Log; import com.android.ims.internal.IImsFeatureStatusCallback; @@ -38,10 +40,12 @@ import java.util.Set; import java.util.WeakHashMap; /** - * Base class for all IMS features that are supported by the framework. + * Base class for all IMS features that are supported by the framework. Use a concrete subclass + * of {@link ImsFeature}, such as {@link MmTelFeature} or {@link RcsFeature}. * * @hide */ +@SystemApi public abstract class ImsFeature { private static final String LOG_TAG = "ImsFeature"; @@ -75,17 +79,35 @@ public abstract class ImsFeature { */ public static final String EXTRA_PHONE_ID = "android:phone_id"; - // Invalid feature value + /** + * Invalid feature value\ + * @hide + */ public static final int FEATURE_INVALID = -1; // ImsFeatures that are defined in the Manifests. Ensure that these values match the previously // defined values in ImsServiceClass for compatibility purposes. + /** + * This feature supports emergency calling over MMTEL. + */ public static final int FEATURE_EMERGENCY_MMTEL = 0; + /** + * This feature supports the MMTEL feature. + */ public static final int FEATURE_MMTEL = 1; + /** + * This feature supports the RCS feature. + */ public static final int FEATURE_RCS = 2; - // Total number of features defined + /** + * Total number of features defined + * @hide + */ public static final int FEATURE_MAX = 3; - // Integer values defining IMS features that are supported in ImsFeature. + /** + * Integer values defining IMS features that are supported in ImsFeature. + * @hide + */ @IntDef(flag = true, value = { FEATURE_EMERGENCY_MMTEL, @@ -95,7 +117,10 @@ public abstract class ImsFeature { @Retention(RetentionPolicy.SOURCE) public @interface FeatureType {} - // Integer values defining the state of the ImsFeature at any time. + /** + * Integer values defining the state of the ImsFeature at any time. + * @hide + */ @IntDef(flag = true, value = { STATE_UNAVAILABLE, @@ -105,12 +130,24 @@ public abstract class ImsFeature { @Retention(RetentionPolicy.SOURCE) public @interface ImsState {} + /** + * This {@link ImsFeature}'s state is unavailable and should not be communicated with. + */ public static final int STATE_UNAVAILABLE = 0; + /** + * This {@link ImsFeature} state is initializing and should not be communicated with. + */ public static final int STATE_INITIALIZING = 1; + /** + * This {@link ImsFeature} is ready for communication. + */ public static final int STATE_READY = 2; - // Integer values defining the result codes that should be returned from - // {@link changeEnabledCapabilities} when the framework tries to set a feature's capability. + /** + * Integer values defining the result codes that should be returned from + * {@link #changeEnabledCapabilities} when the framework tries to set a feature's capability. + * @hide + */ @IntDef(flag = true, value = { CAPABILITY_ERROR_GENERIC, @@ -119,7 +156,13 @@ public abstract class ImsFeature { @Retention(RetentionPolicy.SOURCE) public @interface ImsCapabilityError {} + /** + * The capability was unable to be changed. + */ public static final int CAPABILITY_ERROR_GENERIC = -1; + /** + * The capability was able to be changed. + */ public static final int CAPABILITY_SUCCESS = 0; @@ -129,6 +172,8 @@ public abstract class ImsFeature { * configurations, via {@link #onQueryCapabilityConfiguration}, as well as to receive error * callbacks when the ImsService can not change the capability as requested, via * {@link #onChangeCapabilityConfigurationError}. + * + * @hide */ public static class CapabilityCallback extends IImsCapabilityCallback.Stub { @@ -159,7 +204,7 @@ public abstract class ImsFeature { */ @Override public void onChangeCapabilityConfigurationError(int capability, int radioTech, - int reason) { + @ImsCapabilityError int reason) { } /** @@ -180,6 +225,7 @@ public abstract class ImsFeature { protected static class CapabilityCallbackProxy { private final IImsCapabilityCallback mCallback; + /** @hide */ public CapabilityCallbackProxy(IImsCapabilityCallback c) { mCallback = c; } @@ -188,9 +234,16 @@ public abstract class ImsFeature { * This method notifies the provided framework callback that the request to change the * indicated capability has failed and has not changed. * - * @param capability The Capability that will be notified to the framework. - * @param radioTech The radio tech that this capability failed for. - * @param reason The reason this capability was unable to be changed. + * @param capability The Capability that will be notified to the framework, defined as + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT}, or + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS}. + * @param radioTech The radio tech that this capability failed for, defined as + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE} or + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN}. + * @param reason The reason this capability was unable to be changed, defined as + * {@link #CAPABILITY_ERROR_GENERIC} or {@link #CAPABILITY_SUCCESS}. */ public void onChangeCapabilityConfigurationError(int capability, int radioTech, @ImsCapabilityError int reason) { @@ -203,22 +256,11 @@ public abstract class ImsFeature { Log.e(LOG_TAG, "onChangeCapabilityConfigurationError called on dead binder."); } } - - public void onQueryCapabilityConfiguration(int capability, int radioTech, - boolean isEnabled) { - if (mCallback == null) { - return; - } - try { - mCallback.onQueryCapabilityConfiguration(capability, radioTech, isEnabled); - } catch (RemoteException e) { - Log.e(LOG_TAG, "onQueryCapabilityConfiguration called on dead binder."); - } - } } /** * Contains the capabilities defined and supported by an ImsFeature in the form of a bit mask. + * @hide */ public static class Capabilities { protected int mCapabilities = 0; @@ -253,6 +295,9 @@ public abstract class ImsFeature { return (mCapabilities & capabilities) == capabilities; } + /** + * @return a deep copy of the Capabilites. + */ public Capabilities copy() { return new Capabilities(mCapabilities); } @@ -264,6 +309,9 @@ public abstract class ImsFeature { return mCapabilities; } + /** + * @hide + */ @Override public boolean equals(Object o) { if (this == o) return true; @@ -274,11 +322,17 @@ public abstract class ImsFeature { return mCapabilities == that.mCapabilities; } + /** + * @hide + */ @Override public int hashCode() { return mCapabilities; } + /** + * @hide + */ @Override public String toString() { return "Capabilities: " + Integer.toBinaryString(mCapabilities); @@ -289,24 +343,40 @@ public abstract class ImsFeature { new WeakHashMap()); private @ImsState int mState = STATE_UNAVAILABLE; private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; + /** + * @hide + */ protected Context mContext; private final Object mLock = new Object(); private final RemoteCallbackList mCapabilityCallbacks = new RemoteCallbackList<>(); private Capabilities mCapabilityStatus = new Capabilities(); + /** + * @hide + */ public final void initialize(Context context, int slotId) { mContext = context; mSlotId = slotId; } + /** + * @return The current state of the feature, defined as {@link #STATE_UNAVAILABLE}, + * {@link #STATE_INITIALIZING}, or {@link #STATE_READY}. + */ public final int getFeatureState() { synchronized (mLock) { return mState; } } - protected final void setFeatureState(@ImsState int state) { + /** + * Set the state of the ImsFeature. The state is used as a signal to the framework to start or + * stop communication, depending on the state sent. + * @param state The ImsFeature's state, defined as {@link #STATE_UNAVAILABLE}, + * {@link #STATE_INITIALIZING}, or {@link #STATE_READY}. + */ + public final void setFeatureState(@ImsState int state) { synchronized (mLock) { if (mState != state) { mState = state; @@ -315,7 +385,10 @@ public abstract class ImsFeature { } } - // Not final for testing, but shouldn't be extended! + /** + * Not final for testing, but shouldn't be extended! + * @hide + */ @VisibleForTesting public void addImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { try { @@ -330,8 +403,11 @@ public abstract class ImsFeature { } } + /** + * Not final for testing, but shouldn't be extended! + * @hide + */ @VisibleForTesting - // Not final for testing, but should not be extended! public void removeImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) { synchronized (mLock) { mStatusCallbacks.remove(c); @@ -382,16 +458,23 @@ public abstract class ImsFeature { mContext.sendBroadcast(intent); } + /** + * @hide + */ public final void addCapabilityCallback(IImsCapabilityCallback c) { mCapabilityCallbacks.register(c); } + /** + * @hide + */ public final void removeCapabilityCallback(IImsCapabilityCallback c) { mCapabilityCallbacks.unregister(c); } /** * @return the cached capabilities status for this feature. + * @hide */ @VisibleForTesting public Capabilities queryCapabilityStatus() { @@ -400,7 +483,10 @@ public abstract class ImsFeature { } } - // Called internally to request the change of enabled capabilities. + /** + * Called internally to request the change of enabled capabilities. + * @hide + */ @VisibleForTesting public final void requestChangeEnabledCapabilities(CapabilityChangeRequest request, IImsCapabilityCallback c) throws RemoteException { @@ -415,6 +501,8 @@ public abstract class ImsFeature { * Called by the ImsFeature when the capabilities status has changed. * * @param c A {@link Capabilities} containing the new Capabilities status. + * + * @hide */ protected final void notifyCapabilitiesStatusChanged(Capabilities c) { synchronized (mLock) { @@ -468,6 +556,7 @@ public abstract class ImsFeature { /** * @return Binder instance that the framework will use to communicate with this feature. + * @hide */ protected abstract IInterface getBinder(); } diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java index 83214b32fc3..2baf076d391 100644 --- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -17,28 +17,30 @@ package android.telephony.ims.feature; import android.annotation.IntDef; +import android.annotation.SystemApi; import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.RemoteException; import android.telecom.TelecomManager; +import android.telephony.ims.stub.ImsRegistrationImplBase; +import android.telephony.ims.stub.ImsCallSessionImplBase; +import android.telephony.ims.stub.ImsSmsImplBase; import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsMmTelFeature; import android.telephony.ims.aidl.IImsMmTelListener; import android.telephony.ims.aidl.IImsSmsListener; -import android.telephony.ims.stub.ImsRegistrationImplBase; -import android.telephony.ims.stub.ImsSmsImplBase; import android.telephony.ims.stub.ImsEcbmImplBase; import android.telephony.ims.stub.ImsMultiEndpointImplBase; import android.telephony.ims.stub.ImsUtImplBase; import android.util.Log; -import com.android.ims.ImsCallProfile; +import android.telephony.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsUt; -import com.android.ims.internal.ImsCallSession; +import android.telephony.ims.ImsCallSession; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; @@ -51,7 +53,7 @@ import java.lang.annotation.RetentionPolicy; * service supports. * @hide */ - +@SystemApi public class MmTelFeature extends ImsFeature { private static final String LOG_TAG = "MmTelFeature"; @@ -84,8 +86,8 @@ public class MmTelFeature extends ImsFeature { @Override public IImsCallSession createCallSession(ImsCallProfile profile) throws RemoteException { synchronized (mLock) { - ImsCallSession s = MmTelFeature.this.createCallSession(profile); - return s != null ? s.getSession() : null; + ImsCallSessionImplBase s = MmTelFeature.this.createCallSession(profile); + return s != null ? s.getServiceImpl() : null; } } @@ -99,14 +101,15 @@ public class MmTelFeature extends ImsFeature { @Override public IImsUt getUtInterface() throws RemoteException { synchronized (mLock) { - return MmTelFeature.this.getUt(); + return MmTelFeature.this.getUt().getInterface(); } } @Override public IImsEcbm getEcbmInterface() throws RemoteException { synchronized (mLock) { - return MmTelFeature.this.getEcbm(); + ImsEcbmImplBase ecbm = MmTelFeature.this.getEcbm(); + return ecbm != null ? ecbm.getImsEcbm() : null; } } @@ -120,7 +123,8 @@ public class MmTelFeature extends ImsFeature { @Override public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { synchronized (mLock) { - return MmTelFeature.this.getMultiEndpoint(); + ImsMultiEndpointImplBase multiEndPoint = MmTelFeature.this.getMultiEndpoint(); + return multiEndPoint != null ? multiEndPoint.getIImsMultiEndpoint() : null; } } @@ -184,11 +188,22 @@ public class MmTelFeature extends ImsFeature { return MmTelFeature.this.getSmsFormat(); } } + + @Override + public void onSmsReady() { + synchronized (mLock) { + MmTelFeature.this.onSmsReady(); + } + } }; /** * Contains the capabilities defined and supported by a MmTelFeature in the form of a Bitmask. - * The capabilities that are used in MmTelFeature are defined by {@link MmTelCapability}. + * The capabilities that are used in MmTelFeature are defined as + * {@link MmTelCapabilities#CAPABILITY_TYPE_VOICE}, + * {@link MmTelCapabilities#CAPABILITY_TYPE_VIDEO}, + * {@link MmTelCapabilities#CAPABILITY_TYPE_UT}, and + * {@link MmTelCapabilities#CAPABILITY_TYPE_SMS}. * * The capabilities of this MmTelFeature will be set by the framework and can be queried with * {@link #queryCapabilityStatus()}. @@ -199,6 +214,9 @@ public class MmTelFeature extends ImsFeature { */ public static class MmTelCapabilities extends Capabilities { + /** + * @hide + */ @VisibleForTesting public MmTelCapabilities() { super(); @@ -275,6 +293,7 @@ public class MmTelFeature extends ImsFeature { /** * Listener that the framework implements for communication from the MmTelFeature. + * @hide */ public static class Listener extends IImsMmTelListener.Stub { @@ -375,24 +394,27 @@ public class MmTelFeature extends ImsFeature { * support the capability that is enabled. A capability that is disabled by the framework (via * {@link #changeEnabledCapabilities}) should also show the status as disabled. */ - protected final void notifyCapabilitiesStatusChanged(MmTelCapabilities c) { + public final void notifyCapabilitiesStatusChanged(MmTelCapabilities c) { super.notifyCapabilitiesStatusChanged(c); } /** * Notify the framework of an incoming call. - * @param c The {@link ImsCallSession} of the new incoming call. + * @param c The {@link ImsCallSessionImplBase} of the new incoming call. * - * @throws RemoteException if the connection to the framework is not available. If this happens, - * the call should be no longer considered active and should be cleaned up. + * @throws RuntimeException if the connection to the framework is not available. If this + * happens, the call should be no longer considered active and should be cleaned up. * */ - protected final void notifyIncomingCall(ImsCallSession c, Bundle extras) - throws RemoteException { + public final void notifyIncomingCall(ImsCallSessionImplBase c, Bundle extras) { synchronized (mLock) { if (mListener == null) { throw new IllegalStateException("Session is not available."); } - mListener.onIncomingCall(c.getSession(), extras); + try { + mListener.onIncomingCall(c.getServiceImpl(), extras); + } catch (RemoteException e) { + throw new RuntimeException(e); + } } } @@ -458,7 +480,7 @@ public class MmTelFeature extends ImsFeature { * * @param profile a call profile to make the call */ - public ImsCallSession createCallSession(ImsCallProfile profile) { + public ImsCallSessionImplBase createCallSession(ImsCallProfile profile) { // Base Implementation - Should be overridden return null; } @@ -477,7 +499,8 @@ public class MmTelFeature extends ImsFeature { } /** - * @return The Ut interface for the supplementary service configuration. + * @return The {@link ImsUtImplBase} Ut interface implementation for the supplementary service + * configuration. */ public ImsUtImplBase getUt() { // Base Implementation - Should be overridden @@ -485,7 +508,8 @@ public class MmTelFeature extends ImsFeature { } /** - * @return The Emergency call-back mode interface for emergency VoLTE calls that support it. + * @return The {@link ImsEcbmImplBase} Emergency call-back mode interface for emergency VoLTE + * calls that support it. */ public ImsEcbmImplBase getEcbm() { // Base Implementation - Should be overridden @@ -493,7 +517,8 @@ public class MmTelFeature extends ImsFeature { } /** - * @return The Emergency call-back mode interface for emergency VoLTE calls that support it. + * @return The {@link ImsMultiEndpointImplBase} implementation for implementing Dialog event + * package processing for multi-endpoint. */ public ImsMultiEndpointImplBase getMultiEndpoint() { // Base Implementation - Should be overridden @@ -532,6 +557,10 @@ public class MmTelFeature extends ImsFeature { getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); } + private void onSmsReady() { + getSmsImplementation().onReady(); + } + /** * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default * non-functional implementation is returned. @@ -539,7 +568,7 @@ public class MmTelFeature extends ImsFeature { * @return an instance of {@link ImsSmsImplBase} which should be implemented by the IMS * Provider. */ - protected ImsSmsImplBase getSmsImplementation() { + public ImsSmsImplBase getSmsImplementation() { return new ImsSmsImplBase(); } diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java index 3b6f7e0f1a8..a637e16d0a4 100644 --- a/telephony/java/android/telephony/ims/feature/RcsFeature.java +++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java @@ -16,6 +16,7 @@ package android.telephony.ims.feature; +import android.annotation.SystemApi; import android.telephony.ims.aidl.IImsRcsFeature; /** @@ -23,9 +24,10 @@ import android.telephony.ims.aidl.IImsRcsFeature; * this class and provide implementations of the RcsFeature methods that they support. * @hide */ - +@SystemApi public class RcsFeature extends ImsFeature { + /**{@inheritDoc}*/ private final IImsRcsFeature mImsRcsBinder = new IImsRcsFeature.Stub() { // Empty Default Implementation. }; @@ -35,12 +37,16 @@ public class RcsFeature extends ImsFeature { super(); } + /** + * {@inheritDoc} + */ @Override public void changeEnabledCapabilities(CapabilityChangeRequest request, CapabilityCallbackProxy c) { // Do nothing for base implementation. } + /**{@inheritDoc}*/ @Override public void onFeatureRemoved() { @@ -52,6 +58,9 @@ public class RcsFeature extends ImsFeature { } + /** + * @hide + */ @Override public final IImsRcsFeature getBinder() { return mImsRcsBinder; diff --git a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java index 95243fdc5f1..c6ca6fdb0fd 100644 --- a/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java @@ -16,30 +16,257 @@ package android.telephony.ims.stub; +import android.annotation.SystemApi; import android.os.Message; import android.os.RemoteException; import android.telephony.ims.ImsCallSessionListener; import android.telephony.ims.aidl.IImsCallSessionListener; -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.internal.ImsCallSession; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsStreamMediaProfile; +import android.telephony.ims.ImsCallSession; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsVideoCallProvider; +import android.telephony.ims.ImsVideoCallProvider; + +import dalvik.system.CloseGuard; /** - * Base implementation of IImsCallSession, which implements stub versions of the methods in the - * IImsCallSession AIDL. Override the methods that your implementation of ImsCallSession supports. + * Base implementation of IImsCallSession, which implements stub versions of the methods available. * - * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you - * will break other implementations of ImsCallSession maintained by other ImsServices. + * Override the methods that your implementation of ImsCallSession supports. * * @hide */ +@SystemApi +// DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you +// will break other implementations of ImsCallSession maintained by other ImsServices. +public class ImsCallSessionImplBase implements AutoCloseable { + /** + * Notify USSD Mode. + */ + public static final int USSD_MODE_NOTIFY = 0; + /** + * Request USSD Mode + */ + public static final int USSD_MODE_REQUEST = 1; -public class ImsCallSessionImplBase extends IImsCallSession.Stub { + /** + * Defines IMS call session state. + */ + public static class State { + public static final int IDLE = 0; + public static final int INITIATED = 1; + public static final int NEGOTIATING = 2; + public static final int ESTABLISHING = 3; + public static final int ESTABLISHED = 4; + + public static final int RENEGOTIATING = 5; + public static final int REESTABLISHING = 6; + + public static final int TERMINATING = 7; + public static final int TERMINATED = 8; + + public static final int INVALID = (-1); + + /** + * Converts the state to string. + */ + public static String toString(int state) { + switch (state) { + case IDLE: + return "IDLE"; + case INITIATED: + return "INITIATED"; + case NEGOTIATING: + return "NEGOTIATING"; + case ESTABLISHING: + return "ESTABLISHING"; + case ESTABLISHED: + return "ESTABLISHED"; + case RENEGOTIATING: + return "RENEGOTIATING"; + case REESTABLISHING: + return "REESTABLISHING"; + case TERMINATING: + return "TERMINATING"; + case TERMINATED: + return "TERMINATED"; + default: + return "UNKNOWN"; + } + } + + /** + * @hide + */ + private State() { + } + } + + // Non-final for injection by tests + private IImsCallSession mServiceImpl = new IImsCallSession.Stub() { + @Override + public void close() { + ImsCallSessionImplBase.this.close(); + } + + @Override + public String getCallId() { + return ImsCallSessionImplBase.this.getCallId(); + } + + @Override + public ImsCallProfile getCallProfile() { + return ImsCallSessionImplBase.this.getCallProfile(); + } + + @Override + public ImsCallProfile getLocalCallProfile() { + return ImsCallSessionImplBase.this.getLocalCallProfile(); + } + + @Override + public ImsCallProfile getRemoteCallProfile() { + return ImsCallSessionImplBase.this.getRemoteCallProfile(); + } + + @Override + public String getProperty(String name) { + return ImsCallSessionImplBase.this.getProperty(name); + } + + @Override + public int getState() { + return ImsCallSessionImplBase.this.getState(); + } + + @Override + public boolean isInCall() { + return ImsCallSessionImplBase.this.isInCall(); + } + + @Override + public void setListener(IImsCallSessionListener listener) { + ImsCallSessionImplBase.this.setListener(new ImsCallSessionListener(listener)); + } + + @Override + public void setMute(boolean muted) { + ImsCallSessionImplBase.this.setMute(muted); + } + + @Override + public void start(String callee, ImsCallProfile profile) { + ImsCallSessionImplBase.this.start(callee, profile); + } + + @Override + public void startConference(String[] participants, ImsCallProfile profile) throws + RemoteException { + ImsCallSessionImplBase.this.startConference(participants, profile); + } + + @Override + public void accept(int callType, ImsStreamMediaProfile profile) { + ImsCallSessionImplBase.this.accept(callType, profile); + } + + @Override + public void reject(int reason) { + ImsCallSessionImplBase.this.reject(reason); + } + + @Override + public void terminate(int reason) { + ImsCallSessionImplBase.this.terminate(reason); + } + + @Override + public void hold(ImsStreamMediaProfile profile) { + ImsCallSessionImplBase.this.hold(profile); + } + + @Override + public void resume(ImsStreamMediaProfile profile) { + ImsCallSessionImplBase.this.resume(profile); + } + + @Override + public void merge() { + ImsCallSessionImplBase.this.merge(); + } + + @Override + public void update(int callType, ImsStreamMediaProfile profile) { + ImsCallSessionImplBase.this.update(callType, profile); + } + + @Override + public void extendToConference(String[] participants) { + ImsCallSessionImplBase.this.extendToConference(participants); + } + + @Override + public void inviteParticipants(String[] participants) { + ImsCallSessionImplBase.this.inviteParticipants(participants); + } + + @Override + public void removeParticipants(String[] participants) { + ImsCallSessionImplBase.this.removeParticipants(participants); + } + + @Override + public void sendDtmf(char c, Message result) { + ImsCallSessionImplBase.this.sendDtmf(c, result); + } + + @Override + public void startDtmf(char c) { + ImsCallSessionImplBase.this.startDtmf(c); + } + + @Override + public void stopDtmf() { + ImsCallSessionImplBase.this.stopDtmf(); + } + + @Override + public void sendUssd(String ussdMessage) { + ImsCallSessionImplBase.this.sendUssd(ussdMessage); + } + + @Override + public IImsVideoCallProvider getVideoCallProvider() { + return ImsCallSessionImplBase.this.getVideoCallProvider(); + } + + @Override + public boolean isMultiparty() { + return ImsCallSessionImplBase.this.isMultiparty(); + } + + @Override + public void sendRttModifyRequest(ImsCallProfile toProfile) { + ImsCallSessionImplBase.this.sendRttModifyRequest(toProfile); + } + + @Override + public void sendRttModifyResponse(boolean status) { + ImsCallSessionImplBase.this.sendRttModifyResponse(status); + } + + @Override + public void sendRttMessage(String rttMessage) { + ImsCallSessionImplBase.this.sendRttMessage(rttMessage); + } + }; - @Override + /** + * @hide + */ public final void setListener(IImsCallSessionListener listener) throws RemoteException { setListener(new ImsCallSessionListener(listener)); } @@ -49,13 +276,14 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * can only hold one listener at a time. Subsequent calls to this method * override the previous listener. * - * @param listener to listen to the session events of this object + * @param listener {@link ImsCallSessionListener} used to notify the framework of updates + * to the ImsCallSession */ public void setListener(ImsCallSessionListener listener) { } /** - * Closes the object. This object is not usable after being closed. + * Closes the object. This {@link ImsCallSessionImplBase} is not usable after being closed. */ @Override public void close() { @@ -63,72 +291,55 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { } /** - * Gets the call ID of the session. - * - * @return the call ID + * @return A String containing the unique call ID of this {@link ImsCallSessionImplBase}. */ - @Override public String getCallId() { return null; } /** - * Gets the call profile that this session is associated with - * - * @return the {@link ImsCallProfile} that this session is associated with + * @return The {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is associated + * with. */ - @Override public ImsCallProfile getCallProfile() { return null; } /** - * Gets the local call profile that this session is associated with - * - * @return the local {@link ImsCallProfile} that this session is associated with + * @return The local {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is + * associated with. */ - @Override public ImsCallProfile getLocalCallProfile() { return null; } /** - * Gets the remote call profile that this session is associated with - * - * @return the remote {@link ImsCallProfile} that this session is associated with + * @return The remote {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is + * associated with. */ - @Override public ImsCallProfile getRemoteCallProfile() { return null; } /** - * Gets the value associated with the specified property of this session. - * - * @return the string value associated with the specified property + * @param name The String extra key. + * @return The string extra value associated with the specified property. */ - @Override public String getProperty(String name) { return null; } /** - * Gets the session state. - * The value returned must be one of the states in {@link ImsCallSession.State}. - * - * @return the session state + * @return The {@link ImsCallSessionImplBase} state, defined in + * {@link ImsCallSessionImplBase.State}. */ - @Override public int getState() { - return ImsCallSession.State.INVALID; + return ImsCallSessionImplBase.State.INVALID; } /** - * Checks if the session is in call. - * - * @return true if the session is in call, false otherwise + * @return true if the {@link ImsCallSessionImplBase} is in a call, false otherwise. */ - @Override public boolean isInCall() { return false; } @@ -136,16 +347,16 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { /** * Mutes or unmutes the mic for the active call. * - * @param muted true if the call is muted, false otherwise + * @param muted true if the call should be muted, false otherwise. */ - @Override public void setMute(boolean muted) { } /** - * Initiates an IMS call with the specified target and call profile. - * The session listener set in {@link #setListener} is called back upon defined session events. - * The method is only valid to call when the session state is in + * Initiates an IMS call with the specified number and call profile. + * The session listener set in {@link #setListener(ImsCallSessionListener)} is called back upon + * defined session events. + * Only valid to call when the session state is in * {@link ImsCallSession.State#IDLE}. * * @param callee dialed string to make the call to @@ -154,13 +365,13 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionStarted}, * {@link ImsCallSession.Listener#callSessionStartFailed} */ - @Override public void start(String callee, ImsCallProfile profile) { } /** * Initiates an IMS call with the specified participants and call profile. - * The session listener set in {@link #setListener} is called back upon defined session events. + * The session listener set in {@link #setListener(ImsCallSessionListener)} is called back upon + * defined session events. * The method is only valid to call when the session state is in * {@link ImsCallSession.State#IDLE}. * @@ -170,7 +381,6 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionStarted}, * {@link ImsCallSession.Listener#callSessionStartFailed} */ - @Override public void startConference(String[] participants, ImsCallProfile profile) { } @@ -181,30 +391,25 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered * @see {@link ImsCallSession.Listener#callSessionStarted} */ - @Override public void accept(int callType, ImsStreamMediaProfile profile) { } /** * Rejects an incoming call or session update. * - * @param reason reason code to reject an incoming call, defined in - * com.android.ims.ImsReasonInfo + * @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}. * {@link ImsCallSession.Listener#callSessionStartFailed} */ - @Override public void reject(int reason) { } /** * Terminates a call. * - * @param reason reason code to terminate a call, defined in - * com.android.ims.ImsReasonInfo + * @param reason reason code to terminate a call, defined in {@link ImsReasonInfo}. * * @see {@link ImsCallSession.Listener#callSessionTerminated} */ - @Override public void terminate(int reason) { } @@ -216,7 +421,6 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionHeld}, * {@link ImsCallSession.Listener#callSessionHoldFailed} */ - @Override public void hold(ImsStreamMediaProfile profile) { } @@ -228,12 +432,11 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionResumed}, * {@link ImsCallSession.Listener#callSessionResumeFailed} */ - @Override public void resume(ImsStreamMediaProfile profile) { } /** - * Merges the active & hold call. When the merge starts, + * Merges the active and held call. When the merge starts, * {@link ImsCallSession.Listener#callSessionMergeStarted} is called. * {@link ImsCallSession.Listener#callSessionMergeComplete} is called if the merge is * successful, and {@link ImsCallSession.Listener#callSessionMergeFailed} is called if the merge @@ -243,7 +446,6 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * {@link ImsCallSession.Listener#callSessionMergeComplete}, * {@link ImsCallSession.Listener#callSessionMergeFailed} */ - @Override public void merge() { } @@ -255,7 +457,6 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionUpdated}, * {@link ImsCallSession.Listener#callSessionUpdateFailed} */ - @Override public void update(int callType, ImsStreamMediaProfile profile) { } @@ -267,7 +468,6 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionConferenceExtended}, * {@link ImsCallSession.Listener#callSessionConferenceExtendFailed} */ - @Override public void extendToConference(String[] participants) { } @@ -278,8 +478,7 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestDelivered}, * {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestFailed} */ - @Override - public void inviteParticipants(String[] participants) throws RemoteException { + public void inviteParticipants(String[] participants) { } /** @@ -289,7 +488,6 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * @see {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestDelivered}, * {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestFailed} */ - @Override public void removeParticipants(String[] participants) { } @@ -300,7 +498,6 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. */ - @Override public void sendDtmf(char c, Message result) { } @@ -311,14 +508,12 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. */ - @Override public void startDtmf(char c) { } /** * Stop a DTMF code. */ - @Override public void stopDtmf() { } @@ -327,17 +522,23 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * * @param ussdMessage USSD message to send */ - @Override public void sendUssd(String ussdMessage) { } /** - * Returns a binder for the video call provider implementation contained within the IMS service - * process. This binder is used by the VideoCallProvider subclass in Telephony which - * intermediates between the propriety implementation and Telecomm/InCall. + * See {@link #getImsVideoCallProvider()}, used directly in older ImsService implementations. + * @hide */ - @Override public IImsVideoCallProvider getVideoCallProvider() { + ImsVideoCallProvider provider = getImsVideoCallProvider(); + return provider != null ? provider.getInterface() : null; + } + + /** + * @return The {@link ImsVideoCallProvider} implementation contained within the IMS service + * process. + */ + public ImsVideoCallProvider getImsVideoCallProvider() { return null; } @@ -345,7 +546,6 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * Determines if the current session is multiparty. * @return {@code True} if the session is multiparty. */ - @Override public boolean isMultiparty() { return false; } @@ -354,16 +554,13 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * Device issues RTT modify request * @param toProfile The profile with requested changes made */ - @Override public void sendRttModifyRequest(ImsCallProfile toProfile) { } /** * Device responds to Remote RTT modify request - * @param status true Accepted the request - * false Declined the request + * @param status true if the the request was accepted or false of the request is defined. */ - @Override public void sendRttModifyResponse(boolean status) { } @@ -371,7 +568,16 @@ public class ImsCallSessionImplBase extends IImsCallSession.Stub { * Device sends RTT message * @param rttMessage RTT message to be sent */ - @Override public void sendRttMessage(String rttMessage) { } + + /** @hide */ + public IImsCallSession getServiceImpl() { + return mServiceImpl; + } + + /** @hide */ + public void setServiceImpl(IImsCallSession serviceImpl) { + mServiceImpl = serviceImpl; + } } diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java index 9b73c0cef38..412bef0cc08 100644 --- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java @@ -16,6 +16,7 @@ package android.telephony.ims.stub; +import android.annotation.SystemApi; import android.content.Context; import android.content.Intent; import android.os.RemoteCallbackList; @@ -43,10 +44,10 @@ import java.util.HashMap; * performed every time. * @hide */ - +@SystemApi public class ImsConfigImplBase { - static final private String TAG = "ImsConfigImplBase"; + private static final String TAG = "ImsConfigImplBase"; /** * Implements the IImsConfig AIDL interface, which is called by potentially many processes @@ -90,11 +91,12 @@ public class ImsConfigImplBase { /** * Gets the value for ims service/capabilities parameters. It first checks its local cache, - * if missed, it will call ImsConfigImplBase.getProvisionedValue. + * if missed, it will call ImsConfigImplBase.getConfigInt. * Synchronous blocking call. * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. - * @return value in Integer format. + * @param item integer key + * @return value in Integer format or {@link #CONFIG_RESULT_UNKNOWN} if + * unavailable. */ @Override public synchronized int getConfigInt(int item) throws RemoteException { @@ -111,10 +113,10 @@ public class ImsConfigImplBase { /** * Gets the value for ims service/capabilities parameters. It first checks its local cache, - * if missed, it will call #ImsConfigImplBase.getProvisionedValue. + * if missed, it will call #ImsConfigImplBase.getConfigString. * Synchronous blocking call. * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param item integer key * @return value in String format. */ @Override @@ -136,9 +138,10 @@ public class ImsConfigImplBase { * from which the master value is derived, and write it into local cache. * Synchronous blocking call. * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param item integer key * @param value in Integer format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + * @return the result of setting the configuration value, defined as either + * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}. */ @Override public synchronized int setConfigInt(int item, int value) throws RemoteException { @@ -162,7 +165,8 @@ public class ImsConfigImplBase { * * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. * @param value in String format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + * @return the result of setting the configuration value, defined as either + * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}. */ @Override public synchronized int setConfigString(int item, String value) @@ -245,12 +249,31 @@ public class ImsConfigImplBase { } } + /** + * The configuration requested resulted in an unknown result. This may happen if the + * IMS configurations are unavailable. + */ + public static final int CONFIG_RESULT_UNKNOWN = -1; + /** + * Setting the configuration value completed. + */ + public static final int CONFIG_RESULT_SUCCESS = 0; + /** + * Setting the configuration value failed. + */ + public static final int CONFIG_RESULT_FAILED = 1; + private final RemoteCallbackList mCallbacks = new RemoteCallbackList<>(); ImsConfigStub mImsConfigStub; + /** + * Used for compatibility between older versions of the ImsService. + * @hide + */ public ImsConfigImplBase(Context context) { mImsConfigStub = new ImsConfigStub(this); } + public ImsConfigImplBase() { mImsConfigStub = new ImsConfigStub(this); } @@ -273,7 +296,11 @@ public class ImsConfigImplBase { mCallbacks.unregister(c); } - void notifyConfigChanged(int item, int value) { + /** + * @param item + * @param value + */ + private final void notifyConfigChanged(int item, int value) { // can be null in testing if (mCallbacks == null) { return; @@ -301,14 +328,17 @@ public class ImsConfigImplBase { }); } + /** + * @hide + */ public IImsConfig getIImsConfig() { return mImsConfigStub; } /** * Updates provisioning value and notifies the framework of the change. - * Doesn't call #setProvisionedValue and assumes the result succeeded. - * This should only be used by modem when they implicitly changed provisioned values. + * Doesn't call {@link #setConfig(int,int)} and assumes the result succeeded. + * This should only be used when the IMS implementer implicitly changed provisioned values. * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param item an integer key. * @param value in Integer format. */ public final void notifyProvisionedValueChanged(int item, int value) { @@ -321,10 +351,10 @@ public class ImsConfigImplBase { /** * Updates provisioning value and notifies the framework of the change. - * Doesn't call #setProvisionedValue and assumes the result succeeded. - * This should only be used by modem when they implicitly changed provisioned values. + * Doesn't call {@link #setConfig(int,String)} and assumes the result succeeded. + * This should only be used when the IMS implementer implicitly changed provisioned values. * - * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param item an integer key. * @param value in String format. */ public final void notifyProvisionedValueChanged(int item, String value) { @@ -336,51 +366,51 @@ public class ImsConfigImplBase { } /** - * Sets the value for IMS service/capabilities parameters by the operator device - * management entity. It sets the config item value in the provisioned storage - * from which the master value is derived. + * Sets the configuration value for this ImsService. * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in Integer format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + * @param item an integer key. + * @param value an integer containing the configuration value. + * @return the result of setting the configuration value, defined as either + * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}. */ public int setConfig(int item, int value) { // Base Implementation - To be overridden. - return ImsConfig.OperationStatusConstants.FAILED; + return CONFIG_RESULT_FAILED; } /** - * Sets the value for IMS service/capabilities parameters by the operator device - * management entity. It sets the config item value in the provisioned storage - * from which the master value is derived. + * Sets the configuration value for this ImsService. * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @param value in String format. - * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + * @param item an integer key. + * @param value a String containing the new configuration value. + * @return Result of setting the configuration value, defined as either + * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}. */ public int setConfig(int item, String value) { - return ImsConfig.OperationStatusConstants.FAILED; + // Base Implementation - To be overridden. + return CONFIG_RESULT_FAILED; } /** - * Gets the value for ims service/capabilities parameters from the provisioned - * value storage. + * Gets the currently stored value configuration value from the ImsService for {@code item}. * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @return value in Integer format. + * @param item an integer key. + * @return configuration value, stored in integer format or {@link #CONFIG_RESULT_UNKNOWN} if + * unavailable. */ public int getConfigInt(int item) { - return ImsConfig.OperationStatusConstants.FAILED; + // Base Implementation - To be overridden. + return CONFIG_RESULT_UNKNOWN; } /** - * Gets the value for ims service/capabilities parameters from the provisioned - * value storage. + * Gets the currently stored value configuration value from the ImsService for {@code item}. * - * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. - * @return value in String format. + * @param item an integer key. + * @return configuration value, stored in String format or {@code null} if unavailable. */ public String getConfigString(int item) { + // Base Implementation - To be overridden. return null; } } diff --git a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java index 89f95ff0142..06c35eaec6d 100644 --- a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java @@ -16,7 +16,9 @@ package android.telephony.ims.stub; +import android.annotation.SystemApi; import android.os.RemoteException; +import android.util.Log; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsEcbmListener; @@ -30,22 +32,65 @@ import com.android.ims.internal.IImsEcbmListener; * * @hide */ +@SystemApi +public class ImsEcbmImplBase { + private static final String TAG = "ImsEcbmImplBase"; -public class ImsEcbmImplBase extends IImsEcbm.Stub { + private IImsEcbmListener mListener; + private IImsEcbm mImsEcbm = new IImsEcbm.Stub() { + @Override + public void setListener(IImsEcbmListener listener) { + mListener = listener; + } + + @Override + public void exitEmergencyCallbackMode() { + ImsEcbmImplBase.this.exitEmergencyCallbackMode(); + } + }; + + /** @hide */ + public IImsEcbm getImsEcbm() { + return mImsEcbm; + } /** - * Sets the listener. + * This method should be implemented by the IMS provider. Framework will trigger this method to + * request to come out of ECBM mode */ - @Override - public void setListener(IImsEcbmListener listener) throws RemoteException { - + public void exitEmergencyCallbackMode() { + Log.d(TAG, "exitEmergencyCallbackMode() not implemented"); } /** - * Requests Modem to come out of ECBM mode + * Notifies the framework when the device enters Emergency Callback Mode. + * + * @throws RuntimeException if the connection to the framework is not available. */ - @Override - public void exitEmergencyCallbackMode() throws RemoteException { + public final void enteredEcbm() { + Log.d(TAG, "Entered ECBM."); + if (mListener != null) { + try { + mListener.enteredECBM(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + /** + * Notifies the framework when the device exits Emergency Callback Mode. + * + * @throws RuntimeException if the connection to the framework is not available. + */ + public final void exitedEcbm() { + Log.d(TAG, "Exited ECBM."); + if (mListener != null) { + try { + mListener.exitedECBM(); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } } } diff --git a/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java index c8989e059ee..98b67c3d372 100644 --- a/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java +++ b/telephony/java/android/telephony/ims/stub/ImsFeatureConfiguration.java @@ -16,6 +16,7 @@ package android.telephony.ims.stub; +import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import android.telephony.ims.feature.ImsFeature; @@ -25,17 +26,22 @@ import java.util.Set; /** * Container class for IMS Feature configuration. This class contains the features that the - * ImsService supports, which are defined in {@link ImsFeature.FeatureType}. + * ImsService supports, which are defined in {@link ImsFeature} as + * {@link ImsFeature#FEATURE_EMERGENCY_MMTEL}, {@link ImsFeature#FEATURE_MMTEL}, and + * {@link ImsFeature#FEATURE_RCS}. + * * @hide */ -public class ImsFeatureConfiguration implements Parcelable { +@SystemApi +public final class ImsFeatureConfiguration implements Parcelable { /** * Features that this ImsService supports. */ private final Set mFeatures; /** - * Creates an ImsFeatureConfiguration with the features + * Builder for {@link ImsFeatureConfiguration} that makes adding supported {@link ImsFeature}s + * easier. */ public static class Builder { ImsFeatureConfiguration mConfig; @@ -71,7 +77,10 @@ public class ImsFeatureConfiguration implements Parcelable { * Configuration of the ImsService, which describes which features the ImsService supports * (for registration). * @param features an array of feature integers defined in {@link ImsFeature} that describe - * which features this ImsService supports. + * which features this ImsService supports. Supported values are + * {@link ImsFeature#FEATURE_EMERGENCY_MMTEL}, {@link ImsFeature#FEATURE_MMTEL}, and + * {@link ImsFeature#FEATURE_RCS}. + * @hide */ public ImsFeatureConfiguration(int[] features) { mFeatures = new ArraySet<>(); @@ -84,7 +93,9 @@ public class ImsFeatureConfiguration implements Parcelable { } /** - * @return an int[] containing the features that this ImsService supports. + * @return an int[] containing the features that this ImsService supports. Supported values are + * {@link ImsFeature#FEATURE_EMERGENCY_MMTEL}, {@link ImsFeature#FEATURE_MMTEL}, and + * {@link ImsFeature#FEATURE_RCS}. */ public int[] getServiceFeatures() { return mFeatures.stream().mapToInt(i->i).toArray(); @@ -94,6 +105,7 @@ public class ImsFeatureConfiguration implements Parcelable { mFeatures.add(feature); } + /** @hide */ protected ImsFeatureConfiguration(Parcel in) { int[] features = in.createIntArray(); if (features != null) { @@ -129,6 +141,9 @@ public class ImsFeatureConfiguration implements Parcelable { dest.writeIntArray(mFeatures.stream().mapToInt(i->i).toArray()); } + /** + * @hide + */ @Override public boolean equals(Object o) { if (this == o) return true; @@ -140,6 +155,9 @@ public class ImsFeatureConfiguration implements Parcelable { return mFeatures.equals(that.mFeatures); } + /** + * @hide + */ @Override public int hashCode() { return mFeatures.hashCode(); diff --git a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java index 05da9da485a..ce2d89a8d80 100644 --- a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java @@ -16,11 +16,16 @@ package android.telephony.ims.stub; +import android.annotation.SystemApi; import android.os.RemoteException; +import android.util.Log; +import android.telephony.ims.ImsExternalCallState; import com.android.ims.internal.IImsExternalCallStateListener; import com.android.ims.internal.IImsMultiEndpoint; +import java.util.List; + /** * Base implementation of ImsMultiEndpoint, which implements stub versions of the methods * in the IImsMultiEndpoint AIDL. Override the methods that your implementation of @@ -31,23 +36,49 @@ import com.android.ims.internal.IImsMultiEndpoint; * * @hide */ +@SystemApi +public class ImsMultiEndpointImplBase { + private static final String TAG = "MultiEndpointImplBase"; -public class ImsMultiEndpointImplBase extends IImsMultiEndpoint.Stub { + private IImsExternalCallStateListener mListener; + private IImsMultiEndpoint mImsMultiEndpoint = new IImsMultiEndpoint.Stub() { + @Override + public void setListener(IImsExternalCallStateListener listener) throws RemoteException { + mListener = listener; + } - /** - * Sets the listener. - */ - @Override - public void setListener(IImsExternalCallStateListener listener) throws RemoteException { + @Override + public void requestImsExternalCallStateInfo() throws RemoteException { + ImsMultiEndpointImplBase.this.requestImsExternalCallStateInfo(); + } + }; + /** @hide */ + public IImsMultiEndpoint getIImsMultiEndpoint() { + return mImsMultiEndpoint; } /** - * Query API to get the latest Dialog Event Package information - * Should be invoked only after setListener is done + * Notifies framework when Dialog Event Package update is received + * + * @throws RuntimeException if the connection to the framework is not available. */ - @Override - public void requestImsExternalCallStateInfo() throws RemoteException { + public final void onImsExternalCallStateUpdate(List externalCallDialogs) { + Log.d(TAG, "ims external call state update triggered."); + if (mListener != null) { + try { + mListener.onImsExternalCallStateUpdate(externalCallDialogs); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + /** + * This method should be implemented by the IMS provider. Framework will trigger this to get the + * latest Dialog Event Package information. Should + */ + public void requestImsExternalCallStateInfo() { + Log.d(TAG, "requestImsExternalCallStateInfo() not implemented"); } } diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java index dfb9b63f7b1..4334d3aadab 100644 --- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java @@ -17,6 +17,7 @@ package android.telephony.ims.stub; import android.annotation.IntDef; +import android.annotation.SystemApi; import android.net.Uri; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -24,7 +25,8 @@ import android.telephony.ims.aidl.IImsRegistration; import android.telephony.ims.aidl.IImsRegistrationCallback; import android.util.Log; -import com.android.ims.ImsReasonInfo; +import android.telephony.ims.ImsReasonInfo; + import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; @@ -35,11 +37,14 @@ import java.lang.annotation.RetentionPolicy; * registration for this ImsService has changed status. * @hide */ - +@SystemApi public class ImsRegistrationImplBase { private static final String LOG_TAG = "ImsRegistrationImplBase"; + /** + * @hide + */ // Defines the underlying radio technology type that we have registered for IMS over. @IntDef(flag = true, value = { @@ -154,6 +159,9 @@ public class ImsRegistrationImplBase { // Locked on mLock, create unspecified disconnect cause. private ImsReasonInfo mLastDisconnectCause = new ImsReasonInfo(); + /** + * @hide + */ public final IImsRegistration getBinder() { return mBinder; } @@ -170,8 +178,8 @@ public class ImsRegistrationImplBase { /** * Notify the framework that the device is connected to the IMS network. * - * @param imsRadioTech the radio access technology. Valid values are defined in - * {@link ImsRegistrationTech}. + * @param imsRadioTech the radio access technology. Valid values are defined as + * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN}. */ public final void onRegistered(@ImsRegistrationTech int imsRadioTech) { updateToState(imsRadioTech, REGISTRATION_STATE_REGISTERED); @@ -188,8 +196,8 @@ public class ImsRegistrationImplBase { /** * Notify the framework that the device is trying to connect the IMS network. * - * @param imsRadioTech the radio access technology. Valid values are defined in - * {@link ImsRegistrationTech}. + * @param imsRadioTech the radio access technology. Valid values are defined as + * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN}. */ public final void onRegistering(@ImsRegistrationTech int imsRadioTech) { updateToState(imsRadioTech, REGISTRATION_STATE_REGISTERING); @@ -220,6 +228,13 @@ public class ImsRegistrationImplBase { }); } + /** + * Notify the framework that the handover from the current radio technology to the technology + * defined in {@code imsRadioTech} has failed. + * @param imsRadioTech The technology that has failed to be changed. Valid values are + * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN}. + * @param info The {@link ImsReasonInfo} for the failure to change technology. + */ public final void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech, ImsReasonInfo info) { mCallbacks.broadcast((c) -> { @@ -232,6 +247,11 @@ public class ImsRegistrationImplBase { }); } + /** + * The this device's subscriber associated {@link Uri}s have changed, which are used to filter + * out this device's {@link Uri}s during conference calling. + * @param uris + */ public final void onSubscriberAssociatedUriChanged(Uri[] uris) { mCallbacks.broadcast((c) -> { try { @@ -263,6 +283,11 @@ public class ImsRegistrationImplBase { } } + /** + * @return the current registration connection type. Valid values are + * {@link #REGISTRATION_TECH_LTE} and {@link #REGISTRATION_TECH_IWLAN} + * @hide + */ @VisibleForTesting public final @ImsRegistrationTech int getConnectionType() { synchronized (mLock) { diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java index 7fe1c8cd005..0673a384538 100644 --- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java @@ -157,7 +157,7 @@ public class ImsSmsImplBase { * @param token token provided in {@link #onSmsReceived(int, String, byte[])} * @param result result of delivering the message. Valid values are: * {@link #DELIVER_STATUS_OK}, - * {@link #DELIVER_STATUS_OK} + * {@link #DELIVER_STATUS_ERROR} * @param messageRef the message reference */ public void acknowledgeSms(int token, @DeliverStatusResult int messageRef, int result) { @@ -298,9 +298,9 @@ public class ImsSmsImplBase { } /** - * Called when SmsImpl has been initialized and communication with the framework is set up. + * Called when ImsSmsImpl has been initialized and communication with the framework is set up. * Any attempt by this class to access the framework before this method is called will return - * with an {@link RuntimeException}. + * with a {@link RuntimeException}. */ public void onReady() { // Base Implementation - Should be overridden diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java index 054a8b22d0f..fcd7faf73bb 100644 --- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java @@ -16,177 +16,332 @@ package android.telephony.ims.stub; +import android.annotation.SystemApi; import android.os.Bundle; import android.os.RemoteException; +import android.telephony.ims.ImsUtListener; import com.android.ims.internal.IImsUt; import com.android.ims.internal.IImsUtListener; /** - * Base implementation of ImsUt, which implements stub versions of the methods - * in the IImsUt AIDL. Override the methods that your implementation of ImsUt supports. - * - * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you - * will break other implementations of ImsUt maintained by other ImsServices. - * - * Provides the Ut interface interworking to get/set the supplementary service configuration. + * Base implementation of IMS UT interface, which implements stubs. Override these methods to + * implement functionality. * * @hide */ +// DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you +// will break other implementations of ImsUt maintained by other ImsServices. +@SystemApi +public class ImsUtImplBase { + + private IImsUt.Stub mServiceImpl = new IImsUt.Stub() { + @Override + public void close() throws RemoteException { + ImsUtImplBase.this.close(); + } + + @Override + public int queryCallBarring(int cbType) throws RemoteException { + return ImsUtImplBase.this.queryCallBarring(cbType); + } + + @Override + public int queryCallForward(int condition, String number) throws RemoteException { + return ImsUtImplBase.this.queryCallForward(condition, number); + } + + @Override + public int queryCallWaiting() throws RemoteException { + return ImsUtImplBase.this.queryCallWaiting(); + } + + @Override + public int queryCLIR() throws RemoteException { + return ImsUtImplBase.this.queryCLIR(); + } + + @Override + public int queryCLIP() throws RemoteException { + return ImsUtImplBase.this.queryCLIP(); + } + + @Override + public int queryCOLR() throws RemoteException { + return ImsUtImplBase.this.queryCOLR(); + } + + @Override + public int queryCOLP() throws RemoteException { + return ImsUtImplBase.this.queryCOLP(); + } + + @Override + public int transact(Bundle ssInfo) throws RemoteException { + return ImsUtImplBase.this.transact(ssInfo); + } + + @Override + public int updateCallBarring(int cbType, int action, String[] barrList) throws + RemoteException { + return ImsUtImplBase.this.updateCallBarring(cbType, action, barrList); + } + + @Override + public int updateCallForward(int action, int condition, String number, int serviceClass, + int timeSeconds) throws RemoteException { + return ImsUtImplBase.this.updateCallForward(action, condition, number, serviceClass, + timeSeconds); + } + + @Override + public int updateCallWaiting(boolean enable, int serviceClass) throws RemoteException { + return ImsUtImplBase.this.updateCallWaiting(enable, serviceClass); + } + + @Override + public int updateCLIR(int clirMode) throws RemoteException { + return ImsUtImplBase.this.updateCLIR(clirMode); + } + + @Override + public int updateCLIP(boolean enable) throws RemoteException { + return ImsUtImplBase.this.updateCLIP(enable); + } -public class ImsUtImplBase extends IImsUt.Stub { + @Override + public int updateCOLR(int presentation) throws RemoteException { + return ImsUtImplBase.this.updateCOLR(presentation); + } + + @Override + public int updateCOLP(boolean enable) throws RemoteException { + return ImsUtImplBase.this.updateCOLP(enable); + } + + @Override + public void setListener(IImsUtListener listener) throws RemoteException { + ImsUtImplBase.this.setListener(new ImsUtListener(listener)); + } + + @Override + public int queryCallBarringForServiceClass(int cbType, int serviceClass) + throws RemoteException { + return ImsUtImplBase.this.queryCallBarringForServiceClass(cbType, serviceClass); + } + + @Override + public int updateCallBarringForServiceClass(int cbType, int action, + String[] barrList, int serviceClass) throws RemoteException { + return ImsUtImplBase.this.updateCallBarringForServiceClass( + cbType, action, barrList, serviceClass); + } + }; /** - * Closes the object. This object is not usable after being closed. + * Called when the framework no longer needs to interact with the IMS UT implementation any + * longer. */ - @Override - public void close() throws RemoteException { + public void close() { } /** - * Retrieves the configuration of the call barring. + * Retrieves the call barring configuration. + * @param cbType */ - @Override - public int queryCallBarring(int cbType) throws RemoteException { + public int queryCallBarring(int cbType) { return -1; } /** * Retrieves the configuration of the call barring for specified service class. */ - @Override - public int queryCallBarringForServiceClass(int cbType, int serviceClass) - throws RemoteException { + public int queryCallBarringForServiceClass(int cbType, int serviceClass) { return -1; } /** * Retrieves the configuration of the call forward. */ - @Override - public int queryCallForward(int condition, String number) throws RemoteException { + public int queryCallForward(int condition, String number) { return -1; } /** * Retrieves the configuration of the call waiting. */ - @Override - public int queryCallWaiting() throws RemoteException { + public int queryCallWaiting() { return -1; } + /** + * Retrieves the default CLIR setting. + * @hide + */ + public int queryCLIR() { + return queryClir(); + } + + /** + * Retrieves the CLIP call setting. + * @hide + */ + public int queryCLIP() { + return queryClip(); + } + + /** + * Retrieves the COLR call setting. + * @hide + */ + public int queryCOLR() { + return queryColr(); + } + + /** + * Retrieves the COLP call setting. + * @hide + */ + public int queryCOLP() { + return queryColp(); + } + /** * Retrieves the default CLIR setting. */ - @Override - public int queryCLIR() throws RemoteException { + public int queryClir() { return -1; } /** * Retrieves the CLIP call setting. */ - @Override - public int queryCLIP() throws RemoteException { + public int queryClip() { return -1; } /** * Retrieves the COLR call setting. */ - @Override - public int queryCOLR() throws RemoteException { + public int queryColr() { return -1; } /** * Retrieves the COLP call setting. */ - @Override - public int queryCOLP() throws RemoteException { + public int queryColp() { return -1; } /** * Updates or retrieves the supplementary service configuration. */ - @Override - public int transact(Bundle ssInfo) throws RemoteException { + public int transact(Bundle ssInfo) { return -1; } /** * Updates the configuration of the call barring. */ - @Override - public int updateCallBarring(int cbType, int action, String[] barrList) throws RemoteException { + public int updateCallBarring(int cbType, int action, String[] barrList) { return -1; } /** * Updates the configuration of the call barring for specified service class. */ - @Override public int updateCallBarringForServiceClass(int cbType, int action, String[] barrList, - int serviceClass) throws RemoteException { + int serviceClass) { return -1; } /** * Updates the configuration of the call forward. */ - @Override public int updateCallForward(int action, int condition, String number, int serviceClass, - int timeSeconds) throws RemoteException { + int timeSeconds) { return 0; } /** * Updates the configuration of the call waiting. */ - @Override - public int updateCallWaiting(boolean enable, int serviceClass) throws RemoteException { + public int updateCallWaiting(boolean enable, int serviceClass) { return -1; } + /** + * Updates the configuration of the CLIR supplementary service. + * @hide + */ + public int updateCLIR(int clirMode) { + return updateClir(clirMode); + } + + /** + * Updates the configuration of the CLIP supplementary service. + * @hide + */ + public int updateCLIP(boolean enable) { + return updateClip(enable); + } + + /** + * Updates the configuration of the COLR supplementary service. + * @hide + */ + public int updateCOLR(int presentation) { + return updateColr(presentation); + } + + /** + * Updates the configuration of the COLP supplementary service. + * @hide + */ + public int updateCOLP(boolean enable) { + return updateColp(enable); + } + /** * Updates the configuration of the CLIR supplementary service. */ - @Override - public int updateCLIR(int clirMode) throws RemoteException { + public int updateClir(int clirMode) { return -1; } /** * Updates the configuration of the CLIP supplementary service. */ - @Override - public int updateCLIP(boolean enable) throws RemoteException { + public int updateClip(boolean enable) { return -1; } /** * Updates the configuration of the COLR supplementary service. */ - @Override - public int updateCOLR(int presentation) throws RemoteException { + public int updateColr(int presentation) { return -1; } /** * Updates the configuration of the COLP supplementary service. */ - @Override - public int updateCOLP(boolean enable) throws RemoteException { + public int updateColp(boolean enable) { return -1; } /** * Sets the listener. */ - @Override - public void setListener(IImsUtListener listener) throws RemoteException { + public void setListener(ImsUtListener listener) { + } + + /** + * @hide + */ + public IImsUt getInterface() { + return mServiceImpl; } } diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java index e670341ffe7..1dda6bf2b8b 100644 --- a/telephony/java/com/android/ims/ImsConfig.java +++ b/telephony/java/com/android/ims/ImsConfig.java @@ -19,6 +19,7 @@ package com.android.ims; import android.content.Context; import android.os.RemoteException; import android.telephony.Rlog; +import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.stub.ImsConfigImplBase; diff --git a/telephony/java/com/android/ims/ImsException.java b/telephony/java/com/android/ims/ImsException.java index 0e8bad79c59..f35e88672a2 100644 --- a/telephony/java/com/android/ims/ImsException.java +++ b/telephony/java/com/android/ims/ImsException.java @@ -16,6 +16,8 @@ package com.android.ims; +import android.telephony.ims.ImsReasonInfo; + /** * This class defines a general IMS-related exception. * diff --git a/telephony/java/com/android/ims/ImsUtInterface.java b/telephony/java/com/android/ims/ImsUtInterface.java index 14c184a6406..c9d44055163 100644 --- a/telephony/java/com/android/ims/ImsUtInterface.java +++ b/telephony/java/com/android/ims/ImsUtInterface.java @@ -18,6 +18,8 @@ package com.android.ims; import android.os.Handler; import android.os.Message; +import android.telephony.ims.ImsCallForwardInfo; +import android.telephony.ims.ImsSsInfo; /** * Provides APIs for the supplementary service settings using IMS (Ut interface). diff --git a/telephony/java/com/android/ims/internal/IImsCallSession.aidl b/telephony/java/com/android/ims/internal/IImsCallSession.aidl index b477deab2d5..203e6cf9c57 100644 --- a/telephony/java/com/android/ims/internal/IImsCallSession.aidl +++ b/telephony/java/com/android/ims/internal/IImsCallSession.aidl @@ -19,8 +19,8 @@ package com.android.ims.internal; import android.os.Message; import android.telephony.ims.aidl.IImsCallSessionListener; -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsStreamMediaProfile; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsStreamMediaProfile; import com.android.ims.internal.IImsVideoCallProvider; /** diff --git a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl index 748092d2a3b..a8e8b7dd03a 100644 --- a/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl +++ b/telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl @@ -16,12 +16,12 @@ package com.android.ims.internal; -import com.android.ims.ImsStreamMediaProfile; -import com.android.ims.ImsCallProfile; -import com.android.ims.ImsReasonInfo; -import com.android.ims.ImsConferenceState; +import android.telephony.ims.ImsStreamMediaProfile; +import android.telephony.ims.ImsCallProfile; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsConferenceState; import com.android.ims.internal.IImsCallSession; -import com.android.ims.ImsSuppServiceNotification; +import android.telephony.ims.ImsSuppServiceNotification; /** * A listener type for receiving notification on IMS call session events. diff --git a/telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl b/telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl index 16219671cea..b3d813960c5 100644 --- a/telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl +++ b/telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl @@ -16,7 +16,7 @@ package com.android.ims.internal; -import com.android.ims.ImsExternalCallState; +import android.telephony.ims.ImsExternalCallState; /** * A listener type for receiving notifications about DEP through IMS diff --git a/telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl b/telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl new file mode 100644 index 00000000000..b83b13060af --- /dev/null +++ b/telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl @@ -0,0 +1,25 @@ +/* + * 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.ims.internal; + +/** + * Interface from ImsFeature in the ImsService to ImsServiceController. + * {@hide} + */ +oneway interface IImsFeatureStatusCallback { + void notifyImsFeatureStatus(int featureStatus); +} diff --git a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl index d43077bf10b..51511923402 100644 --- a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl +++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl @@ -18,7 +18,7 @@ package com.android.ims.internal; import android.app.PendingIntent; -import com.android.ims.ImsCallProfile; +import android.telephony.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsConfig; import com.android.ims.internal.IImsEcbm; diff --git a/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl index 15f872603bf..2212109c8a6 100644 --- a/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl +++ b/telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl @@ -16,7 +16,7 @@ package com.android.ims.internal; -import com.android.ims.ImsReasonInfo; +import android.telephony.ims.ImsReasonInfo; import android.net.Uri; diff --git a/telephony/java/com/android/ims/internal/IImsService.aidl b/telephony/java/com/android/ims/internal/IImsService.aidl index 406d22d9bd8..c3cc6fb7a38 100644 --- a/telephony/java/com/android/ims/internal/IImsService.aidl +++ b/telephony/java/com/android/ims/internal/IImsService.aidl @@ -18,7 +18,7 @@ package com.android.ims.internal; import android.app.PendingIntent; -import com.android.ims.ImsCallProfile; +import android.telephony.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; import com.android.ims.internal.IImsCallSessionListener; import com.android.ims.internal.IImsConfig; diff --git a/telephony/java/com/android/ims/internal/IImsUtListener.aidl b/telephony/java/com/android/ims/internal/IImsUtListener.aidl index 1bc03697935..a603cd34dfc 100644 --- a/telephony/java/com/android/ims/internal/IImsUtListener.aidl +++ b/telephony/java/com/android/ims/internal/IImsUtListener.aidl @@ -18,11 +18,11 @@ package com.android.ims.internal; import android.os.Bundle; -import com.android.ims.ImsCallForwardInfo; -import com.android.ims.ImsSsData; -import com.android.ims.ImsSsInfo; +import android.telephony.ims.ImsCallForwardInfo; +import android.telephony.ims.ImsSsInfo; import com.android.ims.internal.IImsUt; -import com.android.ims.ImsReasonInfo; +import android.telephony.ims.ImsReasonInfo; +import android.telephony.ims.ImsSsData; /** * {@hide} -- GitLab From 858bfaf79c97e000af68649970994ee16bdd08ac Mon Sep 17 00:00:00 2001 From: Tyler Gunn Date: Mon, 22 Jan 2018 15:17:54 -0800 Subject: [PATCH 017/416] Add handover permission, fill in some missing API gaps. Adding the ACCEPT_HANDOVER runtime permission which an app must have in order to accept handovers (this is per design). Adding missing onHandoverComplete method in the android.telecom.Connection API (per design). Finishing plumbing for android.telecom.Call#onHandoverComplete API. Fix issue where the new handover API methods would never get called; the legacy handover extra was being used in this case when it should not have been. Bug: 65415068 Test: Verified using new CTS tests Change-Id: If1558f6a23911862c02ac5b18fb62d86911ed7e2 Merged-In: If1558f6a23911862c02ac5b18fb62d86911ed7e2 --- api/current.txt | 2 + core/java/android/app/AppOpsManager.java | 21 ++++++- core/res/AndroidManifest.xml | 17 +++++ core/res/res/values/strings.xml | 11 ++++ .../server/pm/PackageManagerService.java | 3 +- telecomm/java/android/telecom/Call.java | 9 +++ telecomm/java/android/telecom/Connection.java | 9 +++ .../android/telecom/ConnectionService.java | 62 ++++++++++++++++--- .../java/android/telecom/InCallService.java | 11 ++++ telecomm/java/android/telecom/Phone.java | 7 +++ .../java/android/telecom/TelecomManager.java | 11 ++++ .../internal/telecom/IConnectionService.aidl | 2 + .../internal/telecom/IInCallService.aidl | 2 + 13 files changed, 156 insertions(+), 11 deletions(-) diff --git a/api/current.txt b/api/current.txt index fba245d386e..3e94eeecb01 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6,6 +6,7 @@ package android { public static final class Manifest.permission { ctor public Manifest.permission(); + field public static final java.lang.String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER"; field public static final java.lang.String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES"; field public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION"; field public static final java.lang.String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION"; @@ -39332,6 +39333,7 @@ package android.telecom { method public void onCallEvent(java.lang.String, android.os.Bundle); method public void onDisconnect(); method public void onExtrasChanged(android.os.Bundle); + method public void onHandoverComplete(); method public void onHold(); method public void onPlayDtmfTone(char); method public void onPostDialContinue(boolean); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index b331d84010d..e8fbbd78cff 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -252,8 +252,10 @@ public class AppOpsManager { public static final int OP_INSTANT_APP_START_FOREGROUND = 68; /** @hide Answer incoming phone calls */ public static final int OP_ANSWER_PHONE_CALLS = 69; + /** @hide Continue handover of a call from another app */ + public static final int OP_ACCEPT_HANDOVER = 70; /** @hide */ - public static final int _NUM_OP = 70; + public static final int _NUM_OP = 71; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -365,6 +367,12 @@ public class AppOpsManager { /** Answer incoming phone calls */ public static final String OPSTR_ANSWER_PHONE_CALLS = "android:answer_phone_calls"; + /*** + * Accept call handover + * @hide + */ + public static final String OPSTR_ACCEPT_HANDOVER + = "android:accept_handover"; // Warning: If an permission is added here it also has to be added to // com.android.packageinstaller.permission.utils.EventLogger @@ -400,6 +408,7 @@ public class AppOpsManager { OP_USE_SIP, OP_PROCESS_OUTGOING_CALLS, OP_ANSWER_PHONE_CALLS, + OP_ACCEPT_HANDOVER, // Microphone OP_RECORD_AUDIO, // Camera @@ -492,7 +501,8 @@ public class AppOpsManager { OP_REQUEST_INSTALL_PACKAGES, OP_PICTURE_IN_PICTURE, OP_INSTANT_APP_START_FOREGROUND, - OP_ANSWER_PHONE_CALLS + OP_ANSWER_PHONE_CALLS, + OP_ACCEPT_HANDOVER }; /** @@ -570,6 +580,7 @@ public class AppOpsManager { OPSTR_PICTURE_IN_PICTURE, OPSTR_INSTANT_APP_START_FOREGROUND, OPSTR_ANSWER_PHONE_CALLS, + OPSTR_ACCEPT_HANDOVER }; /** @@ -647,6 +658,7 @@ public class AppOpsManager { "PICTURE_IN_PICTURE", "INSTANT_APP_START_FOREGROUND", "ANSWER_PHONE_CALLS", + "ACCEPT_HANDOVER" }; /** @@ -724,6 +736,7 @@ public class AppOpsManager { null, // no permission for entering picture-in-picture on hide Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE, Manifest.permission.ANSWER_PHONE_CALLS, + Manifest.permission.ACCEPT_HANDOVER }; /** @@ -802,6 +815,7 @@ public class AppOpsManager { null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE null, // INSTANT_APP_START_FOREGROUND null, // ANSWER_PHONE_CALLS + null, // ACCEPT_HANDOVER }; /** @@ -879,6 +893,7 @@ public class AppOpsManager { false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE false, // INSTANT_APP_START_FOREGROUND false, // ANSWER_PHONE_CALLS + false, // ACCEPT_HANDOVER }; /** @@ -955,6 +970,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // OP_PICTURE_IN_PICTURE AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS + AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER }; /** @@ -1035,6 +1051,7 @@ public class AppOpsManager { false, // OP_PICTURE_IN_PICTURE false, false, // ANSWER_PHONE_CALLS + false, // ACCEPT_HANDOVER }; /** diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index fdda55bfa01..a8ec4204518 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -950,6 +950,23 @@ android:description="@string/permdesc_manageOwnCalls" android:protectionLevel="normal" /> + + + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index befebd9dd00..0a36ba74752 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1076,6 +1076,17 @@ Allows the app to route its calls through the system in order to improve the calling experience. + + continue a call from another app + + Allows the app to continue a call which was started in another app. + read phone numbers diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f182c05cb76..69afc469bd0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -610,7 +610,8 @@ public class PackageManagerService extends IPackageManager.Stub Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_NUMBERS, - Manifest.permission.ANSWER_PHONE_CALLS); + Manifest.permission.ANSWER_PHONE_CALLS, + Manifest.permission.ACCEPT_HANDOVER); /** diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 8c7d6b30ecd..8a4b046516e 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -1961,6 +1961,15 @@ public final class Call { } } + /** {@hide} */ + final void internalOnHandoverComplete() { + for (CallbackRecord record : mCallbackRecords) { + final Call call = this; + final Callback callback = record.getCallback(); + record.getHandler().post(() -> callback.onHandoverComplete(call)); + } + } + private void fireStateChanged(final int newState) { for (CallbackRecord record : mCallbackRecords) { final Call call = this; diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index d71fde28480..8150efa7257 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -2799,6 +2799,15 @@ public abstract class Connection extends Conferenceable { */ public void onCallEvent(String event, Bundle extras) {} + /** + * Notifies this {@link Connection} that a handover has completed. + *

+ * A handover is initiated with {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, + * Bundle)} on the initiating side of the handover, and on the receiving side with + * {@link TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)}. + */ + public void onHandoverComplete() {} + /** * Notifies this {@link Connection} of a change to the extras made outside the * {@link ConnectionService}. diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index e37aeb47f1a..990b0b4eefe 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -140,6 +140,7 @@ public abstract class ConnectionService extends Service { private static final String SESSION_POST_DIAL_CONT = "CS.oPDC"; private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC"; private static final String SESSION_SEND_CALL_EVENT = "CS.sCE"; + private static final String SESSION_HANDOVER_COMPLETE = "CS.hC"; private static final String SESSION_EXTRAS_CHANGED = "CS.oEC"; private static final String SESSION_START_RTT = "CS.+RTT"; private static final String SESSION_STOP_RTT = "CS.-RTT"; @@ -179,6 +180,7 @@ public abstract class ConnectionService extends Service { private static final int MSG_CONNECTION_SERVICE_FOCUS_LOST = 30; private static final int MSG_CONNECTION_SERVICE_FOCUS_GAINED = 31; private static final int MSG_HANDOVER_FAILED = 32; + private static final int MSG_HANDOVER_COMPLETE = 33; private static Connection sNullConnection; @@ -297,6 +299,19 @@ public abstract class ConnectionService extends Service { } } + @Override + public void handoverComplete(String callId, Session.Info sessionInfo) { + Log.startSession(sessionInfo, SESSION_HANDOVER_COMPLETE); + try { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = callId; + args.arg2 = Log.createSubsession(); + mHandler.obtainMessage(MSG_HANDOVER_COMPLETE, args).sendToTarget(); + } finally { + Log.endSession(); + } + } + @Override public void abort(String callId, Session.Info sessionInfo) { Log.startSession(sessionInfo, SESSION_ABORT); @@ -1028,6 +1043,19 @@ public abstract class ConnectionService extends Service { } break; } + case MSG_HANDOVER_COMPLETE: { + SomeArgs args = (SomeArgs) msg.obj; + try { + Log.continueSession((Session) args.arg2, + SESSION_HANDLER + SESSION_HANDOVER_COMPLETE); + String callId = (String) args.arg1; + notifyHandoverComplete(callId); + } finally { + args.recycle(); + Log.endSession(); + } + break; + } case MSG_ON_EXTRAS_CHANGED: { SomeArgs args = (SomeArgs) msg.obj; try { @@ -1445,19 +1473,24 @@ public abstract class ConnectionService extends Service { final ConnectionRequest request, boolean isIncoming, boolean isUnknown) { + boolean isLegacyHandover = request.getExtras() != null && + request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER, false); + boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean( + TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false); Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " + - "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request, - isIncoming, - isUnknown); + "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b", + callManagerAccount, callId, request, isIncoming, isUnknown, isLegacyHandover, + isHandover); Connection connection = null; - if (getApplicationContext().getApplicationInfo().targetSdkVersion > - Build.VERSION_CODES.O_MR1 && request.getExtras() != null && - request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER,false)) { + if (isHandover) { + PhoneAccountHandle fromPhoneAccountHandle = request.getExtras() != null + ? (PhoneAccountHandle) request.getExtras().getParcelable( + TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT) : null; if (!isIncoming) { - connection = onCreateOutgoingHandoverConnection(callManagerAccount, request); + connection = onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request); } else { - connection = onCreateIncomingHandoverConnection(callManagerAccount, request); + connection = onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request); } } else { connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request) @@ -1753,6 +1786,19 @@ public abstract class ConnectionService extends Service { } } + /** + * Notifies a {@link Connection} that a handover has completed. + * + * @param callId The ID of the call which completed handover. + */ + private void notifyHandoverComplete(String callId) { + Log.d(this, "notifyHandoverComplete(%s)", callId); + Connection connection = findConnectionForAction(callId, "notifyHandoverComplete"); + if (connection != null) { + connection.onHandoverComplete(); + } + } + /** * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom. *

diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java index 74fa62d62cc..fcf04c9a7ee 100644 --- a/telecomm/java/android/telecom/InCallService.java +++ b/telecomm/java/android/telecom/InCallService.java @@ -81,6 +81,7 @@ public abstract class InCallService extends Service { private static final int MSG_ON_RTT_UPGRADE_REQUEST = 10; private static final int MSG_ON_RTT_INITIATION_FAILURE = 11; private static final int MSG_ON_HANDOVER_FAILED = 12; + private static final int MSG_ON_HANDOVER_COMPLETE = 13; /** Default Handler used to consolidate binder method calls onto a single thread. */ private final Handler mHandler = new Handler(Looper.getMainLooper()) { @@ -157,6 +158,11 @@ public abstract class InCallService extends Service { mPhone.internalOnHandoverFailed(callId, error); break; } + case MSG_ON_HANDOVER_COMPLETE: { + String callId = (String) msg.obj; + mPhone.internalOnHandoverComplete(callId); + break; + } default: break; } @@ -237,6 +243,11 @@ public abstract class InCallService extends Service { public void onHandoverFailed(String callId, int error) { mHandler.obtainMessage(MSG_ON_HANDOVER_FAILED, error, 0, callId).sendToTarget(); } + + @Override + public void onHandoverComplete(String callId) { + mHandler.obtainMessage(MSG_ON_HANDOVER_COMPLETE, callId).sendToTarget(); + } } private Phone.Listener mPhoneListener = new Phone.Listener() { diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java index b5394b9b029..99f94f28b6d 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -230,6 +230,13 @@ public final class Phone { } } + final void internalOnHandoverComplete(String callId) { + Call call = mCallByTelecomCallId.get(callId); + if (call != null) { + call.internalOnHandoverComplete(); + } + } + /** * Called to destroy the phone and cleanup any lingering calls. */ diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 96c6e0a5b74..7e897453ab5 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -378,6 +378,17 @@ public class TelecomManager { */ public static final String EXTRA_IS_HANDOVER = "android.telecom.extra.IS_HANDOVER"; + /** + * When {@code true} indicates that a request to create a new connection is for the purpose of + * a handover. Note: This is used with the + * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)} API as part of the + * internal communication mechanism with the {@link android.telecom.ConnectionService}. It is + * not the same as the legacy {@link #EXTRA_IS_HANDOVER} extra. + * @hide + */ + public static final String EXTRA_IS_HANDOVER_CONNECTION = + "android.telecom.extra.IS_HANDOVER_CONNECTION"; + /** * Parcelable extra used with {@link #EXTRA_IS_HANDOVER} to indicate the source * {@link PhoneAccountHandle} when initiating a handover which {@link ConnectionService} diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl index 02e1ff81806..3dbc8ddc340 100644 --- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl +++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl @@ -104,6 +104,8 @@ oneway interface IConnectionService { void handoverFailed(String callId, in ConnectionRequest request, int error, in Session.Info sessionInfo); + void handoverComplete(String callId, in Session.Info sessionInfo); + void connectionServiceFocusLost(in Session.Info sessionInfo); void connectionServiceFocusGained(in Session.Info sessionInfo); diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl index 110109e6e27..b9563fa7bb1 100644 --- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl +++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl @@ -56,4 +56,6 @@ oneway interface IInCallService { void onRttInitiationFailure(String callId, int reason); void onHandoverFailed(String callId, int error); + + void onHandoverComplete(String callId); } -- GitLab From 35d89ea6ac99f3be2ab37d38d1ae99bd9f0eef3a Mon Sep 17 00:00:00 2001 From: Brad Ebinger Date: Wed, 24 Jan 2018 14:38:05 -0800 Subject: [PATCH 018/416] Modify ImsService API to accomodate compat Modifies the ImsService API to accomodate the ImsService compat layer for older vender implementations Bug: 63987047 Test: Manual, Telephony unit tests Change-Id: Ifb2870414e3d80ef114b3c5fa00c5c5e1aa80b05 --- api/system-current.txt | 4 +- .../android/telephony/ims/ImsReasonInfo.java | 6 +- .../ims/ImsSuppServiceNotification.java | 11 + .../ims/compat/feature/MMTelFeature.java | 25 +- .../compat/stub/ImsCallSessionImplBase.java | 325 +++++++++++++++++- .../ims/compat/stub/ImsConfigImplBase.java | 269 ++++++++++++++- .../telephony/ims/feature/ImsFeature.java | 5 +- .../telephony/ims/feature/MmTelFeature.java | 116 +++++-- .../telephony/ims/stub/ImsConfigImplBase.java | 4 +- 9 files changed, 706 insertions(+), 59 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index 6fa5d8abce9..9f182b4db74 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5483,6 +5483,7 @@ package android.telephony.ims { } public final class ImsSuppServiceNotification implements android.os.Parcelable { + ctor public ImsSuppServiceNotification(int, int, int, int, java.lang.String, java.lang.String[]); method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -5550,7 +5551,6 @@ package android.telephony.ims.feature { public abstract class ImsFeature { ctor public ImsFeature(); method public abstract void changeEnabledCapabilities(android.telephony.ims.feature.CapabilityChangeRequest, android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy); - method public final int getFeatureState(); method public abstract void onFeatureReady(); method public abstract void onFeatureRemoved(); method public final void setFeatureState(int); @@ -5579,10 +5579,12 @@ package android.telephony.ims.feature { method public android.telephony.ims.stub.ImsUtImplBase getUt(); method public final void notifyCapabilitiesStatusChanged(android.telephony.ims.feature.MmTelFeature.MmTelCapabilities); method public final void notifyIncomingCall(android.telephony.ims.stub.ImsCallSessionImplBase, android.os.Bundle); + method public final void notifyVoiceMessageCountUpdate(int); method public void onFeatureReady(); method public void onFeatureRemoved(); method public boolean queryCapabilityConfiguration(int, int); method public final android.telephony.ims.feature.MmTelFeature.MmTelCapabilities queryCapabilityStatus(); + method public void setUiTtyMode(int, android.os.Message); method public int shouldProcessCall(java.lang.String[]); field public static final int PROCESS_CALL_CSFB = 1; // 0x1 field public static final int PROCESS_CALL_EMERGENCY_CSFB = 2; // 0x2 diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java index 52a6ab902d9..7b774910259 100644 --- a/telephony/java/android/telephony/ims/ImsReasonInfo.java +++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java @@ -421,13 +421,13 @@ public final class ImsReasonInfo implements Parcelable { // For main reason code /** @hide */ - public final int mCode; + public int mCode; // For the extra code value; it depends on the code value. /** @hide */ - public final int mExtraCode; + public int mExtraCode; // For the additional message of the reason info. /** @hide */ - public final String mExtraMessage; + public String mExtraMessage; /** @hide */ public ImsReasonInfo() { diff --git a/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java index e6f6f1b021f..efaade82522 100644 --- a/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java +++ b/telephony/java/android/telephony/ims/ImsSuppServiceNotification.java @@ -46,6 +46,17 @@ public final class ImsSuppServiceNotification implements Parcelable { /** List of forwarded numbers, if any */ public final String[] history; + + public ImsSuppServiceNotification(int notificationType, int code, int index, int type, + String number, String[] history) { + this.notificationType = notificationType; + this.code = code; + this.index = index; + this.type = type; + this.number = number; + this.history = history; + } + /** @hide */ public ImsSuppServiceNotification(Parcel in) { notificationType = in.readInt(); diff --git a/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java index 7a1b1b218fa..d3d17f4fb19 100644 --- a/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java +++ b/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java @@ -22,6 +22,7 @@ import android.os.RemoteException; import android.telephony.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; +import com.android.ims.internal.IImsCallSessionListener; import com.android.ims.internal.IImsConfig; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMMTelFeature; @@ -29,6 +30,10 @@ import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsRegistrationListener; import com.android.ims.internal.IImsUt; import android.telephony.ims.ImsCallSession; +import android.telephony.ims.compat.stub.ImsCallSessionImplBase; +import android.telephony.ims.stub.ImsEcbmImplBase; +import android.telephony.ims.stub.ImsMultiEndpointImplBase; +import android.telephony.ims.stub.ImsUtImplBase; /** * Base implementation for MMTel. @@ -110,7 +115,7 @@ public class MMTelFeature extends ImsFeature { public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile) throws RemoteException { synchronized (mLock) { - return MMTelFeature.this.createCallSession(sessionId, profile); + return MMTelFeature.this.createCallSession(sessionId, profile, null); } } @@ -125,7 +130,8 @@ public class MMTelFeature extends ImsFeature { @Override public IImsUt getUtInterface() throws RemoteException { synchronized (mLock) { - return MMTelFeature.this.getUtInterface(); + ImsUtImplBase implBase = MMTelFeature.this.getUtInterface(); + return implBase != null ? implBase.getInterface() : null; } } @@ -153,7 +159,8 @@ public class MMTelFeature extends ImsFeature { @Override public IImsEcbm getEcbmInterface() throws RemoteException { synchronized (mLock) { - return MMTelFeature.this.getEcbmInterface(); + ImsEcbmImplBase implBase = MMTelFeature.this.getEcbmInterface(); + return implBase != null ? implBase.getImsEcbm() : null; } } @@ -167,7 +174,8 @@ public class MMTelFeature extends ImsFeature { @Override public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { synchronized (mLock) { - return MMTelFeature.this.getMultiEndpointInterface(); + ImsMultiEndpointImplBase implBase = MMTelFeature.this.getMultiEndpointInterface(); + return implBase != null ? implBase.getIImsMultiEndpoint() : null; } } }; @@ -281,7 +289,8 @@ public class MMTelFeature extends ImsFeature { * @param sessionId a session id which is obtained from {@link #startSession} * @param profile a call profile to make the call */ - public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile) { + public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile, + IImsCallSessionListener listener) { return null; } @@ -298,7 +307,7 @@ public class MMTelFeature extends ImsFeature { /** * @return The Ut interface for the supplementary service configuration. */ - public IImsUt getUtInterface() { + public ImsUtImplBase getUtInterface() { return null; } @@ -324,7 +333,7 @@ public class MMTelFeature extends ImsFeature { /** * @return The Emergency call-back mode interface for emergency VoLTE calls that support it. */ - public IImsEcbm getEcbmInterface() { + public ImsEcbmImplBase getEcbmInterface() { return null; } @@ -339,7 +348,7 @@ public class MMTelFeature extends ImsFeature { /** * @return MultiEndpoint interface for DEP notifications */ - public IImsMultiEndpoint getMultiEndpointInterface() { + public ImsMultiEndpointImplBase getMultiEndpointInterface() { return null; } diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java index 48a5a75ad03..00cb1e25f66 100644 --- a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java +++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java @@ -16,16 +16,18 @@ package android.telephony.ims.compat.stub; +import android.os.Message; import android.os.RemoteException; -import android.telephony.ims.ImsCallSessionListener; import android.telephony.ims.ImsCallProfile; import android.telephony.ims.ImsConferenceState; import android.telephony.ims.ImsReasonInfo; import android.telephony.ims.ImsStreamMediaProfile; import android.telephony.ims.ImsSuppServiceNotification; +import android.telephony.ims.aidl.IImsCallSessionListener; import com.android.ims.internal.IImsCallSession; -import com.android.ims.internal.IImsCallSessionListener; +import com.android.ims.internal.IImsVideoCallProvider; + import android.telephony.ims.ImsCallSession; /** @@ -37,10 +39,12 @@ import android.telephony.ims.ImsCallSession; * @hide */ -public class ImsCallSessionImplBase extends android.telephony.ims.stub.ImsCallSessionImplBase { +public class ImsCallSessionImplBase extends IImsCallSession.Stub { @Override - public final void setListener(ImsCallSessionListener listener) { + // convert to old implementation of listener + public final void setListener(IImsCallSessionListener listener) + throws RemoteException { setListener(new ImsCallSessionListenerConverter(listener)); } @@ -51,8 +55,312 @@ public class ImsCallSessionImplBase extends android.telephony.ims.stub.ImsCallSe * * @param listener to listen to the session events of this object */ - public void setListener(IImsCallSessionListener listener) { + public void setListener(com.android.ims.internal.IImsCallSessionListener listener) { + + } + + /** + * Closes the object. This {@link ImsCallSessionImplBase} is not usable after being closed. + */ + @Override + public void close() { + + } + + /** + * @return A String containing the unique call ID of this {@link ImsCallSessionImplBase}. + */ + @Override + public String getCallId() { + return null; + } + + /** + * @return The {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is associated + * with. + */ + @Override + public ImsCallProfile getCallProfile() { + return null; + } + + /** + * @return The local {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is + * associated with. + */ + @Override + public ImsCallProfile getLocalCallProfile() { + return null; + } + + /** + * @return The remote {@link ImsCallProfile} that this {@link ImsCallSessionImplBase} is + * associated with. + */ + @Override + public ImsCallProfile getRemoteCallProfile() { + return null; + } + + /** + * @param name The String extra key. + * @return The string extra value associated with the specified property. + */ + @Override + public String getProperty(String name) { + return null; + } + + /** + * @return The {@link ImsCallSessionImplBase} state. + */ + @Override + public int getState() { + return -1; + } + + /** + * @return true if the {@link ImsCallSessionImplBase} is in a call, false otherwise. + */ + @Override + public boolean isInCall() { + return false; + } + + /** + * Mutes or unmutes the mic for the active call. + * + * @param muted true if the call should be muted, false otherwise. + */ + @Override + public void setMute(boolean muted) { + } + + /** + * Initiates an IMS call with the specified number and call profile. + * The session listener set in {@link #setListener(IImsCallSessionListener)} is called back upon + * defined session events. + * Only valid to call when the session state is in + * {@link ImsCallSession.State#IDLE}. + * + * @param callee dialed string to make the call to + * @param profile call profile to make the call with the specified service type, + * call type and media information + * @see {@link ImsCallSession.Listener#callSessionStarted}, + * {@link ImsCallSession.Listener#callSessionStartFailed} + */ + @Override + public void start(String callee, ImsCallProfile profile) { + } + + /** + * Initiates an IMS call with the specified participants and call profile. + * The session listener set in {@link #setListener(IImsCallSessionListener)} is called back upon + * defined session events. + * The method is only valid to call when the session state is in + * {@link ImsCallSession.State#IDLE}. + * + * @param participants participant list to initiate an IMS conference call + * @param profile call profile to make the call with the specified service type, + * call type and media information + * @see {@link ImsCallSession.Listener#callSessionStarted}, + * {@link ImsCallSession.Listener#callSessionStartFailed} + */ + @Override + public void startConference(String[] participants, ImsCallProfile profile) { + } + + /** + * Accepts an incoming call or session update. + * + * @param callType call type specified in {@link ImsCallProfile} to be answered + * @param profile stream media profile {@link ImsStreamMediaProfile} to be answered + * @see {@link ImsCallSession.Listener#callSessionStarted} + */ + @Override + public void accept(int callType, ImsStreamMediaProfile profile) { + } + + /** + * Rejects an incoming call or session update. + * + * @param reason reason code to reject an incoming call, defined in {@link ImsReasonInfo}. + * {@link ImsCallSession.Listener#callSessionStartFailed} + */ + @Override + public void reject(int reason) { + } + /** + * Terminates a call. + * + * @param reason reason code to terminate a call, defined in {@link ImsReasonInfo}. + * + * @see {@link ImsCallSession.Listener#callSessionTerminated} + */ + @Override + public void terminate(int reason) { + } + + /** + * Puts a call on hold. When it succeeds, {@link ImsCallSession.Listener#callSessionHeld} is + * called. + * + * @param profile stream media profile {@link ImsStreamMediaProfile} to hold the call + * @see {@link ImsCallSession.Listener#callSessionHeld}, + * {@link ImsCallSession.Listener#callSessionHoldFailed} + */ + @Override + public void hold(ImsStreamMediaProfile profile) { + } + + /** + * Continues a call that's on hold. When it succeeds, + * {@link ImsCallSession.Listener#callSessionResumed} is called. + * + * @param profile stream media profile with {@link ImsStreamMediaProfile} to resume the call + * @see {@link ImsCallSession.Listener#callSessionResumed}, + * {@link ImsCallSession.Listener#callSessionResumeFailed} + */ + @Override + public void resume(ImsStreamMediaProfile profile) { + } + + /** + * Merges the active and held call. When the merge starts, + * {@link ImsCallSession.Listener#callSessionMergeStarted} is called. + * {@link ImsCallSession.Listener#callSessionMergeComplete} is called if the merge is + * successful, and {@link ImsCallSession.Listener#callSessionMergeFailed} is called if the merge + * fails. + * + * @see {@link ImsCallSession.Listener#callSessionMergeStarted}, + * {@link ImsCallSession.Listener#callSessionMergeComplete}, + * {@link ImsCallSession.Listener#callSessionMergeFailed} + */ + @Override + public void merge() { + } + + /** + * Updates the current call's properties (ex. call mode change: video upgrade / downgrade). + * + * @param callType call type specified in {@link ImsCallProfile} to be updated + * @param profile stream media profile {@link ImsStreamMediaProfile} to be updated + * @see {@link ImsCallSession.Listener#callSessionUpdated}, + * {@link ImsCallSession.Listener#callSessionUpdateFailed} + */ + @Override + public void update(int callType, ImsStreamMediaProfile profile) { + } + + /** + * Extends this call to the conference call with the specified recipients. + * + * @param participants participant list to be invited to the conference call after extending the + * call + * @see {@link ImsCallSession.Listener#callSessionConferenceExtended}, + * {@link ImsCallSession.Listener#callSessionConferenceExtendFailed} + */ + @Override + public void extendToConference(String[] participants) { + } + + /** + * Requests the conference server to invite an additional participants to the conference. + * + * @param participants participant list to be invited to the conference call + * @see {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestDelivered}, + * {@link ImsCallSession.Listener#callSessionInviteParticipantsRequestFailed} + */ + @Override + public void inviteParticipants(String[] participants) { + } + + /** + * Requests the conference server to remove the specified participants from the conference. + * + * @param participants participant list to be removed from the conference call + * @see {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestDelivered}, + * {@link ImsCallSession.Listener#callSessionRemoveParticipantsRequestFailed} + */ + @Override + public void removeParticipants(String[] participants) { + } + + /** + * Sends a DTMF code. According to RFC 2833, + * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, + * and event flash to 16. Currently, event flash is not supported. + * + * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. + */ + @Override + public void sendDtmf(char c, Message result) { + } + + /** + * Start a DTMF code. According to RFC 2833, + * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15, + * and event flash to 16. Currently, event flash is not supported. + * + * @param c the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs. + */ + @Override + public void startDtmf(char c) { + } + + /** + * Stop a DTMF code. + */ + @Override + public void stopDtmf() { + } + + /** + * Sends an USSD message. + * + * @param ussdMessage USSD message to send + */ + @Override + public void sendUssd(String ussdMessage) { + } + + @Override + public IImsVideoCallProvider getVideoCallProvider() { + return null; + } + + /** + * Determines if the current session is multiparty. + * @return {@code True} if the session is multiparty. + */ + @Override + public boolean isMultiparty() { + return false; + } + + /** + * Device issues RTT modify request + * @param toProfile The profile with requested changes made + */ + @Override + public void sendRttModifyRequest(ImsCallProfile toProfile) { + } + + /** + * Device responds to Remote RTT modify request + * @param status true if the the request was accepted or false of the request is defined. + */ + @Override + public void sendRttModifyResponse(boolean status) { + } + + /** + * Device sends RTT message + * @param rttMessage RTT message to be sent + */ + @Override + public void sendRttMessage(String rttMessage) { } /** @@ -61,11 +369,12 @@ public class ImsCallSessionImplBase extends android.telephony.ims.stub.ImsCallSe * "new" version of the Listener android.telephony.ims.ImsCallSessionListener when calling * back to the framework. */ - private class ImsCallSessionListenerConverter extends IImsCallSessionListener.Stub { + private class ImsCallSessionListenerConverter + extends com.android.ims.internal.IImsCallSessionListener.Stub { - private final ImsCallSessionListener mNewListener; + private final IImsCallSessionListener mNewListener; - public ImsCallSessionListenerConverter(ImsCallSessionListener listener) { + public ImsCallSessionListenerConverter(IImsCallSessionListener listener) { mNewListener = listener; } diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java index b5417e77c00..2c325ba8e13 100644 --- a/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java +++ b/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 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. @@ -16,15 +16,23 @@ package android.telephony.ims.compat.stub; +import android.content.Context; +import android.content.Intent; import android.os.RemoteException; +import android.util.Log; import com.android.ims.ImsConfig; import com.android.ims.ImsConfigListener; import com.android.ims.internal.IImsConfig; +import com.android.internal.annotations.VisibleForTesting; + +import java.lang.ref.WeakReference; +import java.util.HashMap; + /** - * Base implementation of ImsConfig, which implements stub versions of the methods - * in the IImsConfig AIDL. Override the methods that your implementation of ImsConfig supports. + * Base implementation of ImsConfig. + * Override the methods that your implementation of ImsConfig supports. * * DO NOT remove or change the existing APIs, only add new ones to this Base implementation or you * will break other implementations of ImsConfig maintained by other ImsServices. @@ -34,10 +42,25 @@ import com.android.ims.internal.IImsConfig; * 1) Items provisioned by the operator. * 2) Items configured by user. Mainly service feature class. * + * The inner class {@link ImsConfigStub} implements methods of IImsConfig AIDL interface. + * The IImsConfig AIDL interface is called by ImsConfig, which may exist in many other processes. + * ImsConfigImpl access to the configuration parameters may be arbitrarily slow, especially in + * during initialization, or times when a lot of configuration parameters are being set/get + * (such as during boot up or SIM card change). By providing a cache in ImsConfigStub, we can speed + * up access to these configuration parameters, so a query to the ImsConfigImpl does not have to be + * performed every time. * @hide */ -public class ImsConfigImplBase extends IImsConfig.Stub { +public class ImsConfigImplBase { + + static final private String TAG = "ImsConfigImplBase"; + + ImsConfigStub mImsConfigStub; + + public ImsConfigImplBase(Context context) { + mImsConfigStub = new ImsConfigStub(this, context); + } /** * Gets the value for ims service/capabilities parameters from the provisioned @@ -46,7 +69,6 @@ public class ImsConfigImplBase extends IImsConfig.Stub { * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. * @return value in Integer format. */ - @Override public int getProvisionedValue(int item) throws RemoteException { return -1; } @@ -58,7 +80,6 @@ public class ImsConfigImplBase extends IImsConfig.Stub { * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. * @return value in String format. */ - @Override public String getProvisionedStringValue(int item) throws RemoteException { return null; } @@ -72,7 +93,6 @@ public class ImsConfigImplBase extends IImsConfig.Stub { * @param value in Integer format. * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. */ - @Override public int setProvisionedValue(int item, int value) throws RemoteException { return ImsConfig.OperationStatusConstants.FAILED; } @@ -86,7 +106,6 @@ public class ImsConfigImplBase extends IImsConfig.Stub { * @param value in String format. * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. */ - @Override public int setProvisionedStringValue(int item, String value) throws RemoteException { return ImsConfig.OperationStatusConstants.FAILED; } @@ -100,7 +119,6 @@ public class ImsConfigImplBase extends IImsConfig.Stub { * @param network as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX. * @param listener feature value returned asynchronously through listener. */ - @Override public void getFeatureValue(int feature, int network, ImsConfigListener listener) throws RemoteException { } @@ -115,7 +133,6 @@ public class ImsConfigImplBase extends IImsConfig.Stub { * @param value as defined in com.android.ims.ImsConfig#FeatureValueConstants. * @param listener, provided if caller needs to be notified for set result. */ - @Override public void setFeatureValue(int feature, int network, int value, ImsConfigListener listener) throws RemoteException { } @@ -124,7 +141,6 @@ public class ImsConfigImplBase extends IImsConfig.Stub { * Gets the value for IMS VoLTE provisioned. * This should be the same as the operator provisioned value if applies. */ - @Override public boolean getVolteProvisioned() throws RemoteException { return false; } @@ -134,7 +150,6 @@ public class ImsConfigImplBase extends IImsConfig.Stub { * * @param listener Video quality value returned asynchronously through listener. */ - @Override public void getVideoQuality(ImsConfigListener listener) throws RemoteException { } @@ -144,7 +159,233 @@ public class ImsConfigImplBase extends IImsConfig.Stub { * @param quality, defines the value of video quality. * @param listener, provided if caller needs to be notified for set result. */ - @Override public void setVideoQuality(int quality, ImsConfigListener listener) throws RemoteException { } -} + + public IImsConfig getIImsConfig() { return mImsConfigStub; } + + /** + * Updates provisioning value and notifies the framework of the change. + * Doesn't call #setProvisionedValue and assumes the result succeeded. + * This should only be used by modem when they implicitly changed provisioned values. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in Integer format. + */ + public final void notifyProvisionedValueChanged(int item, int value) { + mImsConfigStub.updateCachedValue(item, value, true); + } + + /** + * Updates provisioning value and notifies the framework of the change. + * Doesn't call #setProvisionedValue and assumes the result succeeded. + * This should only be used by modem when they implicitly changed provisioned values. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in String format. + */ + public final void notifyProvisionedValueChanged(int item, String value) { + mImsConfigStub.updateCachedValue(item, value, true); + } + + /** + * Implements the IImsConfig AIDL interface, which is called by potentially many processes + * in order to get/set configuration parameters. + * + * It holds an object of ImsConfigImplBase class which is usually extended by ImsConfigImpl + * with actual implementations from vendors. This class caches provisioned values from + * ImsConfigImpl layer because queries through ImsConfigImpl can be slow. When query goes in, + * it first checks cache layer. If missed, it will call the vendor implementation of + * ImsConfigImplBase API. + * and cache the return value if the set succeeds. + * + * Provides APIs to get/set the IMS service feature/capability/parameters. + * The config items include: + * 1) Items provisioned by the operator. + * 2) Items configured by user. Mainly service feature class. + * + * @hide + */ + @VisibleForTesting + static public class ImsConfigStub extends IImsConfig.Stub { + Context mContext; + WeakReference mImsConfigImplBaseWeakReference; + private HashMap mProvisionedIntValue = new HashMap<>(); + private HashMap mProvisionedStringValue = new HashMap<>(); + + @VisibleForTesting + public ImsConfigStub(ImsConfigImplBase imsConfigImplBase, Context context) { + mContext = context; + mImsConfigImplBaseWeakReference = + new WeakReference(imsConfigImplBase); + } + + /** + * Gets the value for ims service/capabilities parameters. It first checks its local cache, + * if missed, it will call ImsConfigImplBase.getProvisionedValue. + * Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @return value in Integer format. + */ + @Override + public synchronized int getProvisionedValue(int item) throws RemoteException { + if (mProvisionedIntValue.containsKey(item)) { + return mProvisionedIntValue.get(item); + } else { + int retVal = getImsConfigImpl().getProvisionedValue(item); + if (retVal != ImsConfig.OperationStatusConstants.UNKNOWN) { + updateCachedValue(item, retVal, false); + } + return retVal; + } + } + + /** + * Gets the value for ims service/capabilities parameters. It first checks its local cache, + * if missed, it will call #ImsConfigImplBase.getProvisionedValue. + * Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @return value in String format. + */ + @Override + public synchronized String getProvisionedStringValue(int item) throws RemoteException { + if (mProvisionedIntValue.containsKey(item)) { + return mProvisionedStringValue.get(item); + } else { + String retVal = getImsConfigImpl().getProvisionedStringValue(item); + if (retVal != null) { + updateCachedValue(item, retVal, false); + } + return retVal; + } + } + + /** + * Sets the value for IMS service/capabilities parameters by the operator device + * management entity. It sets the config item value in the provisioned storage + * from which the master value is derived, and write it into local cache. + * Synchronous blocking call. + * + * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in Integer format. + * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + */ + @Override + public synchronized int setProvisionedValue(int item, int value) throws RemoteException { + mProvisionedIntValue.remove(item); + int retVal = getImsConfigImpl().setProvisionedValue(item, value); + if (retVal == ImsConfig.OperationStatusConstants.SUCCESS) { + updateCachedValue(item, value, true); + } else { + Log.d(TAG, "Set provision value of " + item + + " to " + value + " failed with error code " + retVal); + } + + return retVal; + } + + /** + * Sets the value for IMS service/capabilities parameters by the operator device + * management entity. It sets the config item value in the provisioned storage + * from which the master value is derived, and write it into local cache. + * Synchronous blocking call. + * + * @param item as defined in com.android.ims.ImsConfig#ConfigConstants. + * @param value in String format. + * @return as defined in com.android.ims.ImsConfig#OperationStatusConstants. + */ + @Override + public synchronized int setProvisionedStringValue(int item, String value) + throws RemoteException { + mProvisionedStringValue.remove(item); + int retVal = getImsConfigImpl().setProvisionedStringValue(item, value); + if (retVal == ImsConfig.OperationStatusConstants.SUCCESS) { + updateCachedValue(item, value, true); + } + + return retVal; + } + + /** + * Wrapper function to call ImsConfigImplBase.getFeatureValue. + */ + @Override + public void getFeatureValue(int feature, int network, ImsConfigListener listener) + throws RemoteException { + getImsConfigImpl().getFeatureValue(feature, network, listener); + } + + /** + * Wrapper function to call ImsConfigImplBase.setFeatureValue. + */ + @Override + public void setFeatureValue(int feature, int network, int value, ImsConfigListener listener) + throws RemoteException { + getImsConfigImpl().setFeatureValue(feature, network, value, listener); + } + + /** + * Wrapper function to call ImsConfigImplBase.getVolteProvisioned. + */ + @Override + public boolean getVolteProvisioned() throws RemoteException { + return getImsConfigImpl().getVolteProvisioned(); + } + + /** + * Wrapper function to call ImsConfigImplBase.getVideoQuality. + */ + @Override + public void getVideoQuality(ImsConfigListener listener) throws RemoteException { + getImsConfigImpl().getVideoQuality(listener); + } + + /** + * Wrapper function to call ImsConfigImplBase.setVideoQuality. + */ + @Override + public void setVideoQuality(int quality, ImsConfigListener listener) + throws RemoteException { + getImsConfigImpl().setVideoQuality(quality, listener); + } + + private ImsConfigImplBase getImsConfigImpl() throws RemoteException { + ImsConfigImplBase ref = mImsConfigImplBaseWeakReference.get(); + if (ref == null) { + throw new RemoteException("Fail to get ImsConfigImpl"); + } else { + return ref; + } + } + + private void sendImsConfigChangedIntent(int item, int value) { + sendImsConfigChangedIntent(item, Integer.toString(value)); + } + + private void sendImsConfigChangedIntent(int item, String value) { + Intent configChangedIntent = new Intent(ImsConfig.ACTION_IMS_CONFIG_CHANGED); + configChangedIntent.putExtra(ImsConfig.EXTRA_CHANGED_ITEM, item); + configChangedIntent.putExtra(ImsConfig.EXTRA_NEW_VALUE, value); + if (mContext != null) { + mContext.sendBroadcast(configChangedIntent); + } + } + + protected synchronized void updateCachedValue(int item, int value, boolean notifyChange) { + mProvisionedIntValue.put(item, value); + if (notifyChange) { + sendImsConfigChangedIntent(item, value); + } + } + + protected synchronized void updateCachedValue( + int item, String value, boolean notifyChange) { + mProvisionedStringValue.put(item, value); + if (notifyChange) { + sendImsConfigChangedIntent(item, value); + } + } + } +} \ No newline at end of file diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java index a23ce6316a9..bfdd4533275 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -363,8 +363,9 @@ public abstract class ImsFeature { /** * @return The current state of the feature, defined as {@link #STATE_UNAVAILABLE}, * {@link #STATE_INITIALIZING}, or {@link #STATE_READY}. + * @hide */ - public final int getFeatureState() { + public int getFeatureState() { synchronized (mLock) { return mState; } @@ -489,7 +490,7 @@ public abstract class ImsFeature { */ @VisibleForTesting public final void requestChangeEnabledCapabilities(CapabilityChangeRequest request, - IImsCapabilityCallback c) throws RemoteException { + IImsCapabilityCallback c) { if (request == null) { throw new IllegalArgumentException( "ImsFeature#requestChangeEnabledCapabilities called with invalid params."); diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java index 2baf076d391..09267fc2554 100644 --- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -70,7 +70,11 @@ public class MmTelFeature extends ImsFeature { @Override public int getFeatureState() throws RemoteException { synchronized (mLock) { - return MmTelFeature.this.getFeatureState(); + try { + return MmTelFeature.this.getFeatureState(); + } catch (Exception e) { + throw new RemoteException(e.getMessage()); + } } } @@ -79,15 +83,18 @@ public class MmTelFeature extends ImsFeature { public ImsCallProfile createCallProfile(int callSessionType, int callType) throws RemoteException { synchronized (mLock) { - return MmTelFeature.this.createCallProfile(callSessionType, callType); + try { + return MmTelFeature.this.createCallProfile(callSessionType, callType); + } catch (Exception e) { + throw new RemoteException(e.getMessage()); + } } } @Override public IImsCallSession createCallSession(ImsCallProfile profile) throws RemoteException { synchronized (mLock) { - ImsCallSessionImplBase s = MmTelFeature.this.createCallSession(profile); - return s != null ? s.getServiceImpl() : null; + return createCallSessionInterface(profile); } } @@ -101,30 +108,32 @@ public class MmTelFeature extends ImsFeature { @Override public IImsUt getUtInterface() throws RemoteException { synchronized (mLock) { - return MmTelFeature.this.getUt().getInterface(); + return MmTelFeature.this.getUtInterface(); } } @Override public IImsEcbm getEcbmInterface() throws RemoteException { synchronized (mLock) { - ImsEcbmImplBase ecbm = MmTelFeature.this.getEcbm(); - return ecbm != null ? ecbm.getImsEcbm() : null; + return MmTelFeature.this.getEcbmInterface(); } } @Override public void setUiTtyMode(int uiTtyMode, Message onCompleteMessage) throws RemoteException { synchronized (mLock) { - MmTelFeature.this.setUiTtyMode(uiTtyMode, onCompleteMessage); + try { + MmTelFeature.this.setUiTtyMode(uiTtyMode, onCompleteMessage); + } catch (Exception e) { + throw new RemoteException(e.getMessage()); + } } } @Override public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { synchronized (mLock) { - ImsMultiEndpointImplBase multiEndPoint = MmTelFeature.this.getMultiEndpoint(); - return multiEndPoint != null ? multiEndPoint.getIImsMultiEndpoint() : null; + return MmTelFeature.this.getMultiEndpointInterface(); } } @@ -317,18 +326,18 @@ public class MmTelFeature extends ImsFeature { } /** - * To be returned by {@link #shouldProcessCall(Uri[])} when the ImsService should process the + * To be returned by {@link #shouldProcessCall(String[])} when the ImsService should process the * outgoing call as IMS. */ public static final int PROCESS_CALL_IMS = 0; /** - * To be returned by {@link #shouldProcessCall(Uri[])} when the telephony framework should not - * process the outgoing NON_EMERGENCY call as IMS and should instead use circuit switch. + * To be returned by {@link #shouldProcessCall(String[])} when the telephony framework should + * not process the outgoing NON_EMERGENCY call as IMS and should instead use circuit switch. */ public static final int PROCESS_CALL_CSFB = 1; /** - * To be returned by {@link #shouldProcessCall(Uri[])} when the telephony framework should not - * process the outgoing EMERGENCY call as IMS and should instead use circuit switch. + * To be returned by {@link #shouldProcessCall(String[])} when the telephony framework should + * not process the outgoing EMERGENCY call as IMS and should instead use circuit switch. */ public static final int PROCESS_CALL_EMERGENCY_CSFB = 2; @@ -401,10 +410,7 @@ public class MmTelFeature extends ImsFeature { /** * Notify the framework of an incoming call. * @param c The {@link ImsCallSessionImplBase} of the new incoming call. - * - * @throws RuntimeException if the connection to the framework is not available. If this - * happens, the call should be no longer considered active and should be cleaned up. - * */ + */ public final void notifyIncomingCall(ImsCallSessionImplBase c, Bundle extras) { synchronized (mLock) { if (mListener == null) { @@ -418,6 +424,40 @@ public class MmTelFeature extends ImsFeature { } } + /** + * + * @hide + */ + public final void notifyIncomingCallSession(IImsCallSession c, Bundle extras) { + synchronized (mLock) { + if (mListener == null) { + throw new IllegalStateException("Session is not available."); + } + try { + mListener.onIncomingCall(c, extras); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Notify the framework of a change in the Voice Message count. + * @link count the new Voice Message count. + */ + public final void notifyVoiceMessageCountUpdate(int count) { + synchronized (mLock) { + if (mListener == null) { + throw new IllegalStateException("Session is not available."); + } + try { + mListener.onVoiceMessageCountUpdate(count); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + } + /** * Provides the MmTelFeature with the ability to return the framework Capability Configuration * for a provided Capability. If the framework calls {@link #changeEnabledCapabilities} and @@ -473,6 +513,15 @@ public class MmTelFeature extends ImsFeature { return null; } + /** + * @hide + */ + public IImsCallSession createCallSessionInterface(ImsCallProfile profile) + throws RemoteException { + ImsCallSessionImplBase s = MmTelFeature.this.createCallSession(profile); + return s != null ? s.getServiceImpl() : null; + } + /** * Creates an {@link ImsCallSession} with the specified call profile. * Use other methods, if applicable, instead of interacting with @@ -489,7 +538,7 @@ public class MmTelFeature extends ImsFeature { * Called by the framework to determine if the outgoing call, designated by the outgoing * {@link Uri}s, should be processed as an IMS call or CSFB call. * @param numbers An array of {@link String}s that will be used for placing the call. There can - * be multiple {@link Strings}s listed in the case when we want to place an outgoing + * be multiple {@link String}s listed in the case when we want to place an outgoing * call as a conference. * @return a {@link ProcessCallResult} to the framework, which will be used to determine if the * call wil lbe placed over IMS or via CSFB. @@ -498,6 +547,31 @@ public class MmTelFeature extends ImsFeature { return PROCESS_CALL_IMS; } + /** + * + * @hide + */ + protected IImsUt getUtInterface() throws RemoteException { + ImsUtImplBase utImpl = getUt(); + return utImpl != null ? utImpl.getInterface() : null; + } + + /** + * @hide + */ + protected IImsEcbm getEcbmInterface() throws RemoteException { + ImsEcbmImplBase ecbmImpl = getEcbm(); + return ecbmImpl != null ? ecbmImpl.getImsEcbm() : null; + } + + /** + * @hide + */ + public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException { + ImsMultiEndpointImplBase multiendpointImpl = getMultiEndpoint(); + return multiendpointImpl != null ? multiendpointImpl.getIImsMultiEndpoint() : null; + } + /** * @return The {@link ImsUtImplBase} Ut interface implementation for the supplementary service * configuration. @@ -534,7 +608,7 @@ public class MmTelFeature extends ImsFeature { * {@link TelecomManager#TTY_MODE_VCO} * @param onCompleteMessage A {@link Message} to be used when the mode has been set. */ - void setUiTtyMode(int mode, Message onCompleteMessage) { + public void setUiTtyMode(int mode, Message onCompleteMessage) { // Base Implementation - Should be overridden } diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java index 412bef0cc08..dcd7ea714f8 100644 --- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java @@ -148,7 +148,7 @@ public class ImsConfigImplBase { mProvisionedIntValue.remove(item); int retVal = getImsConfigImpl().setConfig(item, value); if (retVal == ImsConfig.OperationStatusConstants.SUCCESS) { - updateCachedValue(item, retVal, true); + updateCachedValue(item, value, true); } else { Log.d(TAG, "Set provision value of " + item + " to " + value + " failed with error code " + retVal); @@ -174,7 +174,7 @@ public class ImsConfigImplBase { mProvisionedStringValue.remove(item); int retVal = getImsConfigImpl().setConfig(item, value); if (retVal == ImsConfig.OperationStatusConstants.SUCCESS) { - updateCachedValue(item, retVal, true); + updateCachedValue(item, value, true); } return retVal; -- GitLab From 15e7c62e45f6c370d5277a8fca5eea58e2d6224b Mon Sep 17 00:00:00 2001 From: Fyodor Kupolov Date: Thu, 25 Jan 2018 18:14:53 -0800 Subject: [PATCH 019/416] Use SQLiteDatabase.deleteDatabase to delete a temp database Deleting individual files wasn't guaranteed to work. And with Compatibility WAL undeleted -wal file can affect the state of the database created later. Test: manual Bug: 72457712 Change-Id: I1bbc3fb0d67846ceff690cbdf1268b34c71d0f4f --- .../com/android/providers/settings/DatabaseHelper.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index 74281492413..1dc8e46a694 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -87,7 +87,6 @@ class DatabaseHelper extends SQLiteOpenHelper { private static final HashSet mValidTables = new HashSet(); - private static final String DATABASE_JOURNAL_SUFFIX = "-journal"; private static final String DATABASE_BACKUP_SUFFIX = "-backup"; private static final String TABLE_SYSTEM = "system"; @@ -148,12 +147,7 @@ class DatabaseHelper extends SQLiteOpenHelper { } File databaseFile = mContext.getDatabasePath(getDatabaseName()); if (databaseFile.exists()) { - databaseFile.delete(); - } - File databaseJournalFile = mContext.getDatabasePath(getDatabaseName() - + DATABASE_JOURNAL_SUFFIX); - if (databaseJournalFile.exists()) { - databaseJournalFile.delete(); + SQLiteDatabase.deleteDatabase(databaseFile); } } -- GitLab From 5185d71470edb287e5b6288411ccfc8fa0a41da2 Mon Sep 17 00:00:00 2001 From: weijuncheng Date: Fri, 26 Jan 2018 13:05:40 +0800 Subject: [PATCH 020/416] Add DENSITY_440 Add 440dpi as a supported screen density Bug: 72424600 Test: run android.dpi.cts.ConfigurationTest#testScreenConfiguration and android.app.cts.ActivityManagerMemoryClassTest#testGetMemoryClass Change-Id: I0dbf998ae02515a97f0d5668eeedc7098da4cca4 Signed-off-by: weijuncheng --- api/current.txt | 1 + core/java/android/util/DisplayMetrics.java | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/api/current.txt b/api/current.txt index fba245d386e..87fe9641196 100644 --- a/api/current.txt +++ b/api/current.txt @@ -44022,6 +44022,7 @@ package android.util { field public static final int DENSITY_360 = 360; // 0x168 field public static final int DENSITY_400 = 400; // 0x190 field public static final int DENSITY_420 = 420; // 0x1a4 + field public static final int DENSITY_440 = 440; // 0x1b8 field public static final int DENSITY_560 = 560; // 0x230 field public static final int DENSITY_DEFAULT = 160; // 0xa0 field public static final int DENSITY_DEVICE_STABLE; diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index b7099b642e9..13de172c811 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -119,6 +119,14 @@ public class DisplayMetrics { */ public static final int DENSITY_420 = 420; + /** + * Intermediate density for screens that sit somewhere between + * {@link #DENSITY_XHIGH} (320 dpi) and {@link #DENSITY_XXHIGH} (480 dpi). + * This is not a density that applications should target, instead relying + * on the system to scale their {@link #DENSITY_XXHIGH} assets for them. + */ + public static final int DENSITY_440 = 440; + /** * Standard quantized DPI for extra-extra-high-density screens. */ -- GitLab From ac1928b7a01194985cbb7caaaa8f7889811fe339 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 17 Jan 2018 11:34:43 -0500 Subject: [PATCH 021/416] Respect the EXIF orientation in ImageDecoder Bug: 63909536 Test: CTS: Ia50449b9b2e3c965bbd7fb95901b239f77990344 Change-Id: I154c0e5c07968b108df5be279860f0c35441f5cf --- core/jni/android/graphics/ImageDecoder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index 743a7efa2b2..e2ce1a465fd 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -74,7 +74,8 @@ static jobject native_create(JNIEnv* env, std::unique_ptr stream) { // FIXME: Avoid parsing the whole image? const bool animated = codec->getFrameCount() > 1; - decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec)); + decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec), + SkAndroidCodec::ExifOrientationBehavior::kRespect); if (!decoder->mCodec.get()) { doThrowIOE(env, "Could not create AndroidCodec"); return nullptr; -- GitLab From 86cc9b7f5cebb2e1e7ed214c57d6132a0ce50959 Mon Sep 17 00:00:00 2001 From: Sundeep Ghuman Date: Wed, 24 Jan 2018 20:08:39 -0800 Subject: [PATCH 022/416] Execute all callbacks on the MainThread. This is a precursory step which will allow us to delete the double handlers that currently exist in WifiTracker. Bug: 37674366 Test: runtest --path frameworks/base/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java Change-Id: Ieea1af712b80ea6ede358d7a1004e489e11c6009 --- .../android/settingslib/wifi/WifiTracker.java | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 1ac56a9de98..c1dce591e83 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -220,7 +220,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro mWifiManager = wifiManager; mIncludeSaved = includeSaved; mIncludeScans = includeScans; - mListener = wifiListener; + mListener = new WifiListenerWrapper(wifiListener); mConnectivityManager = connectivityManager; // check if verbose logging has been turned on or off @@ -1052,6 +1052,39 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } } + /** + * Wraps the given {@link WifiListener} instance and executes it's methods on the Main Thread. + * + *

This mechanism allows us to no longer need a separate MainHandler and WorkHandler, which + * were previously both performing work, while avoiding errors which occur from executing + * callbacks which manipulate UI elements from a different thread than the MainThread. + */ + private static class WifiListenerWrapper implements WifiListener { + + private final Handler mHandler; + private final WifiListener mDelegatee; + + public WifiListenerWrapper(WifiListener listener) { + mHandler = new Handler(Looper.getMainLooper()); + mDelegatee = listener; + } + + @Override + public void onWifiStateChanged(int state) { + mHandler.post(() -> mDelegatee.onWifiStateChanged(state)); + } + + @Override + public void onConnectedChanged() { + mHandler.post(() -> mDelegatee.onConnectedChanged()); + } + + @Override + public void onAccessPointsChanged() { + mHandler.post(() -> mDelegatee.onAccessPointsChanged()); + } + } + public interface WifiListener { /** * Called when the state of Wifi has changed, the state will be one of -- GitLab From 372938942ffc1a7cc3e87cb5be524f234077f5c6 Mon Sep 17 00:00:00 2001 From: Primiano Tucci Date: Fri, 26 Jan 2018 17:28:58 +0000 Subject: [PATCH 023/416] DropboxManager: Add err message if passing an invalid fd to addFile() Follow up to I076bfd3180fb9b4baff7e1bae2e611419061b2a7. Adds an error message if passing -1 to addFile(int fd) Change-Id: I73a8d88f12b14bc28ea3bc3782a9df7d96d53c92 Test: builds --- libs/services/src/os/DropBoxManager.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp index e8e34d7c4cb..95246a0d270 100644 --- a/libs/services/src/os/DropBoxManager.cpp +++ b/libs/services/src/os/DropBoxManager.cpp @@ -185,6 +185,11 @@ DropBoxManager::addFile(const String16& tag, const string& filename, int flags) Status DropBoxManager::addFile(const String16& tag, int fd, int flags) { + if (fd == -1) { + string message("invalid fd (-1) passed to to addFile"); + ALOGW("DropboxManager: %s", message.c_str()); + return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, message.c_str()); + } Entry entry(tag, flags, fd); return add(entry); } @@ -201,4 +206,3 @@ DropBoxManager::add(const Entry& entry) } }} // namespace android::os - -- GitLab From d967610fb865ce2ac5f8c379a13e21feee98fbe0 Mon Sep 17 00:00:00 2001 From: Svet Ganov Date: Wed, 6 Dec 2017 23:45:38 -0800 Subject: [PATCH 024/416] Use correct user id for permission check for instant foreground service Test: cts-tradefed run cts-dev -m CtsAppSecurityHostTestCases -t android.appsecurity.cts.EphemeralTest#testStartForegrondService bug: 68275646 Bug: 71366502 Change-Id: I196522c49ae8a7e0ec07bf631f04bae51e96db5b cherry pick from: https://android-review.googlesource.com/c/platform/frameworks/base/+/559340 (cherry picked from commit e32c238ce76151dd6221e6762f841c8f721c45f7) Change-Id: Idfeae038d42b9dee9f6f57203f2bdb0764f51877 --- services/core/java/com/android/server/am/ActiveServices.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 90ad8a5d013..9d823a726af 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1043,8 +1043,8 @@ public final class ActiveServices { try { if (AppGlobals.getPackageManager().checkPermission( android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE, - r.appInfo.packageName, - r.appInfo.uid) != PackageManager.PERMISSION_GRANTED) { + r.appInfo.packageName, UserHandle.getUserId(r.appInfo.uid)) + != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Instant app " + r.appInfo.packageName + " does not have permission to create foreground" + "services"); -- GitLab From 8980958454ced6db001452e1f5f5811a944716d4 Mon Sep 17 00:00:00 2001 From: Hyunyoung Song Date: Tue, 27 Jun 2017 16:56:41 -0700 Subject: [PATCH 025/416] AdaptiveIconDrawable should not update layer bounds when bound is empty Test: build succeeds. Also, setting tint mode does not result in error. Bug: 37682281 Bug: 69969749 Change-Id: I5991b8e58514a2130a793a5edb90baeafae9b148 --- .../java/android/graphics/drawable/AdaptiveIconDrawable.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java index 8616d58a031..1d0cfa5ff08 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 +} -- GitLab From 8d157e38f201b05c2a73c798d9a9a9e94f7975f3 Mon Sep 17 00:00:00 2001 From: Sundeep Ghuman Date: Wed, 24 Jan 2018 20:32:35 -0800 Subject: [PATCH 026/416] Remove unused variables and resulting conditionals. WifiTracker always is used in 'includeScans' mode, and is never used in includeSaved mode. The former variable was not even referenced. The original functionality of includeSaved true and includeScans mode was for rendering the Saved Access Points page, which is now provided by another utility. All calling apps currently filter out unreachable saved networks they receive from WifiTracker. Subsequent CLs will modify the updateAccessPointsLocked loop to simply insert config data into existing APs, thus reducing the complexity of thi class. Bug: 68030053 Test: runtest --path frameworks/base/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/WifiTrackerTest.java Change-Id: I7adb6de2e700c48543d7a43419527052f2909cc0 --- .../android/settingslib/wifi/WifiTracker.java | 47 +++++++------------ .../settingslib/wifi/WifiTrackerTest.java | 2 - 2 files changed, 17 insertions(+), 32 deletions(-) diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index c1dce591e83..810d941111e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -92,8 +92,6 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro * and used so as to assist with in-the-field WiFi connectivity debugging */ public static boolean sVerboseLogging; - // TODO(b/36733768): Remove flag includeSaved - // TODO: Allow control of this? // Combo scans can take 5-6s to complete - set to 10s. private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000; @@ -106,8 +104,6 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro private final NetworkRequest mNetworkRequest; private final AtomicBoolean mConnected = new AtomicBoolean(false); private final WifiListener mListener; - private final boolean mIncludeSaved; - private final boolean mIncludeScans; @VisibleForTesting MainHandler mMainHandler; @VisibleForTesting WorkHandler mWorkHandler; private HandlerThread mWorkThread; @@ -189,16 +185,18 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro @Deprecated public WifiTracker(Context context, WifiListener wifiListener, boolean includeSaved, boolean includeScans) { - this(context, wifiListener, includeSaved, includeScans, + this(context, wifiListener, context.getSystemService(WifiManager.class), context.getSystemService(ConnectivityManager.class), context.getSystemService(NetworkScoreManager.class), newIntentFilter()); } + // TODO(Sghuman): Clean up includeSaved and includeScans from all constructors and linked + // calling apps once IC window is complete public WifiTracker(Context context, WifiListener wifiListener, @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) { - this(context, wifiListener, includeSaved, includeScans, + this(context, wifiListener, context.getSystemService(WifiManager.class), context.getSystemService(ConnectivityManager.class), context.getSystemService(NetworkScoreManager.class), @@ -208,18 +206,12 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro @VisibleForTesting WifiTracker(Context context, WifiListener wifiListener, - boolean includeSaved, boolean includeScans, WifiManager wifiManager, ConnectivityManager connectivityManager, NetworkScoreManager networkScoreManager, IntentFilter filter) { - if (!includeSaved && !includeScans) { - throw new IllegalArgumentException("Must include either saved or scans"); - } mContext = context; mMainHandler = new MainHandler(Looper.getMainLooper()); mWifiManager = wifiManager; - mIncludeSaved = includeSaved; - mIncludeScans = includeScans; mListener = new WifiListenerWrapper(wifiListener); mConnectivityManager = connectivityManager; @@ -571,26 +563,21 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro if (mLastInfo != null && mLastNetworkInfo != null) { accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); } - if (mIncludeSaved) { - // If saved network not present in scan result then set its Rssi to - // UNREACHABLE_RSSI - boolean apFound = false; - for (ScanResult result : results) { - if (result.SSID.equals(accessPoint.getSsidStr())) { - apFound = true; - break; - } - } - if (!apFound) { - accessPoint.setUnreachable(); + + // If saved network not present in scan result then set its Rssi to + // UNREACHABLE_RSSI + boolean apFound = false; + for (ScanResult result : results) { + if (result.SSID.equals(accessPoint.getSsidStr())) { + apFound = true; + break; } - accessPoints.add(accessPoint); - existingApMap.put(accessPoint.getSsidStr(), accessPoint); - } else { - // If we aren't using saved networks, drop them into the cache so that - // we have access to their saved info. - cachedAccessPoints.add(accessPoint); } + if (!apFound) { + accessPoint.setUnreachable(); + } + accessPoints.add(accessPoint); + existingApMap.put(accessPoint.getSsidStr(), accessPoint); } } 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 b36dda9deec..8362ec2270f 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 @@ -281,8 +281,6 @@ public class WifiTrackerTest { final WifiTracker wifiTracker = new WifiTracker( mContext, mockWifiListener, - true, - true, mockWifiManager, mockConnectivityManager, mockNetworkScoreManager, -- GitLab From e56980401bc1b276bb1818eb280b44a17bd6be4f Mon Sep 17 00:00:00 2001 From: Fyodor Kupolov Date: Fri, 26 Jan 2018 14:24:37 -0800 Subject: [PATCH 027/416] Make MATCH_FACTORY_ONLY @TestApi Test: cts/PrivappPermissionsTest Bug: 69433619 Change-Id: Id4f26f0e54ac88834612d25a59cedb0f1fceba02 --- api/test-current.txt | 1 + core/java/android/content/pm/PackageManager.java | 1 + 2 files changed, 2 insertions(+) diff --git a/api/test-current.txt b/api/test-current.txt index 4e8f904b96b..ec67e0bba87 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -214,6 +214,7 @@ package android.content.pm { method public abstract boolean isPermissionReviewModeEnabled(); field public static final java.lang.String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage"; field public static final java.lang.String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption"; + field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 } public class PermissionInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 5a894c78af5..15046edc44f 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -441,6 +441,7 @@ public abstract class PackageManager { * @hide */ @SystemApi + @TestApi public static final int MATCH_FACTORY_ONLY = 0x00200000; /** -- GitLab From 7ba8fc357e44ee3bb8e614c202852a1c46a9d4a8 Mon Sep 17 00:00:00 2001 From: Yangster-mac Date: Wed, 24 Jan 2018 16:16:46 -0800 Subject: [PATCH 028/416] Cpu usage optimization: 1/ Avoid unnecessary field/dimension proto construction. 2/ use unordered_map for slicing. 3/ Use dimension fields to compare dimension keys. Test: all statsd tests passed. Change-Id: I2f74f78589b7f6ecd0803a2ead822b8d0399f334 --- cmds/statsd/src/HashableDimensionKey.cpp | 55 ++++++- cmds/statsd/src/StatsLogProcessor.cpp | 21 ++- .../src/condition/SimpleConditionTracker.cpp | 3 +- cmds/statsd/src/condition/condition_util.cpp | 57 +++---- cmds/statsd/src/condition/condition_util.h | 5 +- cmds/statsd/src/dimension.cpp | 152 ++++++++---------- cmds/statsd/src/dimension.h | 16 +- cmds/statsd/src/field_util.cpp | 125 +++++++------- cmds/statsd/src/field_util.h | 19 +-- cmds/statsd/src/logd/LogEvent.cpp | 58 +++---- cmds/statsd/src/logd/LogEvent.h | 1 - cmds/statsd/src/matchers/matcher_util.cpp | 91 ++++++----- cmds/statsd/src/matchers/matcher_util.h | 6 +- .../src/metrics/DurationMetricProducer.cpp | 3 +- cmds/statsd/src/metrics/MetricProducer.cpp | 8 +- .../duration_helper/MaxDurationTracker.h | 2 +- .../duration_helper/OringDurationTracker.h | 6 +- .../metrics/CountMetricProducer_test.cpp | 4 +- .../metrics/EventMetricProducer_test.cpp | 4 +- 19 files changed, 316 insertions(+), 320 deletions(-) diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp index 288ebe9dbbe..857a6ddad0b 100644 --- a/cmds/statsd/src/HashableDimensionKey.cpp +++ b/cmds/statsd/src/HashableDimensionKey.cpp @@ -69,18 +69,17 @@ android::hash_t hashDimensionsValue(const DimensionsValue& value) { using std::string; - string HashableDimensionKey::toString() const { string flattened; DimensionsValueToString(getDimensionsValue(), &flattened); return flattened; } -bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2) { +bool EqualsTo(const DimensionsValue& s1, const DimensionsValue& s2) { if (s1.field() != s2.field()) { return false; } - if (s1.value_case() != s1.value_case()) { + if (s1.value_case() != s2.value_case()) { return false; } switch (s1.value_case()) { @@ -102,8 +101,8 @@ bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2 } bool allMatched = true; for (int i = 0; allMatched && i < s1.value_tuple().dimensions_value_size(); ++i) { - allMatched &= compareDimensionsValue(s1.value_tuple().dimensions_value(i), - s2.value_tuple().dimensions_value(i)); + allMatched &= EqualsTo(s1.value_tuple().dimensions_value(i), + s2.value_tuple().dimensions_value(i)); } return allMatched; } @@ -113,12 +112,54 @@ bool compareDimensionsValue(const DimensionsValue& s1, const DimensionsValue& s2 } } +bool LessThan(const DimensionsValue& s1, const DimensionsValue& s2) { + if (s1.field() != s2.field()) { + return s1.field() < s2.field(); + } + if (s1.value_case() != s2.value_case()) { + return s1.value_case() < s2.value_case(); + } + switch (s1.value_case()) { + case DimensionsValue::ValueCase::kValueStr: + return s1.value_str() < s2.value_str(); + case DimensionsValue::ValueCase::kValueInt: + return s1.value_int() < s2.value_int(); + case DimensionsValue::ValueCase::kValueLong: + return s1.value_long() < s2.value_long(); + case DimensionsValue::ValueCase::kValueBool: + return (int)s1.value_bool() < (int)s2.value_bool(); + case DimensionsValue::ValueCase::kValueFloat: + return s1.value_float() < s2.value_float(); + case DimensionsValue::ValueCase::kValueTuple: + { + if (s1.value_tuple().dimensions_value_size() != + s2.value_tuple().dimensions_value_size()) { + return s1.value_tuple().dimensions_value_size() < + s2.value_tuple().dimensions_value_size(); + } + for (int i = 0; i < s1.value_tuple().dimensions_value_size(); ++i) { + if (EqualsTo(s1.value_tuple().dimensions_value(i), + s2.value_tuple().dimensions_value(i))) { + continue; + } else { + return LessThan(s1.value_tuple().dimensions_value(i), + s2.value_tuple().dimensions_value(i)); + } + } + return false; + } + case DimensionsValue::ValueCase::VALUE_NOT_SET: + default: + return false; + } +} + bool HashableDimensionKey::operator==(const HashableDimensionKey& that) const { - return compareDimensionsValue(getDimensionsValue(), that.getDimensionsValue()); + return EqualsTo(getDimensionsValue(), that.getDimensionsValue()); }; bool HashableDimensionKey::operator<(const HashableDimensionKey& that) const { - return toString().compare(that.toString()) < 0; + return LessThan(getDimensionsValue(), that.getDimensionsValue()); }; } // namespace statsd diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index edc9f2ce6b3..90639b40a99 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -91,23 +91,22 @@ void StatsLogProcessor::onAnomalyAlarmFired( } void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const { - std::vector uidFields; + std::set uidFields; if (android::util::kAtomsWithAttributionChain.find(event->GetTagId()) != android::util::kAtomsWithAttributionChain.end()) { - findFields( - event->getFieldValueMap(), - buildAttributionUidFieldMatcher(event->GetTagId(), Position::ANY), - &uidFields); + FieldMatcher matcher; + buildAttributionUidFieldMatcher(event->GetTagId(), Position::ANY, &matcher); + findFields(event->getFieldValueMap(), matcher, &uidFields); } else if (android::util::kAtomsWithUidField.find(event->GetTagId()) != android::util::kAtomsWithUidField.end()) { - findFields( - event->getFieldValueMap(), - buildSimpleAtomFieldMatcher(event->GetTagId(), 1 /* uid is always the 1st field. */), - &uidFields); + FieldMatcher matcher; + buildSimpleAtomFieldMatcher( + event->GetTagId(), 1 /* uid is always the 1st field. */, &matcher); + findFields(event->getFieldValueMap(), matcher, &uidFields); } - for (size_t i = 0; i < uidFields.size(); ++i) { - DimensionsValue* value = event->findFieldValueOrNull(uidFields[i]); + for (const auto& uidField : uidFields) { + DimensionsValue* value = event->findFieldValueOrNull(uidField); if (value != nullptr && value->value_case() == DimensionsValue::ValueCase::kValueInt) { const int uid = mUidMap->getHostUidOrSelf(value->value_int()); value->set_value_int(uid); diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp index 7a1bb0c7137..5cfc349ea46 100644 --- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp +++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp @@ -289,7 +289,8 @@ void SimpleConditionTracker::evaluateCondition(const LogEvent& event, } // outputKey is the output values. e.g, uid:1234 - const std::vector outputValues = getDimensionKeys(event, mOutputDimensions); + std::vector outputValues; + getDimensionKeys(event, mOutputDimensions, &outputValues); if (outputValues.size() == 0) { // The original implementation would generate an empty string dimension hash when condition // is not sliced. diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp index ddfb8d12a33..3b2d480b3eb 100644 --- a/cmds/statsd/src/condition/condition_util.cpp +++ b/cmds/statsd/src/condition/condition_util.cpp @@ -116,28 +116,30 @@ void OrBooleanVector(const std::vector& ref, vector * ored) { } } -void getFieldsFromFieldMatcher(const FieldMatcher& matcher, const Field& parentField, - std::vector *allFields) { - Field newParent = parentField; - Field* leaf = getSingleLeaf(&newParent); - leaf->set_field(matcher.field()); +void getFieldsFromFieldMatcher(const FieldMatcher& matcher, Field* rootField, Field* leafField, + std::vector *allFields) { if (matcher.child_size() == 0) { - allFields->push_back(newParent); + allFields->push_back(*rootField); return; } for (int i = 0; i < matcher.child_size(); ++i) { - leaf->add_child(); - getFieldsFromFieldMatcher(matcher.child(i), newParent, allFields); + Field* newLeafField = leafField->add_child(); + newLeafField->set_field(matcher.child(i).field()); + getFieldsFromFieldMatcher(matcher.child(i), rootField, newLeafField, allFields); } } void getFieldsFromFieldMatcher(const FieldMatcher& matcher, std::vector *allFields) { - Field parentField; - getFieldsFromFieldMatcher(matcher, parentField, allFields); + if (!matcher.has_field()) { + return; + } + Field rootField; + rootField.set_field(matcher.field()); + getFieldsFromFieldMatcher(matcher, &rootField, &rootField, allFields); } void flattenValueLeaves(const DimensionsValue& value, - std::vector *allLaves) { + std::vector *allLaves) { switch (value.value_case()) { case DimensionsValue::ValueCase::kValueStr: case DimensionsValue::ValueCase::kValueInt: @@ -145,7 +147,7 @@ void flattenValueLeaves(const DimensionsValue& value, case DimensionsValue::ValueCase::kValueBool: case DimensionsValue::ValueCase::kValueFloat: case DimensionsValue::ValueCase::VALUE_NOT_SET: - allLaves->push_back(value); + allLaves->push_back(&value); break; case DimensionsValue::ValueCase::kValueTuple: for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) { @@ -155,45 +157,44 @@ void flattenValueLeaves(const DimensionsValue& value, } } -std::vector getDimensionKeysForCondition( - const LogEvent& event, const MetricConditionLink& link) { +void getDimensionKeysForCondition( + const LogEvent& event, const MetricConditionLink& link, + std::vector *hashableDimensionKeys) { std::vector whatFields; getFieldsFromFieldMatcher(link.fields_in_what(), &whatFields); std::vector conditionFields; getFieldsFromFieldMatcher(link.fields_in_condition(), &conditionFields); - std::vector hashableDimensionKeys; - // TODO(yanglu): here we could simplify the logic to get the leaf value node in what and // directly construct the full condition value tree. - std::vector whatValues = getDimensionKeys(event, link.fields_in_what()); + std::vector whatValues; + getDimensionKeys(event, link.fields_in_what(), &whatValues); for (size_t i = 0; i < whatValues.size(); ++i) { - std::vector whatLeaves; + std::vector whatLeaves; flattenValueLeaves(whatValues[i], &whatLeaves); if (whatLeaves.size() != whatFields.size() || whatLeaves.size() != conditionFields.size()) { ALOGE("Dimensions between what and condition not equal."); - return hashableDimensionKeys; + return; } FieldValueMap conditionValueMap; for (size_t j = 0; j < whatLeaves.size(); ++j) { - if (!setFieldInLeafValueProto(conditionFields[j], &whatLeaves[j])) { + DimensionsValue* conditionValue = &conditionValueMap[conditionFields[j]]; + *conditionValue = *whatLeaves[i]; + if (!setFieldInLeafValueProto(conditionFields[j], conditionValue)) { ALOGE("Not able to reset the field for condition leaf value."); - return hashableDimensionKeys; + return; } - conditionValueMap.insert(std::make_pair(conditionFields[j], whatLeaves[j])); } - std::vector conditionValues; - findDimensionsValues(conditionValueMap, link.fields_in_condition(), &conditionValues); - if (conditionValues.size() != 1) { + std::vector conditionValueTrees; + findDimensionsValues(conditionValueMap, link.fields_in_condition(), &conditionValueTrees); + if (conditionValueTrees.size() != 1) { ALOGE("Not able to find unambiguous field value in condition atom."); continue; } - hashableDimensionKeys.push_back(HashableDimensionKey(conditionValues[0])); + hashableDimensionKeys->push_back(HashableDimensionKey(conditionValueTrees[0])); } - - return hashableDimensionKeys; } } // namespace statsd diff --git a/cmds/statsd/src/condition/condition_util.h b/cmds/statsd/src/condition/condition_util.h index 598027b7e36..a7288beb69c 100644 --- a/cmds/statsd/src/condition/condition_util.h +++ b/cmds/statsd/src/condition/condition_util.h @@ -40,8 +40,9 @@ ConditionState evaluateCombinationCondition(const std::vector& children, const LogicalOperation& operation, const std::vector& conditionCache); -std::vector getDimensionKeysForCondition( - const LogEvent& event, const MetricConditionLink& link); +void getDimensionKeysForCondition( + const LogEvent& event, const MetricConditionLink& link, + std::vector *dimensionKeys); } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/dimension.cpp b/cmds/statsd/src/dimension.cpp index bb7a044fa8c..04445ca0e23 100644 --- a/cmds/statsd/src/dimension.cpp +++ b/cmds/statsd/src/dimension.cpp @@ -38,7 +38,7 @@ DimensionsValue getSingleLeafValue(const DimensionsValue& value) { return *leafValue; } -void appendLeafNodeToParent(const Field& field, +void appendLeafNodeToTree(const Field& field, const DimensionsValue& value, DimensionsValue* parentValue) { if (field.child_size() <= 0) { @@ -58,24 +58,24 @@ void appendLeafNodeToParent(const Field& field, parentValue->mutable_value_tuple()->add_dimensions_value(); idx = parentValue->mutable_value_tuple()->dimensions_value_size() - 1; } - appendLeafNodeToParent( + appendLeafNodeToTree( field.child(0), value, parentValue->mutable_value_tuple()->mutable_dimensions_value(idx)); } -void addNodeToRootDimensionsValues(const Field& field, - const DimensionsValue& node, - std::vector* rootValues) { - if (rootValues == nullptr) { +void appendLeafNodeToTrees(const Field& field, + const DimensionsValue& node, + std::vector* rootTrees) { + if (rootTrees == nullptr) { return; } - if (rootValues->empty()) { - DimensionsValue rootValue; - appendLeafNodeToParent(field, node, &rootValue); - rootValues->push_back(rootValue); + if (rootTrees->empty()) { + DimensionsValue tree; + appendLeafNodeToTree(field, node, &tree); + rootTrees->push_back(tree); } else { - for (size_t i = 0; i < rootValues->size(); ++i) { - appendLeafNodeToParent(field, node, &rootValues->at(i)); + for (size_t i = 0; i < rootTrees->size(); ++i) { + appendLeafNodeToTree(field, node, &rootTrees->at(i)); } } } @@ -85,22 +85,25 @@ namespace { void findDimensionsValues( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, + Field* rootField, + Field* leafField, std::vector* rootDimensionsValues); void findNonRepeatedDimensionsValues( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, + Field* rootField, + Field* leafField, std::vector* rootValues) { if (matcher.child_size() > 0) { + Field* newLeafField = leafField->add_child(); for (const auto& childMatcher : matcher.child()) { - Field childField = field; - appendLeaf(&childField, childMatcher.field()); - findDimensionsValues(fieldValueMap, childMatcher, childField, rootValues); + newLeafField->set_field(childMatcher.field()); + findDimensionsValues(fieldValueMap, childMatcher, rootField, newLeafField, rootValues); } + leafField->clear_child(); } else { - auto ret = fieldValueMap.equal_range(field); + auto ret = fieldValueMap.equal_range(*rootField); int found = 0; for (auto it = ret.first; it != ret.second; ++it) { found++; @@ -113,40 +116,43 @@ void findNonRepeatedDimensionsValues( ALOGE("Found multiple values for optional field."); return; } - addNodeToRootDimensionsValues(field, ret.first->second, rootValues); + appendLeafNodeToTrees(*rootField, ret.first->second, rootValues); } } void findRepeatedDimensionsValues(const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, + Field* rootField, + Field* leafField, std::vector* rootValues) { if (matcher.position() == Position::FIRST) { - Field first_field = field; - setPositionForLeaf(&first_field, 0); - findNonRepeatedDimensionsValues(fieldValueMap, matcher, first_field, rootValues); + leafField->set_position_index(0); + findNonRepeatedDimensionsValues(fieldValueMap, matcher, rootField, leafField, rootValues); + leafField->clear_position_index(); } else { - auto itLower = fieldValueMap.lower_bound(field); + auto itLower = fieldValueMap.lower_bound(*rootField); if (itLower == fieldValueMap.end()) { return; } - Field next_field = field; - getNextField(&next_field); - auto itUpper = fieldValueMap.lower_bound(next_field); + const int leafFieldNum = leafField->field(); + leafField->set_field(leafFieldNum + 1); + auto itUpper = fieldValueMap.lower_bound(*rootField); + // Resets the field number. + leafField->set_field(leafFieldNum); switch (matcher.position()) { case Position::LAST: { itUpper--; if (itUpper != fieldValueMap.end()) { - Field last_field = field; - int last_index = getPositionByReferenceField(field, itUpper->first); + int last_index = getPositionByReferenceField(*rootField, itUpper->first); if (last_index < 0) { return; } - setPositionForLeaf(&last_field, last_index); + leafField->set_position_index(last_index); findNonRepeatedDimensionsValues( - fieldValueMap, matcher, last_field, rootValues); + fieldValueMap, matcher, rootField, leafField, rootValues); + leafField->clear_position_index(); } } break; @@ -154,20 +160,20 @@ void findRepeatedDimensionsValues(const FieldValueMap& fieldValueMap, { std::set indexes; for (auto it = itLower; it != itUpper; ++it) { - int index = getPositionByReferenceField(field, it->first); + int index = getPositionByReferenceField(*rootField, it->first); if (index >= 0) { indexes.insert(index); } } if (!indexes.empty()) { - Field any_field = field; std::vector allValues; for (const int index : indexes) { - setPositionForLeaf(&any_field, index); + leafField->set_position_index(index); std::vector newValues = *rootValues; findNonRepeatedDimensionsValues( - fieldValueMap, matcher, any_field, &newValues); + fieldValueMap, matcher, rootField, leafField, &newValues); allValues.insert(allValues.end(), newValues.begin(), newValues.end()); + leafField->clear_position_index(); } rootValues->clear(); rootValues->insert(rootValues->end(), allValues.begin(), allValues.end()); @@ -183,12 +189,15 @@ void findRepeatedDimensionsValues(const FieldValueMap& fieldValueMap, void findDimensionsValues( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, + Field* rootField, + Field* leafField, std::vector* rootDimensionsValues) { if (!matcher.has_position()) { - findNonRepeatedDimensionsValues(fieldValueMap, matcher, field, rootDimensionsValues); + findNonRepeatedDimensionsValues(fieldValueMap, matcher, rootField, leafField, + rootDimensionsValues); } else { - findRepeatedDimensionsValues(fieldValueMap, matcher, field, rootDimensionsValues); + findRepeatedDimensionsValues(fieldValueMap, matcher, rootField, leafField, + rootDimensionsValues); } } @@ -198,56 +207,49 @@ void findDimensionsValues( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, std::vector* rootDimensionsValues) { - findDimensionsValues(fieldValueMap, matcher, - buildSimpleAtomField(matcher.field()), rootDimensionsValues); + Field rootField; + buildSimpleAtomField(matcher.field(), &rootField); + findDimensionsValues(fieldValueMap, matcher, &rootField, &rootField, rootDimensionsValues); } -FieldMatcher buildSimpleAtomFieldMatcher(const int tagId) { - FieldMatcher matcher; - matcher.set_field(tagId); - return matcher; +void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher) { + matcher->set_field(tagId); } -FieldMatcher buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum) { - FieldMatcher matcher; - matcher.set_field(tagId); - matcher.add_child()->set_field(atomFieldNum); - return matcher; +void buildSimpleAtomFieldMatcher(const int tagId, const int fieldNum, FieldMatcher* matcher) { + matcher->set_field(tagId); + matcher->add_child()->set_field(fieldNum); } constexpr int ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO = 1; constexpr int UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO = 1; constexpr int TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO = 2; -FieldMatcher buildAttributionUidFieldMatcher(const int tagId, const Position position) { - FieldMatcher matcher; - matcher.set_field(tagId); - auto child = matcher.add_child(); +void buildAttributionUidFieldMatcher(const int tagId, const Position position, + FieldMatcher* matcher) { + matcher->set_field(tagId); + auto child = matcher->add_child(); child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO); child->set_position(position); child->add_child()->set_field(UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); - return matcher; } -FieldMatcher buildAttributionTagFieldMatcher(const int tagId, const Position position) { - FieldMatcher matcher; - matcher.set_field(tagId); - FieldMatcher* child = matcher.add_child(); +void buildAttributionTagFieldMatcher(const int tagId, const Position position, + FieldMatcher* matcher) { + matcher->set_field(tagId); + FieldMatcher* child = matcher->add_child(); child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO); child->set_position(position); child->add_child()->set_field(TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); - return matcher; } -FieldMatcher buildAttributionFieldMatcher(const int tagId, const Position position) { - FieldMatcher matcher; - matcher.set_field(tagId); - FieldMatcher* child = matcher.add_child(); +void buildAttributionFieldMatcher(const int tagId, const Position position, FieldMatcher* matcher) { + matcher->set_field(tagId); + FieldMatcher* child = matcher->add_child(); child->set_field(ATTRIBUTION_FIELD_NUM_IN_ATOM_PROTO); child->set_position(position); child->add_child()->set_field(UID_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); child->add_child()->set_field(TAG_FIELD_NUM_IN_ATTRIBUTION_NODE_PROTO); - return matcher; } void DimensionsValueToString(const DimensionsValue& value, std::string *flattened) { @@ -284,28 +286,6 @@ void DimensionsValueToString(const DimensionsValue& value, std::string *flattene } } -void getDimensionsValueLeafNodes( - const DimensionsValue& value, std::vector *leafNodes) { - switch (value.value_case()) { - case DimensionsValue::ValueCase::kValueStr: - case DimensionsValue::ValueCase::kValueInt: - case DimensionsValue::ValueCase::kValueLong: - case DimensionsValue::ValueCase::kValueBool: - case DimensionsValue::ValueCase::kValueFloat: - leafNodes->push_back(value); - break; - case DimensionsValue::ValueCase::kValueTuple: - for (int i = 0; i < value.value_tuple().dimensions_value_size(); ++i) { - getDimensionsValueLeafNodes(value.value_tuple().dimensions_value(i), leafNodes); - } - break; - case DimensionsValue::ValueCase::VALUE_NOT_SET: - break; - default: - break; - } -} - std::string DimensionsValueToString(const DimensionsValue& value) { std::string flatten; DimensionsValueToString(value, &flatten); diff --git a/cmds/statsd/src/dimension.h b/cmds/statsd/src/dimension.h index d0f96a2abe6..e900c5e8722 100644 --- a/cmds/statsd/src/dimension.h +++ b/cmds/statsd/src/dimension.h @@ -33,8 +33,7 @@ const DimensionsValue* getSingleLeafValue(const DimensionsValue* value); DimensionsValue getSingleLeafValue(const DimensionsValue& value); // Appends the leaf node to the parent tree. -void appendLeafNodeToParent(const Field& field, const DimensionsValue& value, - DimensionsValue* parentValue); +void appendLeafNodeToTree(const Field& field, const DimensionsValue& value, DimensionsValue* tree); // Constructs the DimensionsValue protos from the FieldMatcher. Each DimensionsValue proto // represents a tree. When the input proto has repeated fields and the input "dimensions" wants @@ -45,13 +44,16 @@ void findDimensionsValues( std::vector* rootDimensionsValues); // Utils to build FieldMatcher proto for simple one-depth atoms. -FieldMatcher buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum); -FieldMatcher buildSimpleAtomFieldMatcher(const int tagId); +void buildSimpleAtomFieldMatcher(const int tagId, const int atomFieldNum, FieldMatcher* matcher); +void buildSimpleAtomFieldMatcher(const int tagId, FieldMatcher* matcher); // Utils to build FieldMatcher proto for attribution nodes. -FieldMatcher buildAttributionUidFieldMatcher(const int tagId, const Position position); -FieldMatcher buildAttributionTagFieldMatcher(const int tagId, const Position position); -FieldMatcher buildAttributionFieldMatcher(const int tagId, const Position position); +void buildAttributionUidFieldMatcher(const int tagId, const Position position, + FieldMatcher* matcher); +void buildAttributionTagFieldMatcher(const int tagId, const Position position, + FieldMatcher* matcher); +void buildAttributionFieldMatcher(const int tagId, const Position position, + FieldMatcher* matcher); // Utils to print pretty string for DimensionsValue proto. std::string DimensionsValueToString(const DimensionsValue& value); diff --git a/cmds/statsd/src/field_util.cpp b/cmds/statsd/src/field_util.cpp index 4ff4f74f797..acf64fe12e6 100644 --- a/cmds/statsd/src/field_util.cpp +++ b/cmds/statsd/src/field_util.cpp @@ -102,24 +102,13 @@ bool setFieldInLeafValueProto(const Field &field, DimensionsValue* leafValue) { } } -Field buildAtomField(const int tagId, const Field &atomField) { - Field field; - *field.add_child() = atomField; - field.set_field(tagId); - return field; +void buildSimpleAtomField(const int tagId, const int atomFieldNum, Field *field) { + field->set_field(tagId); + field->add_child()->set_field(atomFieldNum); } -Field buildSimpleAtomField(const int tagId, const int atomFieldNum) { - Field field; - field.set_field(tagId); - field.add_child()->set_field(atomFieldNum); - return field; -} - -Field buildSimpleAtomField(const int tagId) { - Field field; - field.set_field(tagId); - return field; +void buildSimpleAtomField(const int tagId, Field *field) { + field->set_field(tagId); } void appendLeaf(Field *parent, int node_field_num) { @@ -145,18 +134,6 @@ void appendLeaf(Field *parent, int node_field_num, int position) { } } - -void getNextField(Field* field) { - if (field->child_size() <= 0) { - field->set_field(field->field() + 1); - return; - } - if (field->child_size() != 1) { - return; - } - getNextField(field->mutable_child(0)); -} - void increasePosition(Field *field) { if (!field->has_position_index()) { field->set_position_index(0); @@ -176,34 +153,30 @@ int getPositionByReferenceField(const Field& ref, const Field& field_with_index) return getPositionByReferenceField(ref.child(0), field_with_index.child(0)); } -void setPositionForLeaf(Field *field, int index) { - if (field->child_size() <= 0) { - field->set_position_index(index); - } else { - setPositionForLeaf(field->mutable_child(0), index); - } -} - namespace { + void findFields( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, - std::vector* rootFields); + Field* rootField, + Field* leafField, + std::set* rootFields); void findNonRepeatedFields( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, - std::vector* rootFields) { + Field* rootField, + Field* leafField, + std::set* rootFields) { if (matcher.child_size() > 0) { + Field* newLeafField = leafField->add_child(); for (const auto& childMatcher : matcher.child()) { - Field childField = field; - appendLeaf(&childField, childMatcher.field()); - findFields(fieldValueMap, childMatcher, childField, rootFields); + newLeafField->set_field(childMatcher.field()); + findFields(fieldValueMap, childMatcher, rootField, newLeafField, rootFields); } + leafField->clear_child(); } else { - auto ret = fieldValueMap.equal_range(field); + auto ret = fieldValueMap.equal_range(*rootField); int found = 0; for (auto it = ret.first; it != ret.second; ++it) { found++; @@ -216,38 +189,42 @@ void findNonRepeatedFields( ALOGE("Found multiple values for optional field."); return; } - rootFields->push_back(ret.first->first); + rootFields->insert(ret.first->first); } } void findRepeatedFields(const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, std::vector* rootFields) { + Field* rootField, Field* leafField, + std::set* rootFields) { if (matcher.position() == Position::FIRST) { - Field first_field = field; - setPositionForLeaf(&first_field, 0); - findNonRepeatedFields(fieldValueMap, matcher, first_field, rootFields); + leafField->set_position_index(0); + findNonRepeatedFields(fieldValueMap, matcher, rootField, leafField, rootFields); + leafField->clear_position_index(); } else { - auto itLower = fieldValueMap.lower_bound(field); + auto itLower = fieldValueMap.lower_bound(*rootField); if (itLower == fieldValueMap.end()) { return; } - Field next_field = field; - getNextField(&next_field); - auto itUpper = fieldValueMap.lower_bound(next_field); + + const int leafFieldNum = leafField->field(); + leafField->set_field(leafFieldNum + 1); + auto itUpper = fieldValueMap.lower_bound(*rootField); + // Resets the field number. + leafField->set_field(leafFieldNum); switch (matcher.position()) { case Position::LAST: { itUpper--; if (itUpper != fieldValueMap.end()) { - Field last_field = field; - int last_index = getPositionByReferenceField(field, itUpper->first); + int last_index = getPositionByReferenceField(*rootField, itUpper->first); if (last_index < 0) { return; } - setPositionForLeaf(&last_field, last_index); + leafField->set_position_index(last_index); findNonRepeatedFields( - fieldValueMap, matcher, last_field, rootFields); + fieldValueMap, matcher, rootField, leafField, rootFields); + leafField->clear_position_index(); } } break; @@ -255,17 +232,17 @@ void findRepeatedFields(const FieldValueMap& fieldValueMap, const FieldMatcher& { std::set indexes; for (auto it = itLower; it != itUpper; ++it) { - int index = getPositionByReferenceField(field, it->first); + int index = getPositionByReferenceField(*rootField, it->first); if (index >= 0) { indexes.insert(index); } } if (!indexes.empty()) { - Field any_field = field; for (const int index : indexes) { - setPositionForLeaf(&any_field, index); + leafField->set_position_index(index); findNonRepeatedFields( - fieldValueMap, matcher, any_field, rootFields); + fieldValueMap, matcher, rootField, leafField, rootFields); + leafField->clear_position_index(); } } } @@ -279,12 +256,13 @@ void findRepeatedFields(const FieldValueMap& fieldValueMap, const FieldMatcher& void findFields( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - const Field& field, - std::vector* rootFields) { + Field* rootField, + Field* leafField, + std::set* rootFields) { if (!matcher.has_position()) { - findNonRepeatedFields(fieldValueMap, matcher, field, rootFields); + findNonRepeatedFields(fieldValueMap, matcher, rootField, leafField, rootFields); } else { - findRepeatedFields(fieldValueMap, matcher, field, rootFields); + findRepeatedFields(fieldValueMap, matcher, rootField, leafField, rootFields); } } @@ -293,17 +271,24 @@ void findFields( void findFields( const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, - std::vector* rootFields) { - return findFields(fieldValueMap, matcher, buildSimpleAtomField(matcher.field()), rootFields); + std::set* rootFields) { + if (!matcher.has_field() || fieldValueMap.empty()) { + return; + } + Field rootField; + buildSimpleAtomField(matcher.field(), &rootField); + return findFields(fieldValueMap, matcher, &rootField, &rootField, rootFields); } void filterFields(const FieldMatcher& matcher, FieldValueMap* fieldValueMap) { - std::vector rootFields; + if (!matcher.has_field()) { + return; + } + std::set rootFields; findFields(*fieldValueMap, matcher, &rootFields); - std::set rootFieldSet(rootFields.begin(), rootFields.end()); auto it = fieldValueMap->begin(); while (it != fieldValueMap->end()) { - if (rootFieldSet.find(it->first) == rootFieldSet.end()) { + if (rootFields.find(it->first) == rootFields.end()) { it = fieldValueMap->erase(it); } else { it++; diff --git a/cmds/statsd/src/field_util.h b/cmds/statsd/src/field_util.h index a4dfdddf210..b04465dc986 100644 --- a/cmds/statsd/src/field_util.h +++ b/cmds/statsd/src/field_util.h @@ -20,7 +20,8 @@ #include "frameworks/base/cmds/statsd/src/statsd_internal.pb.h" #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" -#include +#include +#include namespace android { namespace os { @@ -54,15 +55,9 @@ Field* getSingleLeaf(Field* field); void appendLeaf(Field *parent, int node_field_num); void appendLeaf(Field *parent, int node_field_num, int position); -// Given the field sorting logic, this function is to increase the "field" at the leaf node. -void getNextField(Field* field); - // Increase the position index for the node. If the "position_index" is not set, set it as 0. void increasePosition(Field *field); -// Finds the leaf node and set the index there. -void setPositionForLeaf(Field *field, int index); - // Returns true if the matcher has specified at least one leaf node. bool hasLeafNode(const FieldMatcher& matcher); @@ -72,15 +67,13 @@ bool hasLeafNode(const FieldMatcher& matcher); int getPositionByReferenceField(const Field& reference, const Field& field_with_index); // Utils to build the Field proto for simple atom fields. -Field buildAtomField(const int tagId, const Field &atomField); -Field buildSimpleAtomField(const int tagId, const int atomFieldNum); -Field buildSimpleAtomField(const int tagId); +void buildSimpleAtomField(const int tagId, const int atomFieldNum, Field* field); +void buildSimpleAtomField(const int tagId, Field* field); // Find out all the fields specified by the matcher. void findFields( - const FieldValueMap& fieldValueMap, - const FieldMatcher& matcher, - std::vector* rootFields); + const FieldValueMap& fieldValueMap, const FieldMatcher& matcher, + std::set* rootFields); // Filter out the fields not in the field matcher. void filterFields(const FieldMatcher& matcher, FieldValueMap* fieldValueMap); diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 1ca793c8187..9e72f5bd4b7 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -198,7 +198,8 @@ void LogEvent::init(android_log_context context) { int seenListStart = 0; - Field field; + Field fieldTree; + Field* atomField = fieldTree.add_child(); do { elem = android_log_read_next(context); switch ((int)elem.type) { @@ -206,51 +207,37 @@ void LogEvent::init(android_log_context context) { // elem at [0] is EVENT_TYPE_LIST, [1] is the tag id. if (i == 1) { mTagId = elem.data.int32; + fieldTree.set_field(mTagId); } else { - increaseField(&field, seenListStart > 0/* is_child */); - DimensionsValue dimensionsValue; - dimensionsValue.set_value_int(elem.data.int32); - setFieldInLeafValueProto(field, &dimensionsValue); - mFieldValueMap.insert( - std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + increaseField(atomField, seenListStart > 0/* is_child */); + mFieldValueMap[fieldTree].set_value_int(elem.data.int32); } break; case EVENT_TYPE_FLOAT: { - increaseField(&field, seenListStart > 0/* is_child */); - DimensionsValue dimensionsValue; - dimensionsValue.set_value_float(elem.data.float32); - setFieldInLeafValueProto(field, &dimensionsValue); - mFieldValueMap.insert( - std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + increaseField(atomField, seenListStart > 0/* is_child */); + mFieldValueMap[fieldTree].set_value_float(elem.data.float32); } break; case EVENT_TYPE_STRING: { - increaseField(&field, seenListStart > 0/* is_child */); - DimensionsValue dimensionsValue; - dimensionsValue.set_value_str(string(elem.data.string, elem.len).c_str()); - setFieldInLeafValueProto(field, &dimensionsValue); - mFieldValueMap.insert( - std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + increaseField(atomField, seenListStart > 0/* is_child */); + mFieldValueMap[fieldTree].set_value_str( + string(elem.data.string, elem.len).c_str()); } break; case EVENT_TYPE_LONG: { - increaseField(&field, seenListStart > 0 /* is_child */); - DimensionsValue dimensionsValue; - dimensionsValue.set_value_long(elem.data.int64); - setFieldInLeafValueProto(field, &dimensionsValue); - mFieldValueMap.insert( - std::make_pair(buildAtomField(mTagId, field), dimensionsValue)); + increaseField(atomField, seenListStart > 0 /* is_child */); + mFieldValueMap[fieldTree].set_value_long(elem.data.int64); } break; case EVENT_TYPE_LIST: if (i >= 1) { if (seenListStart > 0) { - increasePosition(&field); + increasePosition(atomField); } else { - increaseField(&field, false /* is_child */); + increaseField(atomField, false /* is_child */); } seenListStart++; if (seenListStart >= 3) { @@ -262,10 +249,10 @@ void LogEvent::init(android_log_context context) { case EVENT_TYPE_LIST_STOP: seenListStart--; if (seenListStart == 0) { - field.clear_position_index(); + atomField->clear_position_index(); } else { - if (field.child_size() > 0) { - field.mutable_child(0)->clear_field(); + if (atomField->child_size() > 0) { + atomField->mutable_child(0)->clear_field(); } } break; @@ -393,14 +380,9 @@ bool LogEvent::GetAtomDimensionsValueProto(const FieldMatcher& matcher, bool LogEvent::GetSimpleAtomDimensionsValueProto(size_t atomField, DimensionsValue* dimensionsValue) const { - return GetAtomDimensionsValueProto( - buildSimpleAtomFieldMatcher(mTagId, atomField), dimensionsValue); -} - -DimensionsValue LogEvent::GetSimpleAtomDimensionsValueProto(size_t atomField) const { - DimensionsValue dimensionsValue; - GetSimpleAtomDimensionsValueProto(atomField, &dimensionsValue); - return dimensionsValue; + FieldMatcher matcher; + buildSimpleAtomFieldMatcher(mTagId, atomField, &matcher); + return GetAtomDimensionsValueProto(matcher, dimensionsValue); } DimensionsValue* LogEvent::findFieldValueOrNull(const Field& field) { diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 5a4efd4b54a..eb2c00845d0 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -92,7 +92,6 @@ public: * Get a DimensionsValue proto objects from Field. */ bool GetSimpleAtomDimensionsValueProto(size_t field, DimensionsValue* dimensionsValue) const; - DimensionsValue GetSimpleAtomDimensionsValueProto(size_t atomField) const; /** * Write test data to the LogEvent. This can only be used when the LogEvent is constructed diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp index 48f62e70256..b6f440f2e34 100644 --- a/cmds/statsd/src/matchers/matcher_util.cpp +++ b/cmds/statsd/src/matchers/matcher_util.cpp @@ -93,25 +93,28 @@ bool combinationMatch(const vector& children, const LogicalOperation& opera return matched; } -bool matchesNonRepeatedField( - const UidMap& uidMap, - const FieldValueMap& fieldMap, - const FieldValueMatcher&matcher, - const Field& field) { +namespace { + +bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& fieldMap, + const FieldValueMatcher&matcher, Field* rootField, Field* leafField); + +bool matchesNonRepeatedField(const UidMap& uidMap, const FieldValueMap& fieldMap, + const FieldValueMatcher&matcher, Field* rootField, Field* leafField) { if (matcher.value_matcher_case() == FieldValueMatcher::ValueMatcherCase::VALUE_MATCHER_NOT_SET) { return !fieldMap.empty() && fieldMap.begin()->first.field() == matcher.field(); } else if (matcher.value_matcher_case() == FieldValueMatcher::ValueMatcherCase::kMatchesTuple) { bool allMatched = true; + Field* newLeafField = leafField->add_child(); for (int i = 0; allMatched && i < matcher.matches_tuple().field_value_matcher_size(); ++i) { const auto& childMatcher = matcher.matches_tuple().field_value_matcher(i); - Field childField = field; - appendLeaf(&childField, childMatcher.field()); - allMatched &= matchFieldSimple(uidMap, fieldMap, childMatcher, childField); + newLeafField->set_field(childMatcher.field()); + allMatched &= matchFieldSimple(uidMap, fieldMap, childMatcher, rootField, newLeafField); } + leafField->clear_child(); return allMatched; } else { - auto ret = fieldMap.equal_range(field); + auto ret = fieldMap.equal_range(*rootField); int found = 0; for (auto it = ret.first; it != ret.second; ++it) { found++; @@ -132,7 +135,7 @@ bool matchesNonRepeatedField( break; case FieldValueMatcher::ValueMatcherCase::kEqString: { - if (IsAttributionUidField(field)) { + if (IsAttributionUidField(*rootField)) { const int uid = ret.first->second.value_int(); std::set packageNames = uidMap.getAppNamesFromUid(uid, true /* normalize*/); @@ -171,19 +174,25 @@ bool matchesNonRepeatedField( } bool matchesRepeatedField(const UidMap& uidMap, const FieldValueMap& fieldMap, - const FieldValueMatcher&matcher, const Field& field) { + const FieldValueMatcher&matcher, + Field* rootField, Field* leafField) { if (matcher.position() == Position::FIRST) { - Field first_field = field; - setPositionForLeaf(&first_field, 0); - return matchesNonRepeatedField(uidMap, fieldMap, matcher, first_field); + leafField->set_position_index(0); + bool res = matchesNonRepeatedField(uidMap, fieldMap, matcher, rootField, leafField); + leafField->clear_position_index(); + return res; } else { - auto itLower = fieldMap.lower_bound(field); + auto itLower = fieldMap.lower_bound(*rootField); if (itLower == fieldMap.end()) { return false; } - Field next_field = field; - getNextField(&next_field); - auto itUpper = fieldMap.lower_bound(next_field); + + const int leafFieldNum = leafField->field(); + leafField->set_field(leafFieldNum + 1); + auto itUpper = fieldMap.lower_bound(*rootField); + // Resets the field number. + leafField->set_field(leafFieldNum); + switch (matcher.position()) { case Position::LAST: { @@ -191,31 +200,31 @@ bool matchesRepeatedField(const UidMap& uidMap, const FieldValueMap& fieldMap, if (itUpper == fieldMap.end()) { return false; } else { - Field last_field = field; - int last_index = getPositionByReferenceField(field, itUpper->first); + int last_index = getPositionByReferenceField(*rootField, itUpper->first); if (last_index < 0) { return false; } - setPositionForLeaf(&last_field, last_index); - return matchesNonRepeatedField(uidMap, fieldMap, matcher, last_field); + leafField->set_position_index(last_index); + bool res = matchesNonRepeatedField(uidMap, fieldMap, matcher, rootField, leafField); + leafField->clear_position_index(); + return res; } } break; case Position::ANY: { - std::set indexes; + bool matched = false; for (auto it = itLower; it != itUpper; ++it) { - int index = getPositionByReferenceField(field, it->first); + int index = getPositionByReferenceField(*rootField, it->first); if (index >= 0) { - indexes.insert(index); + leafField->set_position_index(index); + matched |= matchesNonRepeatedField(uidMap, fieldMap, matcher, rootField, leafField); + leafField->clear_position_index(); + if (matched) { + break; + } } } - bool matched = false; - for (const int index : indexes) { - Field any_field = field; - setPositionForLeaf(&any_field, index); - matched |= matchesNonRepeatedField(uidMap, fieldMap, matcher, any_field); - } return matched; } default: @@ -226,14 +235,16 @@ bool matchesRepeatedField(const UidMap& uidMap, const FieldValueMap& fieldMap, } bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& fieldMap, - const FieldValueMatcher&matcher, const Field& field) { + const FieldValueMatcher&matcher, Field* rootField, Field* leafField) { if (!matcher.has_position()) { - return matchesNonRepeatedField(uidMap, fieldMap, matcher, field); + return matchesNonRepeatedField(uidMap, fieldMap, matcher, rootField, leafField); } else { - return matchesRepeatedField(uidMap, fieldMap, matcher, field); + return matchesRepeatedField(uidMap, fieldMap, matcher, rootField, leafField); } } +} // namespace + bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher, const LogEvent& event) { if (simpleMatcher.field_value_matcher_size() <= 0) { @@ -247,13 +258,15 @@ bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher, *root_field_matcher.mutable_matches_tuple()->add_field_value_matcher() = simpleMatcher.field_value_matcher(i); } - return matchFieldSimple(uidMap, event.getFieldValueMap(), root_field_matcher, root_field); + return matchFieldSimple( + uidMap, event.getFieldValueMap(), root_field_matcher, &root_field, &root_field); } -vector getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher) { - vector values; - findDimensionsValues(event.getFieldValueMap(), matcher, &values); - return values; +void getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher, + std::vector *dimensionKeys) { + if (matcher.has_field()) { + findDimensionsValues(event.getFieldValueMap(), matcher, dimensionKeys); + } } } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/matchers/matcher_util.h b/cmds/statsd/src/matchers/matcher_util.h index 704cb4c22e0..a45a9fb26a1 100644 --- a/cmds/statsd/src/matchers/matcher_util.h +++ b/cmds/statsd/src/matchers/matcher_util.h @@ -42,13 +42,11 @@ enum MatchingState { bool combinationMatch(const std::vector& children, const LogicalOperation& operation, const std::vector& matcherResults); -bool matchFieldSimple(const UidMap& uidMap, const FieldValueMap& dimensionsMap, - const FieldValueMatcher& matcher, const Field& field); - bool matchesSimple(const UidMap& uidMap, const SimpleAtomMatcher& simpleMatcher, const LogEvent& wrapper); -std::vector getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher); +void getDimensionKeys(const LogEvent& event, const FieldMatcher& matcher, + std::vector *dimensionKeys); } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index e26fe564909..d233a84cfa4 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -275,7 +275,8 @@ void DurationMetricProducer::onMatchedLogEventInternalLocked( auto it = mCurrentSlicedDuration.find(eventKey); - std::vector values = getDimensionKeys(event, mInternalDimensions); + std::vector values; + getDimensionKeys(event, mInternalDimensions, &values); if (values.empty()) { if (matcherIndex == mStartIndex) { it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index d620a7ebee0..e74924a81fb 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -32,8 +32,7 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo ConditionKey conditionKey; if (mConditionSliced) { for (const auto& link : mConditionLinks) { - conditionKey.insert(std::make_pair(link.condition(), - getDimensionKeysForCondition(event, link))); + getDimensionKeysForCondition(event, link, &conditionKey[link.condition()]); } if (mWizard->query(mConditionTrackerIndex, conditionKey) != ConditionState::kTrue) { condition = false; @@ -44,8 +43,9 @@ void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const Lo condition = mCondition; } - if (mDimensions.child_size() > 0) { - vector dimensionValues = getDimensionKeys(event, mDimensions); + if (mDimensions.has_field() && mDimensions.child_size() > 0) { + vector dimensionValues; + getDimensionKeys(event, mDimensions, &dimensionValues); for (const DimensionsValue& dimensionValue : dimensionValues) { onMatchedLogEventInternalLocked( matcherIndex, HashableDimensionKey(dimensionValue), conditionKey, condition, event); diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h index 5d3c1580463..5053bde89fc 100644 --- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h @@ -50,7 +50,7 @@ public: const uint64_t currentTimestamp) const override; private: - std::map mInfos; + std::unordered_map mInfos; void noteConditionChanged(const HashableDimensionKey& key, bool conditionMet, const uint64_t timestamp); diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h index 638b7ad7af2..9093a51e59c 100644 --- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h @@ -55,10 +55,10 @@ private: // 2) which keys are paused (started but condition was false) // 3) whenever a key stops, we remove it from the started set. And if the set becomes empty, // it means everything has stopped, we then record the end time. - std::map mStarted; - std::map mPaused; + std::unordered_map mStarted; + std::unordered_map mPaused; int64_t mLastStartTime; - std::map mConditionKeyMap; + std::unordered_map mConditionKeyMap; // return true if we should not allow newKey to be tracked because we are above the threshold bool hitGuardRail(const HashableDimensionKey& newKey); diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index 897328d4635..4ad20971290 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -149,8 +149,8 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); MetricConditionLink* link = metric.add_links(); link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); - *link->mutable_fields_in_what() = buildSimpleAtomFieldMatcher(tagId, 1); - *link->mutable_fields_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2); + buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what()); + buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition()); LogEvent event1(tagId, bucketStartTimeNs + 1); event1.write("111"); // uid diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp index 34cde607988..da00cae125c 100644 --- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp @@ -98,8 +98,8 @@ TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) { metric.set_condition(StringToId("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON")); MetricConditionLink* link = metric.add_links(); link->set_condition(StringToId("APP_IN_BACKGROUND_PER_UID")); - *link->mutable_fields_in_what() = buildSimpleAtomFieldMatcher(tagId, 1); - *link->mutable_fields_in_condition() = buildSimpleAtomFieldMatcher(conditionTagId, 2); + buildSimpleAtomFieldMatcher(tagId, 1, link->mutable_fields_in_what()); + buildSimpleAtomFieldMatcher(conditionTagId, 2, link->mutable_fields_in_condition()); LogEvent event1(tagId, bucketStartTimeNs + 1); EXPECT_TRUE(event1.write("111")); -- GitLab From a43e216e3eb8b9cfe011cb92ab00aea11fa9da29 Mon Sep 17 00:00:00 2001 From: Aurimas Liutikas Date: Fri, 26 Jan 2018 23:13:54 +0000 Subject: [PATCH 029/416] Use the correct prebuilts for lifecycles. This change should have no effect on the app. It is simply unifying some of the prebuilts. Bug: 72566647 Change-Id: Ic748f8f425e8c8e44bacf62ea61b9af307774911 --- packages/SettingsLib/Android.mk | 4 ++-- packages/SettingsLib/common.mk | 4 ++-- packages/SystemUI/Android.mk | 4 ++-- packages/SystemUI/tests/Android.mk | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk index 69287e85853..4338d26d502 100644 --- a/packages/SettingsLib/Android.mk +++ b/packages/SettingsLib/Android.mk @@ -14,10 +14,10 @@ LOCAL_SHARED_ANDROID_LIBRARIES := \ android-support-v7-preference \ android-support-v7-appcompat \ android-support-v14-preference \ - apptoolkit-lifecycle-runtime + android-arch-lifecycle-runtime LOCAL_SHARED_JAVA_LIBRARIES := \ - apptoolkit-lifecycle-common + android-arch-lifecycle-common LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk index 14f06254009..3c2ca2d3188 100644 --- a/packages/SettingsLib/common.mk +++ b/packages/SettingsLib/common.mk @@ -64,7 +64,7 @@ LOCAL_AAPT_FLAGS += --auto-add-overlay --extra-packages com.android.settingslib LOCAL_STATIC_JAVA_LIBRARIES += \ android-support-annotations \ android-support-v4 \ - apptoolkit-lifecycle-runtime \ - apptoolkit-lifecycle-common \ + android-arch-lifecycle-runtime \ + android-arch-lifecycle-common \ SettingsLib endif diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk index 251ae2da121..2bcf4ef9f85 100644 --- a/packages/SystemUI/Android.mk +++ b/packages/SystemUI/Android.mk @@ -48,8 +48,8 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ android-slices-core \ android-slices-view \ android-slices-builders \ - apptoolkit-arch-core-runtime \ - apptoolkit-lifecycle-extensions \ + android-arch-core-runtime \ + android-arch-lifecycle-extensions \ LOCAL_STATIC_JAVA_LIBRARIES := \ SystemUI-tags \ diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index 066cfe5862e..936ff512b8c 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -50,8 +50,8 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ android-slices-core \ android-slices-view \ android-slices-builders \ - apptoolkit-arch-core-runtime \ - apptoolkit-lifecycle-extensions \ + android-arch-core-runtime \ + android-arch-lifecycle-extensions \ LOCAL_STATIC_JAVA_LIBRARIES := \ metrics-helper-lib \ -- GitLab From 369208256755a5ded185112dd2ceecadb5986fa5 Mon Sep 17 00:00:00 2001 From: Bookatz Date: Fri, 26 Jan 2018 09:18:07 -0800 Subject: [PATCH 030/416] StatsCompanionService permissions Makes several public things private, since they aren't used elsewhere. Adds enforcing permission for a binder call (albeit, for a function that is going to be removed soon anyway.) Bug: 71768461 Test: Still compiles Change-Id: I844cfa2375c8ca98fe265b05084e8a4b0b240e80 --- .../android/server/stats/StatsCompanionService.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index faafb39cd1f..95a3ec5ced1 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -157,7 +157,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override public void sendBroadcast(String pkg, String cls) { - // TODO: Use a pending intent, and enfoceCallingPermission. + // TODO: Use a pending intent. + enforceCallingPermission(); mContext.sendBroadcastAsUser(new Intent(ACTION_TRIGGER_COLLECTION).setClassName(pkg, cls), UserHandle.SYSTEM); } @@ -235,7 +236,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - public final static class AppUpdateReceiver extends BroadcastReceiver { + private final static class AppUpdateReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { /** @@ -280,7 +281,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - public final static class AnomalyAlarmReceiver extends BroadcastReceiver { + private final static class AnomalyAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred."); @@ -300,7 +301,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - public final static class PullingAlarmReceiver extends BroadcastReceiver { + private final static class PullingAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) @@ -321,7 +322,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } - public final static class ShutdownEventReceiver extends BroadcastReceiver { + private final static class ShutdownEventReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { /** -- GitLab From 58822bec4eb6e0930f2efc0da5b6e464b193c558 Mon Sep 17 00:00:00 2001 From: Previr Rangroo Date: Tue, 28 Nov 2017 17:53:54 +1100 Subject: [PATCH 031/416] Add audio presentation interface to Java API In order to enable applications to query audio sub-selection information of one audio programme delivered by Next Generation Audio streams, implement presentation API in MediaExtractor. Bug: 63901775 Test: make Change-Id: I2b0bfc5d1089614aa93d58fec99324d7a0ed464b Signed-off-by: Previr Rangroo --- api/current.txt | 16 ++ core/jni/android_media_AudioTrack.cpp | 13 ++ .../java/android/media/AudioPresentation.java | 181 ++++++++++++++++++ media/java/android/media/AudioTrack.java | 20 ++ media/java/android/media/MediaExtractor.java | 13 ++ media/jni/android_media_AudioPresentation.h | 173 +++++++++++++++++ 6 files changed, 416 insertions(+) create mode 100644 media/java/android/media/AudioPresentation.java create mode 100644 media/jni/android_media_AudioPresentation.h diff --git a/api/current.txt b/api/current.txt index fa896eb5cc1..b04085d6b40 100644 --- a/api/current.txt +++ b/api/current.txt @@ -22073,6 +22073,20 @@ package android.media { field public static final android.os.Parcelable.Creator CREATOR; } + public final class AudioPresentation { + method public java.util.Map getLabels(); + method public java.util.Locale getLocale(); + method public int getMasteringIndication(); + method public boolean hasAudioDescription(); + method public boolean hasDialogueEnhancement(); + method public boolean hasSpokenSubtitles(); + field public static final int MASTERED_FOR_3D = 3; // 0x3 + field public static final int MASTERED_FOR_HEADPHONE = 4; // 0x4 + field public static final int MASTERED_FOR_STEREO = 1; // 0x1 + field public static final int MASTERED_FOR_SURROUND = 2; // 0x2 + field public static final int MASTERING_NOT_INDICATED = 0; // 0x0 + } + public class AudioRecord implements android.media.AudioRouting { ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException; method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); @@ -22228,6 +22242,7 @@ package android.media { method public int setPlaybackRate(int); method public int setPositionNotificationPeriod(int); method public boolean setPreferredDevice(android.media.AudioDeviceInfo); + method public int setPresentation(android.media.AudioPresentation); method protected deprecated void setState(int); method public deprecated int setStereoVolume(float, float); method public void setStreamEventCallback(java.util.concurrent.Executor, android.media.AudioTrack.StreamEventCallback); @@ -23293,6 +23308,7 @@ package android.media { ctor public MediaExtractor(); method public boolean advance(); method protected void finalize(); + method public java.util.List getAudioPresentations(int); method public long getCachedDuration(); method public android.media.MediaExtractor.CasInfo getCasInfo(int); method public android.media.DrmInitData getDrmInitData(); diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 11011b1d7c1..50e6cc3ecfa 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -1228,6 +1228,18 @@ static jobject android_media_AudioTrack_get_volume_shaper_state(JNIEnv *env, job return VolumeShaperHelper::convertStateToJobject(env, gVolumeShaperFields, state); } +static int android_media_AudioTrack_setPresentation( + JNIEnv *env, jobject thiz, jint presentationId, jint programId) { + sp lpTrack = getAudioTrack(env, thiz); + if (lpTrack == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "AudioTrack not initialized"); + return (jint)AUDIO_JAVA_ERROR; + } + + return (jint)lpTrack->selectPresentation((int)presentationId, (int)programId); +} + // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static const JNINativeMethod gMethods[] = { @@ -1297,6 +1309,7 @@ static const JNINativeMethod gMethods[] = { {"native_getVolumeShaperState", "(I)Landroid/media/VolumeShaper$State;", (void *)android_media_AudioTrack_get_volume_shaper_state}, + {"native_setPresentation", "(II)I", (void *)android_media_AudioTrack_setPresentation}, }; diff --git a/media/java/android/media/AudioPresentation.java b/media/java/android/media/AudioPresentation.java new file mode 100644 index 00000000000..4652c180936 --- /dev/null +++ b/media/java/android/media/AudioPresentation.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + + +/** + * The AudioPresentation class encapsulates the information that describes an audio presentation + * which is available in next generation audio content. + * + * Used by {@link MediaExtractor} {@link MediaExtractor#getAudioPresentations(int)} and + * {@link AudioTrack} {@link AudioTrack#setPresentation(AudioPresentation)} to query available + * presentations and to select one. + * + * A list of available audio presentations in a media source can be queried using + * {@link MediaExtractor#getAudioPresentations(int)}. This list can be presented to a user for + * selection. + * An AudioPresentation can be passed to an offloaded audio decoder via + * {@link AudioTrack#setPresentation(AudioPresentation)} to request decoding of the selected + * presentation. An audio stream may contain multiple presentations that differ by language, + * accessibility, end point mastering and dialogue enhancement. An audio presentation may also have + * a set of description labels in different languages to help the user to make an informed + * selection. + */ +public final class AudioPresentation { + private final int mPresentationId; + private final int mProgramId; + private final Map mLabels; + private final String mLanguage; + + /** @hide */ + @IntDef( + value = { + MASTERING_NOT_INDICATED, + MASTERED_FOR_STEREO, + MASTERED_FOR_SURROUND, + MASTERED_FOR_3D, + MASTERED_FOR_HEADPHONE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface MasteringIndicationType {} + + private final @MasteringIndicationType int mMasteringIndication; + private final boolean mAudioDescriptionAvailable; + private final boolean mSpokenSubtitlesAvailable; + private final boolean mDialogueEnhancementAvailable; + + /** + * No preferred reproduction channel layout. + */ + public static final int MASTERING_NOT_INDICATED = 0; + /** + * Stereo speaker layout. + */ + public static final int MASTERED_FOR_STEREO = 1; + /** + * Two-dimensional (e.g. 5.1) speaker layout. + */ + public static final int MASTERED_FOR_SURROUND = 2; + /** + * Three-dimensional (e.g. 5.1.2) speaker layout. + */ + public static final int MASTERED_FOR_3D = 3; + /** + * Prerendered for headphone playback. + */ + public static final int MASTERED_FOR_HEADPHONE = 4; + + AudioPresentation(int presentationId, + int programId, + Map labels, + String language, + @MasteringIndicationType int masteringIndication, + boolean audioDescriptionAvailable, + boolean spokenSubtitlesAvailable, + boolean dialogueEnhancementAvailable) { + this.mPresentationId = presentationId; + this.mProgramId = programId; + this.mLanguage = language; + this.mMasteringIndication = masteringIndication; + this.mAudioDescriptionAvailable = audioDescriptionAvailable; + this.mSpokenSubtitlesAvailable = spokenSubtitlesAvailable; + this.mDialogueEnhancementAvailable = dialogueEnhancementAvailable; + + this.mLabels = new HashMap(labels); + } + + /** + * The framework uses this presentation id to select an audio presentation rendered by a + * decoder. Presentation id is typically sequential, but does not have to be. + * @hide + */ + public int getPresentationId() { + return mPresentationId; + } + + /** + * The framework uses this program id to select an audio presentation rendered by a decoder. + * Program id can be used to further uniquely identify the presentation to a decoder. + * @hide + */ + public int getProgramId() { + return mProgramId; + } + + /** + * @return a map of available text labels for this presentation. Each label is indexed by its + * locale corresponding to the language code as specified by ISO 639-2 [42]. Either ISO 639-2/B + * or ISO 639-2/T could be used. + */ + public Map getLabels() { + Map localeLabels = new HashMap<>(); + for (Map.Entry entry : mLabels.entrySet()) { + localeLabels.put(new Locale(entry.getKey()), entry.getValue()); + } + return localeLabels; + } + + /** + * @return the locale corresponding to audio presentation's ISO 639-1/639-2 language code. + */ + public Locale getLocale() { + return new Locale(mLanguage); + } + + /** + * @return the mastering indication of the audio presentation. + * See {@link #MASTERING_NOT_INDICATED}, {@link #MASTERED_FOR_STEREO}, + * {@link #MASTERED_FOR_SURROUND}, {@link #MASTERED_FOR_3D}, {@link #MASTERED_FOR_HEADPHONE} + */ + @MasteringIndicationType + public int getMasteringIndication() { + return mMasteringIndication; + } + + /** + * Indicates whether an audio description for the visually impaired is available. + * @return {@code true} if audio description is available. + */ + public boolean hasAudioDescription() { + return mAudioDescriptionAvailable; + } + + /** + * Indicates whether spoken subtitles for the visually impaired are available. + * @return {@code true} if spoken subtitles are available. + */ + public boolean hasSpokenSubtitles() { + return mSpokenSubtitlesAvailable; + } + + /** + * Indicates whether dialogue enhancement is available. + * @return {@code true} if dialogue enhancement is available. + */ + public boolean hasDialogueEnhancement() { + return mDialogueEnhancementAvailable; + } +} diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 5928d03dc4a..9f2e973832d 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -1989,6 +1989,25 @@ public class AudioTrack extends PlayerBase return native_set_loop(startInFrames, endInFrames, loopCount); } + /** + * Sets the audio presentation. + * If the audio presentation is invalid then {@link #ERROR_BAD_VALUE} will be returned. + * If a multi-stream decoder (MSD) is not present, or the format does not support + * multiple presentations, then {@link #ERROR_INVALID_OPERATION} will be returned. + * @param presentation see {@link AudioPresentation}. In particular, id should be set. + * @return error code or success, see {@link #SUCCESS}, {@link #ERROR_BAD_VALUE}, + * {@link #ERROR_INVALID_OPERATION} + * @throws IllegalArgumentException if the audio presentation is null. + * @throws IllegalStateException if track is not initialized. + */ + public int setPresentation(@NonNull AudioPresentation presentation) { + if (presentation == null) { + throw new IllegalArgumentException("audio presentation is null"); + } + return native_setPresentation(presentation.getPresentationId(), + presentation.getProgramId()); + } + /** * Sets the initialization state of the instance. This method was originally intended to be used * in an AudioTrack subclass constructor to set a subclass-specific post-initialization state. @@ -3227,6 +3246,7 @@ public class AudioTrack extends PlayerBase @NonNull VolumeShaper.Operation operation); private native @Nullable VolumeShaper.State native_getVolumeShaperState(int id); + private native final int native_setPresentation(int presentationId, int programId); //--------------------------------------------------------- // Utility methods diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java index 2c1b4b3526e..1cebe4ca0f2 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; +import android.media.AudioPresentation; import android.media.MediaCodec; import android.media.MediaFormat; import android.media.MediaHTTPService; @@ -40,6 +41,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -395,6 +397,17 @@ final public class MediaExtractor { return null; } + /** + * Get the list of available audio presentations for the track. + * @param trackIndex index of the track. + * @return a list of available audio presentations for a given valid audio track index. + * The list will be empty if the source does not contain any audio presentations. + */ + @NonNull + public List getAudioPresentations(int trackIndex) { + return new ArrayList(); + } + /** * Get the PSSH info if present. * @return a map of uuid-to-bytes, with the uuid specifying diff --git a/media/jni/android_media_AudioPresentation.h b/media/jni/android_media_AudioPresentation.h new file mode 100644 index 00000000000..71b8dacfbdf --- /dev/null +++ b/media/jni/android_media_AudioPresentation.h @@ -0,0 +1,173 @@ +/* + * Copyright 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ANDROID_MEDIA_AUDIO_PRESENTATION_H_ +#define _ANDROID_MEDIA_AUDIO_PRESENTATION_H_ + +#include "jni.h" + +#include +#include +#include + +#include + +namespace android { + +struct JAudioPresentationInfo { + struct fields_t { + jclass clazz; + jmethodID constructID; + + // list parameters + jclass listclazz; + jmethodID listConstructId; + jmethodID listAddId; + + void init(JNIEnv *env) { + jclass lclazz = env->FindClass("android/media/AudioPresentation"); + if (lclazz == NULL) { + return; + } + + clazz = (jclass)env->NewGlobalRef(lclazz); + if (clazz == NULL) { + return; + } + + constructID = env->GetMethodID(clazz, "", + "(IILjava/util/Map;Ljava/lang/String;IZZZ)V"); + env->DeleteLocalRef(lclazz); + + // list objects + jclass llistclazz = env->FindClass("java/util/ArrayList"); + CHECK(llistclazz != NULL); + listclazz = static_cast(env->NewGlobalRef(llistclazz)); + CHECK(listclazz != NULL); + listConstructId = env->GetMethodID(listclazz, "", "()V"); + CHECK(listConstructId != NULL); + listAddId = env->GetMethodID(listclazz, "add", "(Ljava/lang/Object;)Z"); + CHECK(listAddId != NULL); + env->DeleteLocalRef(llistclazz); + } + + void exit(JNIEnv *env) { + env->DeleteGlobalRef(clazz); + clazz = NULL; + env->DeleteGlobalRef(listclazz); + listclazz = NULL; + } + }; + + static status_t ConvertMessageToMap(JNIEnv *env, const sp &msg, jobject *map) { + ScopedLocalRef hashMapClazz(env, env->FindClass("java/util/HashMap")); + + if (hashMapClazz.get() == NULL) { + return -EINVAL; + } + jmethodID hashMapConstructID = + env->GetMethodID(hashMapClazz.get(), "", "()V"); + + if (hashMapConstructID == NULL) { + return -EINVAL; + } + jmethodID hashMapPutID = + env->GetMethodID( + hashMapClazz.get(), + "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + + if (hashMapPutID == NULL) { + return -EINVAL; + } + + jobject hashMap = env->NewObject(hashMapClazz.get(), hashMapConstructID); + + for (size_t i = 0; i < msg->countEntries(); ++i) { + AMessage::Type valueType; + const char *key = msg->getEntryNameAt(i, &valueType); + + if (!strncmp(key, "android._", 9)) { + // don't expose private keys (starting with android._) + continue; + } + + jobject valueObj = NULL; + + AString val; + CHECK(msg->findString(key, &val)); + + valueObj = env->NewStringUTF(val.c_str()); + + if (valueObj != NULL) { + jstring keyObj = env->NewStringUTF(key); + + env->CallObjectMethod(hashMap, hashMapPutID, keyObj, valueObj); + + env->DeleteLocalRef(keyObj); keyObj = NULL; + env->DeleteLocalRef(valueObj); valueObj = NULL; + } + } + + *map = hashMap; + + return OK; + } + + jobject asJobject(JNIEnv *env, const fields_t& fields, const AudioPresentationInfo &info) { + jobject list = env->NewObject(fields.listclazz, fields.listConstructId); + + for (size_t i = 0; i < info.countPresentations(); ++i) { + const sp &ap = info.getPresentation(i); + jobject jLabelObject; + + sp labelMessage = new AMessage(); + for (size_t i = 0; i < ap->mLabels.size(); ++i) { + labelMessage->setString(ap->mLabels.keyAt(i).string(), + ap->mLabels.valueAt(i).string()); + } + if (ConvertMessageToMap(env, labelMessage, &jLabelObject) != OK) { + return NULL; + } + jstring jLanguage = env->NewStringUTF(ap->mLanguage.string()); + + jobject jValueObj = env->NewObject(fields.clazz, fields.constructID, + static_cast(ap->mPresentationId), + static_cast(ap->mProgramId), + jLabelObject, + jLanguage, + static_cast(ap->mMasteringIndication), + static_cast((ap->mAudioDescriptionAvailable == 1) ? + 1 : 0), + static_cast((ap->mSpokenSubtitlesAvailable == 1) ? + 1 : 0), + static_cast((ap->mDialogueEnhancementAvailable == 1) ? + 1 : 0)); + if (jValueObj == NULL) { + env->DeleteLocalRef(jLanguage); jLanguage = NULL; + return NULL; + } + + env->CallBooleanMethod(list, fields.listAddId, jValueObj); + env->DeleteLocalRef(jValueObj); jValueObj = NULL; + env->DeleteLocalRef(jLanguage); jLanguage = NULL; + } + return list; + } +}; +} // namespace android + +#endif // _ANDROID_MEDIA_AUDIO_PRESENTATION_H_ -- GitLab From 72a73ea1cfd8b6d9f6067fe12731cbef396bf0e0 Mon Sep 17 00:00:00 2001 From: Chad Brubaker Date: Fri, 26 Jan 2018 15:56:55 -0800 Subject: [PATCH 032/416] Fix lockdown button placement Test: Visually verified new placement Change-Id: Ib57c8504d341bf07ef982000f714e2966d370336 Fixes: 72555245 --- core/res/res/values/config.xml | 2 +- .../systemui/globalactions/GlobalActionsDialog.java | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index d2685cfb5a8..9c0185e44b5 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2482,9 +2482,9 @@ power restart logout + lockdown bugreport users - lockdown + Date: Mon, 29 Jan 2018 10:32:13 -0500 Subject: [PATCH 041/416] Test is causing others to break in specific cases - ZenModePanel is not currently being used (changed to EnableZenModeDialog.java), so ignore test Test: runtest systemui (ZenmodePanelTest don't run) Bug: 72636697 Change-Id: I7bcb628e303c55bc4764c61c82a75b5ebdeeeade --- .../src/com/android/systemui/volume/ZenModePanelTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java index 0fdbfd1944f..4ab2063196d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/ZenModePanelTest.java @@ -30,15 +30,18 @@ import android.service.notification.ZenModeConfig; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.test.FlakyTest; import android.view.LayoutInflater; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.ZenModeController; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +@Ignore @SmallTest @RunWith(AndroidJUnit4.class) public class ZenModePanelTest extends SysuiTestCase { -- GitLab From 5dd532315ed80d4dcdbbe4d0f2b265f7a869157c Mon Sep 17 00:00:00 2001 From: Kweku Adams Date: Fri, 26 Jan 2018 16:49:04 -0800 Subject: [PATCH 042/416] Adding privacy tags to last few protos. I left out the protos that aren't officially used in incident.proto in any way. The notification captions are set in NotificationManagerService and ConditionProviders. They're not set by the app at all. Bug: 72393215 Test: flash device and check incident.proto output Change-Id: I4b36e066740fa1e6755eb946e2bcfa4959d9f9db --- core/proto/android/app/window_configuration.proto | 3 +++ core/proto/android/media/audioattributes.proto | 6 +++++- core/proto/android/server/forceappstandbytracker.proto | 5 +++-- core/proto/android/server/intentresolver.proto | 6 ++++++ core/proto/android/server/statlogger.proto | 4 ++++ .../proto/android/server/wirelesschargerdetector.proto | 6 ++++++ core/proto/android/service/notification.proto | 4 ++-- core/proto/android/util/event_log_tags.proto | 10 +++++++++- core/proto/android/view/displaycutout.proto | 3 +++ core/proto/android/view/surfacecontrol.proto | 6 +++++- 10 files changed, 46 insertions(+), 7 deletions(-) diff --git a/core/proto/android/app/window_configuration.proto b/core/proto/android/app/window_configuration.proto index 4d748e8fb1d..1e8ace4fa06 100644 --- a/core/proto/android/app/window_configuration.proto +++ b/core/proto/android/app/window_configuration.proto @@ -21,9 +21,12 @@ option java_multiple_files = true; package android.app; import "frameworks/base/core/proto/android/graphics/rect.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; /** Proto representation for WindowConfiguration.java class. */ message WindowConfigurationProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.graphics.RectProto app_bounds = 1; optional int32 windowing_mode = 2; optional int32 activity_type = 3; diff --git a/core/proto/android/media/audioattributes.proto b/core/proto/android/media/audioattributes.proto index 860d60807c0..ef04720405f 100644 --- a/core/proto/android/media/audioattributes.proto +++ b/core/proto/android/media/audioattributes.proto @@ -20,15 +20,19 @@ option java_multiple_files = true; package android.media; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + /** * An android.media.AudioAttributes object. */ message AudioAttributesProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional Usage usage = 1; optional ContentType content_type = 2; // Bit representation of set flags. optional int32 flags = 3; - repeated string tags = 4; + repeated string tags = 4 [ (android.privacy).dest = DEST_EXPLICIT ]; } enum ContentType { diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto index d0fd0b4bbc4..43c869c7670 100644 --- a/core/proto/android/server/forceappstandbytracker.proto +++ b/core/proto/android/server/forceappstandbytracker.proto @@ -17,13 +17,12 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/server/statlogger.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; package com.android.server; option java_multiple_files = true; -import "frameworks/base/libs/incident/proto/android/privacy.proto"; - // Dump from com.android.server.ForceAppStandbyTracker. message ForceAppStandbyTrackerProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; @@ -62,6 +61,8 @@ message ForceAppStandbyTrackerProto { optional StatLoggerProto stats = 9; message ExemptedPackage { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 userId = 1; optional string package_name = 2; } diff --git a/core/proto/android/server/intentresolver.proto b/core/proto/android/server/intentresolver.proto index 60c060c02b1..0ada89533cb 100644 --- a/core/proto/android/server/intentresolver.proto +++ b/core/proto/android/server/intentresolver.proto @@ -19,9 +19,15 @@ option java_multiple_files = true; package com.android.server; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + message IntentResolverProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + message ArrayMapEntry { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + optional string key = 1; repeated string values = 2; } diff --git a/core/proto/android/server/statlogger.proto b/core/proto/android/server/statlogger.proto index fa430d8264d..2ae526a5996 100644 --- a/core/proto/android/server/statlogger.proto +++ b/core/proto/android/server/statlogger.proto @@ -20,8 +20,12 @@ package com.android.server; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + // Dump from StatLogger. message StatLoggerProto { + option (.android.msg_privacy).dest = DEST_EXPLICIT; + message Event { optional int32 eventId = 1; optional string label = 2; diff --git a/core/proto/android/server/wirelesschargerdetector.proto b/core/proto/android/server/wirelesschargerdetector.proto index 89cf2f8900f..2118deb6ede 100644 --- a/core/proto/android/server/wirelesschargerdetector.proto +++ b/core/proto/android/server/wirelesschargerdetector.proto @@ -19,8 +19,14 @@ package com.android.server.power; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + message WirelessChargerDetectorProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + message VectorProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional float x = 1; optional float y = 2; optional float z = 3; diff --git a/core/proto/android/service/notification.proto b/core/proto/android/service/notification.proto index 9013a23664f..5c40e5fe346 100644 --- a/core/proto/android/service/notification.proto +++ b/core/proto/android/service/notification.proto @@ -50,7 +50,7 @@ message NotificationServiceDumpProto { message NotificationRecordProto { option (.android.msg_privacy).dest = DEST_AUTOMATIC; - optional string key = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional string key = 1; enum State { ENQUEUED = 0; @@ -88,7 +88,7 @@ message ManagedServiceInfoProto { message ManagedServicesProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; - optional string caption = 1 [ (.android.privacy).dest = DEST_EXPLICIT ]; + optional string caption = 1; message ServiceProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; diff --git a/core/proto/android/util/event_log_tags.proto b/core/proto/android/util/event_log_tags.proto index cb039be55b8..457219fde83 100644 --- a/core/proto/android/util/event_log_tags.proto +++ b/core/proto/android/util/event_log_tags.proto @@ -19,17 +19,25 @@ package android.util; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + // Proto representation of event.logtags. // Usually sit in /system/etc/event-log-tags. message EventLogTagMapProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + repeated EventLogTag event_log_tags = 1; } message EventLogTag { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional uint32 tag_number = 1; // keyed by tag number. optional string tag_name = 2; message ValueDescriptor { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional string name = 1; enum DataType { @@ -55,4 +63,4 @@ message EventLogTag { optional DataUnit unit = 3; } repeated ValueDescriptor value_descriptors = 3; -} \ No newline at end of file +} diff --git a/core/proto/android/view/displaycutout.proto b/core/proto/android/view/displaycutout.proto index ff13fab35bf..ee258b7369a 100644 --- a/core/proto/android/view/displaycutout.proto +++ b/core/proto/android/view/displaycutout.proto @@ -17,11 +17,14 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/graphics/rect.proto"; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; package android.view; option java_multiple_files = true; message DisplayCutoutProto { + option (.android.msg_privacy).dest = DEST_AUTOMATIC; + optional .android.graphics.RectProto insets = 1; optional .android.graphics.RectProto bounds = 2; } diff --git a/core/proto/android/view/surfacecontrol.proto b/core/proto/android/view/surfacecontrol.proto index 9288b4fc367..665d688b05a 100644 --- a/core/proto/android/view/surfacecontrol.proto +++ b/core/proto/android/view/surfacecontrol.proto @@ -19,10 +19,14 @@ package android.view; option java_multiple_files = true; +import "frameworks/base/libs/incident/proto/android/privacy.proto"; + /** * Represents a {@link android.view.SurfaceControl} object. */ message SurfaceControlProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + optional int32 hash_code = 1; - optional string name = 2; + optional string name = 2 [ (android.privacy).dest = DEST_EXPLICIT ]; } -- GitLab From 31c11a05a493e18e5c950b3a9d318094ad7525b2 Mon Sep 17 00:00:00 2001 From: Emilian Peev Date: Mon, 29 Jan 2018 16:41:57 +0000 Subject: [PATCH 043/416] Camera: "getMaxSharedSurfaceCount" should be non-static Allow for more flexiblity in case the maximum shared supported shared surface count changes in the future. Bug: 72562887 Test: Camera CTS Change-Id: If3be8659c05997502368f51a14152a4516a9f159 --- api/current.txt | 2 +- .../android/hardware/camera2/params/OutputConfiguration.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/current.txt b/api/current.txt index 292ef5275aa..c36fe0c852d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -16338,7 +16338,7 @@ package android.hardware.camera2.params { method public void addSurface(android.view.Surface); method public int describeContents(); method public void enableSurfaceSharing(); - method public static int getMaxSharedSurfaceCount(); + method public int getMaxSharedSurfaceCount(); method public android.view.Surface getSurface(); method public int getSurfaceGroupId(); method public java.util.List getSurfaces(); diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index f47cd665fd9..eb4bcededbf 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -576,7 +576,7 @@ public final class OutputConfiguration implements Parcelable { * * @see #enableSurfaceSharing */ - public static int getMaxSharedSurfaceCount() { + public int getMaxSharedSurfaceCount() { return MAX_SURFACES_COUNT; } -- GitLab From c71ab6049f7d6f8d509bdc11f21776ae3eb167f8 Mon Sep 17 00:00:00 2001 From: Mikael Magnusson Date: Mon, 29 Jan 2018 17:56:10 +0100 Subject: [PATCH 044/416] Prevent ArrayIndexOutOfBoundsException for some invalid sysui_nav_bar values A user can change sysui_nav_bar via adb. If value of sysui_nav_bar doesn't contain two semicolons, split String array is accessed out of bounds throwing an exception, sending SystemUI in an exception loop. Test: manual Test: adb exec-out settings put secure sysui_nav_bar "home;back" Change-Id: Ia9d74be36d287085650393476029489c9a359a0f --- .../systemui/statusbar/phone/NavigationBarInflaterView.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java index 4e79314bb81..9f89fe6cb37 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java @@ -219,6 +219,11 @@ public class NavigationBarInflaterView extends FrameLayout newLayout = getDefaultLayout(); } String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3); + if (sets.length != 3) { + Log.d(TAG, "Invalid layout."); + newLayout = getDefaultLayout(); + sets = newLayout.split(GRAVITY_SEPARATOR, 3); + } String[] start = sets[0].split(BUTTON_SEPARATOR); String[] center = sets[1].split(BUTTON_SEPARATOR); String[] end = sets[2].split(BUTTON_SEPARATOR); -- GitLab From c43dfdf077438d67cb2bcf3b95c4b9846a045116 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Mon, 29 Jan 2018 17:55:44 +0100 Subject: [PATCH 045/416] Surface: Fix bad casts Fixes bad reinterpret_casts to ANativeWindow* where the reinterpreted pointer is actually a Surface* Test: make droid Bug: 72492508 Change-Id: Iadc92e8e82efab26be584f5a1c83c678e4496505 --- core/jni/android_view_Surface.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 421e0de52cc..f5c09fd316d 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -524,15 +524,15 @@ static jint nativeAttachAndQueueBuffer(JNIEnv *env, jclass clazz, jlong nativeOb static jint nativeSetSharedBufferModeEnabled(JNIEnv* env, jclass clazz, jlong nativeObject, jboolean enabled) { Surface* surface = reinterpret_cast(nativeObject); - return ((ANativeWindow*) nativeObject)->perform(surface, - NATIVE_WINDOW_SET_SHARED_BUFFER_MODE, enabled); + ANativeWindow* anw = static_cast(surface); + return anw->perform(surface, NATIVE_WINDOW_SET_SHARED_BUFFER_MODE, int(enabled)); } static jint nativeSetAutoRefreshEnabled(JNIEnv* env, jclass clazz, jlong nativeObject, jboolean enabled) { Surface* surface = reinterpret_cast(nativeObject); - return ((ANativeWindow*) nativeObject)->perform(surface, - NATIVE_WINDOW_SET_AUTO_REFRESH, enabled); + ANativeWindow* anw = static_cast(surface); + return anw->perform(surface, NATIVE_WINDOW_SET_AUTO_REFRESH, int(enabled)); } namespace uirenderer { -- GitLab From c07a96d1e4a90c1c42040fd24ad9e4f209becce4 Mon Sep 17 00:00:00 2001 From: Pavel Maltsev Date: Tue, 31 Oct 2017 15:34:16 -0700 Subject: [PATCH 046/416] Enable multiple active Ethernet interfaces - add Ethernet interface configurations to config.xml; no vendors can specify network capabilities (in particular they can mark network as restricted which make sense for embedded applications + static IP configuration) - extend EthernetManager to support multiple interfaces, use interface name as an identificator - extend IpConfigStore to store IP configuration based on string identifier (e.g. ethernet name) Test: runtest -x frameworks/base/services/tests/servicestests/ -c com.android.server.net.IpConfigStoreTest Change-Id: Ic1e70003f2380ca8edb4469d6b34e27c5e8cf059 --- core/java/android/net/EthernetManager.java | 49 ++++-- core/java/android/net/IEthernetManager.aidl | 7 +- .../android/net/IEthernetServiceListener.aidl | 2 +- core/res/res/values/config.xml | 17 +++ core/res/res/values/symbols.xml | 1 + .../com/android/server/net/IpConfigStore.java | 100 ++++++++++--- .../android/server/net/IpConfigStoreTest.java | 140 ++++++++++++++++++ 7 files changed, 277 insertions(+), 39 deletions(-) create mode 100644 services/tests/servicestests/src/com/android/server/net/IpConfigStoreTest.java diff --git a/core/java/android/net/EthernetManager.java b/core/java/android/net/EthernetManager.java index 31a30968cbc..ecccda588ae 100644 --- a/core/java/android/net/EthernetManager.java +++ b/core/java/android/net/EthernetManager.java @@ -18,9 +18,6 @@ package android.net; import android.annotation.SystemService; import android.content.Context; -import android.net.IEthernetManager; -import android.net.IEthernetServiceListener; -import android.net.IpConfiguration; import android.os.Handler; import android.os.Message; import android.os.RemoteException; @@ -45,18 +42,18 @@ public class EthernetManager { if (msg.what == MSG_AVAILABILITY_CHANGED) { boolean isAvailable = (msg.arg1 == 1); for (Listener listener : mListeners) { - listener.onAvailabilityChanged(isAvailable); + listener.onAvailabilityChanged((String) msg.obj, isAvailable); } } } }; - private final ArrayList mListeners = new ArrayList(); + private final ArrayList mListeners = new ArrayList<>(); private final IEthernetServiceListener.Stub mServiceListener = new IEthernetServiceListener.Stub() { @Override - public void onAvailabilityChanged(boolean isAvailable) { + public void onAvailabilityChanged(String iface, boolean isAvailable) { mHandler.obtainMessage( - MSG_AVAILABILITY_CHANGED, isAvailable ? 1 : 0, 0, null).sendToTarget(); + MSG_AVAILABILITY_CHANGED, isAvailable ? 1 : 0, 0, iface).sendToTarget(); } }; @@ -66,9 +63,10 @@ public class EthernetManager { public interface Listener { /** * Called when Ethernet port's availability is changed. - * @param isAvailable {@code true} if one or more Ethernet port exists. + * @param iface Ethernet interface name + * @param isAvailable {@code true} if Ethernet port exists. */ - public void onAvailabilityChanged(boolean isAvailable); + void onAvailabilityChanged(String iface, boolean isAvailable); } /** @@ -86,9 +84,9 @@ public class EthernetManager { * Get Ethernet configuration. * @return the Ethernet Configuration, contained in {@link IpConfiguration}. */ - public IpConfiguration getConfiguration() { + public IpConfiguration getConfiguration(String iface) { try { - return mService.getConfiguration(); + return mService.getConfiguration(iface); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -97,21 +95,29 @@ public class EthernetManager { /** * Set Ethernet configuration. */ - public void setConfiguration(IpConfiguration config) { + public void setConfiguration(String iface, IpConfiguration config) { try { - mService.setConfiguration(config); + mService.setConfiguration(iface, config); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** - * Indicates whether the system currently has one or more - * Ethernet interfaces. + * Indicates whether the system currently has one or more Ethernet interfaces. */ public boolean isAvailable() { + return getAvailableInterfaces().length > 0; + } + + /** + * Indicates whether the system has given interface. + * + * @param iface Ethernet interface name + */ + public boolean isAvailable(String iface) { try { - return mService.isAvailable(); + return mService.isAvailable(iface); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -136,6 +142,17 @@ public class EthernetManager { } } + /** + * Returns an array of available Ethernet interface names. + */ + public String[] getAvailableInterfaces() { + try { + return mService.getAvailableInterfaces(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + /** * Removes a listener. * @param listener A {@link Listener} to remove. diff --git a/core/java/android/net/IEthernetManager.aidl b/core/java/android/net/IEthernetManager.aidl index 7a92eb955ac..94960b51d32 100644 --- a/core/java/android/net/IEthernetManager.aidl +++ b/core/java/android/net/IEthernetManager.aidl @@ -26,9 +26,10 @@ import android.net.IEthernetServiceListener; /** {@hide} */ interface IEthernetManager { - IpConfiguration getConfiguration(); - void setConfiguration(in IpConfiguration config); - boolean isAvailable(); + String[] getAvailableInterfaces(); + IpConfiguration getConfiguration(String iface); + void setConfiguration(String iface, in IpConfiguration config); + boolean isAvailable(String iface); void addListener(in IEthernetServiceListener listener); void removeListener(in IEthernetServiceListener listener); } diff --git a/core/java/android/net/IEthernetServiceListener.aidl b/core/java/android/net/IEthernetServiceListener.aidl index 356690e8f7a..782fa19d9df 100644 --- a/core/java/android/net/IEthernetServiceListener.aidl +++ b/core/java/android/net/IEthernetServiceListener.aidl @@ -19,5 +19,5 @@ package android.net; /** @hide */ oneway interface IEthernetServiceListener { - void onAvailabilityChanged(boolean isAvailable); + void onAvailabilityChanged(String iface, boolean isAvailable); } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5cc727d28cd..1be7fae91a9 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -360,6 +360,23 @@ eth\\d + + + + + + + Disable eSIM + + Can\u2019t disable eSIM + + The eSIM can\u2019t be disabled due to an error. Enter @@ -146,8 +150,8 @@ Enter SIM PIN. Enter SIM PIN for \"%1$s\". - - Disable eSIM to use device without mobile service. + + %1$s Disable eSIM to use device without mobile service. Enter PIN diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java index cb5afec7907..b8a07cdbdc4 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardEsimArea.java @@ -16,14 +16,18 @@ package com.android.keyguard; +import android.app.AlertDialog; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Handler; +import android.os.HandlerThread; import android.os.UserHandle; import android.util.AttributeSet; import android.view.View; +import android.view.WindowManager; import android.widget.Button; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionInfo; @@ -50,8 +54,17 @@ class KeyguardEsimArea extends Button implements View.OnClickListener { if (ACTION_DISABLE_ESIM.equals(intent.getAction())) { int resultCode = getResultCode(); if (resultCode != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) { - // TODO (b/62680294): Surface more info. to the end users for this failure. Log.e(TAG, "Error disabling esim, result code = " + resultCode); + AlertDialog.Builder builder = + new AlertDialog.Builder(mContext) + .setMessage(R.string.error_disable_esim_msg) + .setTitle(R.string.error_disable_esim_title) + .setCancelable(false /* cancelable */) + .setNeutralButton(R.string.ok, null /* listener */); + AlertDialog alertDialog = builder.create(); + alertDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + alertDialog.show(); } } } @@ -101,14 +114,13 @@ class KeyguardEsimArea extends Button implements View.OnClickListener { @Override public void onClick(View v) { - Intent intent = new Intent(mContext, KeyguardEsimArea.class); - intent.setAction(ACTION_DISABLE_ESIM); + Intent intent = new Intent(ACTION_DISABLE_ESIM); intent.setPackage(mContext.getPackageName()); - PendingIntent callbackIntent = PendingIntent.getBroadcast( + PendingIntent callbackIntent = PendingIntent.getBroadcastAsUser( mContext, 0 /* requestCode */, intent, - PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent.FLAG_UPDATE_CURRENT, UserHandle.SYSTEM); mEuiccManager .switchToSubscription(SubscriptionManager.INVALID_SUBSCRIPTION_ID, callbackIntent); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java index e7432ba5855..703b2053139 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java @@ -131,7 +131,7 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { } if (isEsimLocked) { - msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim); + msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); } mSecurityMessageDisplay.setMessage(msg); @@ -187,6 +187,10 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed; displayMessage = getContext().getString(msgId); } + if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { + displayMessage = getResources() + .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); + } if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:" + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); return displayMessage; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java index afee8ece26c..347c9792ec9 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java @@ -181,7 +181,7 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { } } if (isEsimLocked) { - msg = msg + " " + rez.getString(R.string.kg_sim_lock_instructions_esim); + msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); } mSecurityMessageDisplay.setMessage(msg); mSimImageView.setImageTintList(ColorStateList.valueOf(color)); @@ -231,6 +231,10 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { R.string.kg_password_puk_failed; displayMessage = getContext().getString(msgId); } + if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { + displayMessage = getResources() + .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); + } if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:" + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); return displayMessage; -- GitLab From daf17d415c1a99c515ffa75f3ec3bb0fb87627fe Mon Sep 17 00:00:00 2001 From: Alex Light Date: Mon, 29 Jan 2018 14:27:38 -0800 Subject: [PATCH 061/416] Make AndroidRuntime only start the debugger for zygote forked apps. This got changed unintentionally in commit fffb273. Restore the original behavior where JDWP will not be enabled for non-zygote apps. Bug: 72400560 Test: atest CtsJdwpSecurityHostTestCases Change-Id: I364a9d8b6e87efc1604741a7e5dd68221ed8e491 --- core/jni/AndroidRuntime.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index d7f725d03ed..3784d4daa2e 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -761,18 +761,17 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) /* * Enable debugging only for apps forked from zygote. - * Set suspend=y to pause during VM init and use android ADB transport. */ if (zygote) { + // Set the JDWP provider and required arguments. By default let the runtime choose how JDWP is + // implemented. When this is not set the runtime defaults to not allowing JDWP. addOption("-XjdwpOptions:suspend=n,server=y"); + parseRuntimeOption("dalvik.vm.jdwp-provider", + jdwpProviderBuf, + "-XjdwpProvider:", + "default"); } - // Set the JDWP provider. By default let the runtime choose. - parseRuntimeOption("dalvik.vm.jdwp-provider", - jdwpProviderBuf, - "-XjdwpProvider:", - "default"); - parseRuntimeOption("dalvik.vm.lockprof.threshold", lockProfThresholdBuf, "-Xlockprofthreshold:"); -- GitLab From ac8b69a71596f6e40a44bc5edd65c864d2fa1bd4 Mon Sep 17 00:00:00 2001 From: Siddharth Ray Date: Sat, 27 Jan 2018 18:05:44 -0800 Subject: [PATCH 062/416] Wifi power stats proto Wifi power stats added to metrics proto. BUG:72383800 Test: Manual Change-Id: I42a04c7d27922d2b9ced534a48c90e65d3517323 --- proto/src/wifi.proto | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto index f5349df0bd9..f6a54af8919 100644 --- a/proto/src/wifi.proto +++ b/proto/src/wifi.proto @@ -373,6 +373,9 @@ message WifiLog { // Wps connection metrics optional WpsMetrics wps_metrics = 91; + + // Wifi power statistics + optional WifiPowerStats wifi_power_stats = 92; } // Information that gets logged for every WiFi connection. @@ -1134,3 +1137,22 @@ message WpsMetrics { // Total number of wps cancellation optional int32 num_wps_cancellation = 8; } + +// Power stats for Wifi +message WifiPowerStats { + + // Duration of log (ms) + optional int64 logging_duration_ms = 1; + + // Energy consumed by wifi (mAh) + optional double energy_consumed_mah = 2; + + // Amount of time wifi is in idle (ms) + optional int64 idle_time_ms = 3; + + // Amount of time wifi is in rx (ms) + optional int64 rx_time_ms = 4; + + // Amount of time wifi is in tx (ms) + optional int64 tx_time_ms = 5; +} \ No newline at end of file -- GitLab From d32906c202db3b84151c310ecd89a07bb41208f7 Mon Sep 17 00:00:00 2001 From: Abodunrinwa Toki Date: Thu, 18 Jan 2018 04:34:44 -0800 Subject: [PATCH 063/416] Introduce a TextClassifierManagerService. Apps wanting to use a TextClassifier service (instead of an in-app-process TextClassifier) bind to this service. The service binds to and reroutes calls to a configured system TextClassifierService. TextClassifierManagerService manages the lifecycle of the configured TextClassifierService and binds/unbinds to preserve system health. A configurable TextClassifierService extends TextClassifierService, declares an android.textclassifier.TextClassifierService intent, and requires a permission that is only granted to the system so only the system may bind to it. The TextClassifierManagerService implements a similar interface to TextClassifierService (i.e. ITextClassifierService) but doesn't have to. This is done for simplicity sake and things may change in the future. The configuration of the default service is in config.xml. OEMs may change this with a config overlay. If no TextClassifierService is specified, the default in app process TextClassifierImpl is used. Bug: 67609167 Test: bit FrameworksCoreTests:android.view.textclassifier.TextClassificationManagerTest Test: tbd Change-Id: I8e7bd6d12aa1a772897529c3b12f47f48757cfe6 --- Android.bp | 4 + api/current.txt | 10 +- api/system-current.txt | 19 + .../ITextClassificationCallback.aidl | 28 ++ .../ITextClassifierService.aidl | 47 +++ .../textclassifier/ITextLinksCallback.aidl | 28 ++ .../ITextSelectionCallback.aidl | 28 ++ .../textclassifier/TextClassifierService.java | 290 +++++++++++++ .../textclassifier/SystemTextClassifier.java | 197 +++++++++ .../textclassifier/TextClassification.aidl | 20 + .../textclassifier/TextClassification.java | 118 ++---- .../TextClassificationManager.java | 48 ++- .../view/textclassifier/TextClassifier.java | 52 ++- .../textclassifier/TextClassifierImpl.java | 45 +- .../view/textclassifier/TextLinks.aidl | 20 + .../view/textclassifier/TextSelection.aidl | 20 + .../view/textclassifier/TextSelection.java | 82 ++-- .../widget/SelectionActionModeHelper.java | 1 + core/res/AndroidManifest.xml | 8 + core/res/res/values/config.xml | 9 + core/res/res/values/symbols.xml | 1 + .../TextClassificationTest.java | 10 +- .../textclassifier/TextSelectionTest.java | 10 +- .../TextClassificationManagerService.java | 395 ++++++++++++++++++ .../java/com/android/server/SystemServer.java | 9 + 25 files changed, 1321 insertions(+), 178 deletions(-) create mode 100644 core/java/android/service/textclassifier/ITextClassificationCallback.aidl create mode 100644 core/java/android/service/textclassifier/ITextClassifierService.aidl create mode 100644 core/java/android/service/textclassifier/ITextLinksCallback.aidl create mode 100644 core/java/android/service/textclassifier/ITextSelectionCallback.aidl create mode 100644 core/java/android/service/textclassifier/TextClassifierService.java create mode 100644 core/java/android/view/textclassifier/SystemTextClassifier.java create mode 100644 core/java/android/view/textclassifier/TextClassification.aidl create mode 100644 core/java/android/view/textclassifier/TextLinks.aidl create mode 100644 core/java/android/view/textclassifier/TextSelection.aidl create mode 100644 services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java diff --git a/Android.bp b/Android.bp index 763d242a12f..ad306cfad01 100644 --- a/Android.bp +++ b/Android.bp @@ -319,6 +319,10 @@ java_library { "core/java/android/service/chooser/IChooserTargetResult.aidl", "core/java/android/service/resolver/IResolverRankerService.aidl", "core/java/android/service/resolver/IResolverRankerResult.aidl", + "core/java/android/service/textclassifier/ITextClassificationCallback.aidl", + "core/java/android/service/textclassifier/ITextClassifierService.aidl", + "core/java/android/service/textclassifier/ITextLinksCallback.aidl", + "core/java/android/service/textclassifier/ITextSelectionCallback.aidl", "core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl", "core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl", "core/java/android/view/accessibility/IAccessibilityManager.aidl", diff --git a/api/current.txt b/api/current.txt index 292ef5275aa..56c0dc43367 100644 --- a/api/current.txt +++ b/api/current.txt @@ -49983,7 +49983,8 @@ package android.view.inputmethod { package android.view.textclassifier { - public final class TextClassification { + public final class TextClassification implements android.os.Parcelable { + method public int describeContents(); method public float getConfidenceScore(java.lang.String); method public java.lang.String getEntity(int); method public int getEntityCount(); @@ -49997,6 +49998,8 @@ package android.view.textclassifier { method public java.lang.CharSequence getSecondaryLabel(int); method public java.lang.String getSignature(); method public java.lang.String getText(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; } public static final class TextClassification.Builder { @@ -50102,13 +50105,16 @@ package android.view.textclassifier { field public static final android.os.Parcelable.Creator CREATOR; } - public final class TextSelection { + public final class TextSelection implements android.os.Parcelable { + method public int describeContents(); method public float getConfidenceScore(java.lang.String); method public java.lang.String getEntity(int); method public int getEntityCount(); method public int getSelectionEndIndex(); method public int getSelectionStartIndex(); method public java.lang.String getSignature(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; } public static final class TextSelection.Builder { diff --git a/api/system-current.txt b/api/system-current.txt index 663ad112280..52612d52220 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -29,6 +29,7 @@ package android { field public static final java.lang.String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE"; field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE"; field public static final java.lang.String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE"; + field public static final java.lang.String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE"; field public static final java.lang.String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT"; field public static final java.lang.String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE"; field public static final java.lang.String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED"; @@ -4438,6 +4439,24 @@ package android.service.settings.suggestions { } +package android.service.textclassifier { + + public abstract class TextClassifierService extends android.app.Service { + ctor public TextClassifierService(); + method public final android.os.IBinder onBind(android.content.Intent); + method public abstract void onClassifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback); + method public abstract void onGenerateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback); + method public abstract void onSuggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options, android.os.CancellationSignal, android.service.textclassifier.TextClassifierService.Callback); + field public static final java.lang.String SERVICE_INTERFACE = "android.service.textclassifier.TextClassifierService"; + } + + public static abstract interface TextClassifierService.Callback { + method public abstract void onFailure(java.lang.CharSequence); + method public abstract void onSuccess(T); + } + +} + package android.service.trust { public class TrustAgentService extends android.app.Service { diff --git a/core/java/android/service/textclassifier/ITextClassificationCallback.aidl b/core/java/android/service/textclassifier/ITextClassificationCallback.aidl new file mode 100644 index 00000000000..10bfe632450 --- /dev/null +++ b/core/java/android/service/textclassifier/ITextClassificationCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.service.textclassifier; + +import android.view.textclassifier.TextClassification; + +/** + * Callback for a TextClassification request. + * @hide + */ +oneway interface ITextClassificationCallback { + void onSuccess(in TextClassification classification); + void onFailure(); +} diff --git a/core/java/android/service/textclassifier/ITextClassifierService.aidl b/core/java/android/service/textclassifier/ITextClassifierService.aidl new file mode 100644 index 00000000000..d2ffe345ae3 --- /dev/null +++ b/core/java/android/service/textclassifier/ITextClassifierService.aidl @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.service.textclassifier; + +import android.service.textclassifier.ITextClassificationCallback; +import android.service.textclassifier.ITextLinksCallback; +import android.service.textclassifier.ITextSelectionCallback; +import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextLinks; +import android.view.textclassifier.TextSelection; + +/** + * TextClassifierService binder interface. + * See TextClassifier for interface documentation. + * {@hide} + */ +oneway interface ITextClassifierService { + + void onSuggestSelection( + in CharSequence text, int selectionStartIndex, int selectionEndIndex, + in TextSelection.Options options, + in ITextSelectionCallback c); + + void onClassifyText( + in CharSequence text, int startIndex, int endIndex, + in TextClassification.Options options, + in ITextClassificationCallback c); + + void onGenerateLinks( + in CharSequence text, + in TextLinks.Options options, + in ITextLinksCallback c); +} diff --git a/core/java/android/service/textclassifier/ITextLinksCallback.aidl b/core/java/android/service/textclassifier/ITextLinksCallback.aidl new file mode 100644 index 00000000000..a9e0dde5677 --- /dev/null +++ b/core/java/android/service/textclassifier/ITextLinksCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.service.textclassifier; + +import android.view.textclassifier.TextLinks; + +/** + * Callback for a TextLinks request. + * @hide + */ +oneway interface ITextLinksCallback { + void onSuccess(in TextLinks links); + void onFailure(); +} \ No newline at end of file diff --git a/core/java/android/service/textclassifier/ITextSelectionCallback.aidl b/core/java/android/service/textclassifier/ITextSelectionCallback.aidl new file mode 100644 index 00000000000..1b4c4d10d42 --- /dev/null +++ b/core/java/android/service/textclassifier/ITextSelectionCallback.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.service.textclassifier; + +import android.view.textclassifier.TextSelection; + +/** + * Callback for a TextSelection request. + * @hide + */ +oneway interface ITextSelectionCallback { + void onSuccess(in TextSelection selection); + void onFailure(); +} \ No newline at end of file diff --git a/core/java/android/service/textclassifier/TextClassifierService.java b/core/java/android/service/textclassifier/TextClassifierService.java new file mode 100644 index 00000000000..6c8c8bc3612 --- /dev/null +++ b/core/java/android/service/textclassifier/TextClassifierService.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.service.textclassifier; + +import android.Manifest; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.app.Service; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.os.CancellationSignal; +import android.os.IBinder; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Slog; +import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextLinks; +import android.view.textclassifier.TextSelection; + +import com.android.internal.R; + +/** + * Abstract base class for the TextClassifier service. + * + *

A TextClassifier service provides text classification related features for the system. + * The system's default TextClassifierService is configured in + * {@code config_defaultTextClassifierService}. If this config has no value, a + * {@link android.view.textclassifier.TextClassifierImpl} is loaded in the calling app's process. + * + *

See: {@link TextClassifier}. + * See: {@link android.view.textclassifier.TextClassificationManager}. + * + *

Include the following in the manifest: + * + *

+ * {@literal
+ * 
+ *     
+ *         
+ *     
+ * }
+ * + * @see TextClassifier + * @hide + */ +@SystemApi +public abstract class TextClassifierService extends Service { + + private static final String LOG_TAG = "TextClassifierService"; + + /** + * The {@link Intent} that must be declared as handled by the service. + * To be supported, the service must also require the + * {@link android.Manifest.permission#BIND_TEXTCLASSIFIER_SERVICE} permission so + * that other applications can not abuse it. + */ + @SystemApi + public static final String SERVICE_INTERFACE = + "android.service.textclassifier.TextClassifierService"; + + private final ITextClassifierService.Stub mBinder = new ITextClassifierService.Stub() { + + // TODO(b/72533911): Implement cancellation signal + @NonNull private final CancellationSignal mCancellationSignal = new CancellationSignal(); + + /** {@inheritDoc} */ + @Override + public void onSuggestSelection( + CharSequence text, int selectionStartIndex, int selectionEndIndex, + TextSelection.Options options, ITextSelectionCallback callback) + throws RemoteException { + TextClassifierService.this.onSuggestSelection( + text, selectionStartIndex, selectionEndIndex, options, mCancellationSignal, + new Callback() { + @Override + public void onSuccess(TextSelection result) { + try { + callback.onSuccess(result); + } catch (RemoteException e) { + Slog.d(LOG_TAG, "Error calling callback"); + } + } + + @Override + public void onFailure(CharSequence error) { + try { + if (callback.asBinder().isBinderAlive()) { + callback.onFailure(); + } + } catch (RemoteException e) { + Slog.d(LOG_TAG, "Error calling callback"); + } + } + }); + } + + /** {@inheritDoc} */ + @Override + public void onClassifyText( + CharSequence text, int startIndex, int endIndex, + TextClassification.Options options, ITextClassificationCallback callback) + throws RemoteException { + TextClassifierService.this.onClassifyText( + text, startIndex, endIndex, options, mCancellationSignal, + new Callback() { + @Override + public void onSuccess(TextClassification result) { + try { + callback.onSuccess(result); + } catch (RemoteException e) { + Slog.d(LOG_TAG, "Error calling callback"); + } + } + + @Override + public void onFailure(CharSequence error) { + try { + callback.onFailure(); + } catch (RemoteException e) { + Slog.d(LOG_TAG, "Error calling callback"); + } + } + }); + } + + /** {@inheritDoc} */ + @Override + public void onGenerateLinks( + CharSequence text, TextLinks.Options options, ITextLinksCallback callback) + throws RemoteException { + TextClassifierService.this.onGenerateLinks( + text, options, mCancellationSignal, + new Callback() { + @Override + public void onSuccess(TextLinks result) { + try { + callback.onSuccess(result); + } catch (RemoteException e) { + Slog.d(LOG_TAG, "Error calling callback"); + } + } + + @Override + public void onFailure(CharSequence error) { + try { + callback.onFailure(); + } catch (RemoteException e) { + Slog.d(LOG_TAG, "Error calling callback"); + } + } + }); + } + }; + + @Nullable + @Override + public final IBinder onBind(Intent intent) { + if (SERVICE_INTERFACE.equals(intent.getAction())) { + return mBinder; + } + return null; + } + + /** + * Returns suggested text selection start and end indices, recognized entity types, and their + * associated confidence scores. The entity types are ordered from highest to lowest scoring. + * + * @param text text providing context for the selected text (which is specified + * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) + * @param selectionStartIndex start index of the selected part of text + * @param selectionEndIndex end index of the selected part of text + * @param options optional input parameters + * @param cancellationSignal object to watch for canceling the current operation + * @param callback the callback to return the result to + */ + public abstract void onSuggestSelection( + @NonNull CharSequence text, + @IntRange(from = 0) int selectionStartIndex, + @IntRange(from = 0) int selectionEndIndex, + @Nullable TextSelection.Options options, + @NonNull CancellationSignal cancellationSignal, + @NonNull Callback callback); + + /** + * Classifies the specified text and returns a {@link TextClassification} object that can be + * used to generate a widget for handling the classified text. + * + * @param text text providing context for the text to classify (which is specified + * by the sub sequence starting at startIndex and ending at endIndex) + * @param startIndex start index of the text to classify + * @param endIndex end index of the text to classify + * @param options optional input parameters + * @param cancellationSignal object to watch for canceling the current operation + * @param callback the callback to return the result to + */ + public abstract void onClassifyText( + @NonNull CharSequence text, + @IntRange(from = 0) int startIndex, + @IntRange(from = 0) int endIndex, + @Nullable TextClassification.Options options, + @NonNull CancellationSignal cancellationSignal, + @NonNull Callback callback); + + /** + * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with + * links information. + * + * @param text the text to generate annotations for + * @param options configuration for link generation + * @param cancellationSignal object to watch for canceling the current operation + * @param callback the callback to return the result to + */ + public abstract void onGenerateLinks( + @NonNull CharSequence text, + @Nullable TextLinks.Options options, + @NonNull CancellationSignal cancellationSignal, + @NonNull Callback callback); + + /** + * Callbacks for TextClassifierService results. + * + * @param the type of the result + * @hide + */ + @SystemApi + public interface Callback { + /** + * Returns the result. + */ + void onSuccess(T result); + + /** + * Signals a failure. + */ + void onFailure(CharSequence error); + } + + /** + * Returns the component name of the system default textclassifier service if it can be found + * on the system. Otherwise, returns null. + * @hide + */ + @Nullable + public static ComponentName getServiceComponentName(Context context) { + final String str = context.getString(R.string.config_defaultTextClassifierService); + if (!TextUtils.isEmpty(str)) { + try { + final ComponentName componentName = ComponentName.unflattenFromString(str); + final Intent intent = new Intent(SERVICE_INTERFACE).setComponent(componentName); + final ServiceInfo si = context.getPackageManager() + .getServiceInfo(intent.getComponent(), 0); + final String permission = si == null ? null : si.permission; + if (Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE.equals(permission)) { + return componentName; + } + Slog.w(LOG_TAG, String.format( + "Service %s should require %s permission. Found %s permission", + intent.getComponent().flattenToString(), + Manifest.permission.BIND_TEXTCLASSIFIER_SERVICE, + si.permission)); + } catch (PackageManager.NameNotFoundException e) { + Slog.w(LOG_TAG, String.format("Service %s not found", str)); + } + } else { + Slog.d(LOG_TAG, "No configured system TextClassifierService"); + } + return null; + } +} diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java new file mode 100644 index 00000000000..af55dcd0ed7 --- /dev/null +++ b/core/java/android/view/textclassifier/SystemTextClassifier.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.content.Context; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.service.textclassifier.ITextClassificationCallback; +import android.service.textclassifier.ITextClassifierService; +import android.service.textclassifier.ITextLinksCallback; +import android.service.textclassifier.ITextSelectionCallback; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Proxy to the system's default TextClassifier. + */ +final class SystemTextClassifier implements TextClassifier { + + private static final String LOG_TAG = "SystemTextClassifier"; + + private final ITextClassifierService mManagerService; + private final TextClassifier mFallback; + + SystemTextClassifier(Context context) throws ServiceManager.ServiceNotFoundException { + mManagerService = ITextClassifierService.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE)); + mFallback = new TextClassifierImpl(context); + } + + /** + * @inheritDoc + */ + @WorkerThread + public TextSelection suggestSelection( + @NonNull CharSequence text, + @IntRange(from = 0) int selectionStartIndex, + @IntRange(from = 0) int selectionEndIndex, + @Nullable TextSelection.Options options) { + Utils.validate(text, selectionStartIndex, selectionEndIndex, false /* allowInMainThread */); + try { + final TextSelectionCallback callback = new TextSelectionCallback(); + mManagerService.onSuggestSelection( + text, selectionStartIndex, selectionEndIndex, options, callback); + final TextSelection selection = callback.mReceiver.get(); + if (selection != null) { + return selection; + } + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } catch (InterruptedException e) { + Log.d(LOG_TAG, e.getMessage()); + } + return mFallback.suggestSelection(text, selectionStartIndex, selectionEndIndex, options); + } + + /** + * @inheritDoc + */ + @WorkerThread + public TextClassification classifyText( + @NonNull CharSequence text, + @IntRange(from = 0) int startIndex, + @IntRange(from = 0) int endIndex, + @Nullable TextClassification.Options options) { + Utils.validate(text, startIndex, endIndex, false /* allowInMainThread */); + try { + final TextClassificationCallback callback = new TextClassificationCallback(); + mManagerService.onClassifyText(text, startIndex, endIndex, options, callback); + final TextClassification classification = callback.mReceiver.get(); + if (classification != null) { + return classification; + } + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } catch (InterruptedException e) { + Log.d(LOG_TAG, e.getMessage()); + } + return mFallback.classifyText(text, startIndex, endIndex, options); + } + + /** + * @inheritDoc + */ + @WorkerThread + public TextLinks generateLinks( + @NonNull CharSequence text, @Nullable TextLinks.Options options) { + Utils.validate(text, false /* allowInMainThread */); + try { + final TextLinksCallback callback = new TextLinksCallback(); + mManagerService.onGenerateLinks(text, options, callback); + final TextLinks links = callback.mReceiver.get(); + if (links != null) { + return links; + } + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } catch (InterruptedException e) { + Log.d(LOG_TAG, e.getMessage()); + } + return mFallback.generateLinks(text, options); + } + + private static final class TextSelectionCallback extends ITextSelectionCallback.Stub { + + final ResponseReceiver mReceiver = new ResponseReceiver<>(); + + @Override + public void onSuccess(TextSelection selection) { + mReceiver.onSuccess(selection); + } + + @Override + public void onFailure() { + mReceiver.onFailure(); + } + } + + private static final class TextClassificationCallback extends ITextClassificationCallback.Stub { + + final ResponseReceiver mReceiver = new ResponseReceiver<>(); + + @Override + public void onSuccess(TextClassification classification) { + mReceiver.onSuccess(classification); + } + + @Override + public void onFailure() { + mReceiver.onFailure(); + } + } + + private static final class TextLinksCallback extends ITextLinksCallback.Stub { + + final ResponseReceiver mReceiver = new ResponseReceiver<>(); + + @Override + public void onSuccess(TextLinks links) { + mReceiver.onSuccess(links); + } + + @Override + public void onFailure() { + mReceiver.onFailure(); + } + } + + private static final class ResponseReceiver { + + private final CountDownLatch mLatch = new CountDownLatch(1); + + private T mResponse; + + public void onSuccess(T response) { + mResponse = response; + mLatch.countDown(); + } + + public void onFailure() { + Log.e(LOG_TAG, "Request failed.", null); + mLatch.countDown(); + } + + @Nullable + public T get() throws InterruptedException { + // If this is running on the main thread, do not block for a response. + // The response will unfortunately be null and the TextClassifier should depend on its + // fallback. + // NOTE that TextClassifier calls should preferably always be called on a worker thread. + if (Looper.myLooper() != Looper.getMainLooper()) { + mLatch.await(2, TimeUnit.SECONDS); + } + return mResponse; + } + } +} diff --git a/core/java/android/view/textclassifier/TextClassification.aidl b/core/java/android/view/textclassifier/TextClassification.aidl new file mode 100644 index 00000000000..9fefe5d4176 --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassification.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +parcelable TextClassification; +parcelable TextClassification.Options; \ No newline at end of file diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java index 54e93d5afd8..a6a2a945e77 100644 --- a/core/java/android/view/textclassifier/TextClassification.java +++ b/core/java/android/view/textclassifier/TextClassification.java @@ -97,7 +97,7 @@ import java.util.Map; * }); * } */ -public final class TextClassification { +public final class TextClassification implements Parcelable { /** * @hide @@ -310,42 +310,6 @@ public final class TextClassification { mSignature); } - /** Helper for parceling via #ParcelableWrapper. */ - private void writeToParcel(Parcel dest, int flags) { - dest.writeString(mText); - final Bitmap primaryIconBitmap = drawableToBitmap(mPrimaryIcon, MAX_PRIMARY_ICON_SIZE); - dest.writeInt(primaryIconBitmap != null ? 1 : 0); - if (primaryIconBitmap != null) { - primaryIconBitmap.writeToParcel(dest, flags); - } - dest.writeString(mPrimaryLabel); - dest.writeInt(mPrimaryIntent != null ? 1 : 0); - if (mPrimaryIntent != null) { - mPrimaryIntent.writeToParcel(dest, flags); - } - // mPrimaryOnClickListener is not parcelable. - dest.writeTypedList(drawablesToBitmaps(mSecondaryIcons, MAX_SECONDARY_ICON_SIZE)); - dest.writeStringList(mSecondaryLabels); - dest.writeTypedList(mSecondaryIntents); - mEntityConfidence.writeToParcel(dest, flags); - dest.writeString(mSignature); - } - - /** Helper for unparceling via #ParcelableWrapper. */ - private TextClassification(Parcel in) { - mText = in.readString(); - mPrimaryIcon = in.readInt() == 0 - ? null : new BitmapDrawable(null, Bitmap.CREATOR.createFromParcel(in)); - mPrimaryLabel = in.readString(); - mPrimaryIntent = in.readInt() == 0 ? null : Intent.CREATOR.createFromParcel(in); - mPrimaryOnClickListener = null; // not parcelable - mSecondaryIcons = bitmapsToDrawables(in.createTypedArrayList(Bitmap.CREATOR)); - mSecondaryLabels = in.createStringArrayList(); - mSecondaryIntents = in.createTypedArrayList(Intent.CREATOR); - mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); - mSignature = in.readString(); - } - /** * Creates an OnClickListener that starts an activity with the specified intent. * @@ -675,46 +639,56 @@ public final class TextClassification { } } - /** - * Parcelable wrapper for TextClassification objects. - * @hide - */ - public static final class ParcelableWrapper implements Parcelable { - - @NonNull private TextClassification mTextClassification; - - public ParcelableWrapper(@NonNull TextClassification textClassification) { - Preconditions.checkNotNull(textClassification); - mTextClassification = textClassification; - } - - @NonNull - public TextClassification getTextClassification() { - return mTextClassification; - } + @Override + public int describeContents() { + return 0; + } - @Override - public int describeContents() { - return 0; + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mText); + final Bitmap primaryIconBitmap = drawableToBitmap(mPrimaryIcon, MAX_PRIMARY_ICON_SIZE); + dest.writeInt(primaryIconBitmap != null ? 1 : 0); + if (primaryIconBitmap != null) { + primaryIconBitmap.writeToParcel(dest, flags); } - - @Override - public void writeToParcel(Parcel dest, int flags) { - mTextClassification.writeToParcel(dest, flags); + dest.writeString(mPrimaryLabel); + dest.writeInt(mPrimaryIntent != null ? 1 : 0); + if (mPrimaryIntent != null) { + mPrimaryIntent.writeToParcel(dest, flags); } + // mPrimaryOnClickListener is not parcelable. + dest.writeTypedList(drawablesToBitmaps(mSecondaryIcons, MAX_SECONDARY_ICON_SIZE)); + dest.writeStringList(mSecondaryLabels); + dest.writeTypedList(mSecondaryIntents); + mEntityConfidence.writeToParcel(dest, flags); + dest.writeString(mSignature); + } - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public ParcelableWrapper createFromParcel(Parcel in) { - return new ParcelableWrapper(new TextClassification(in)); - } + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public TextClassification createFromParcel(Parcel in) { + return new TextClassification(in); + } - @Override - public ParcelableWrapper[] newArray(int size) { - return new ParcelableWrapper[size]; - } - }; + @Override + public TextClassification[] newArray(int size) { + return new TextClassification[size]; + } + }; + private TextClassification(Parcel in) { + mText = in.readString(); + mPrimaryIcon = in.readInt() == 0 + ? null : new BitmapDrawable(null, Bitmap.CREATOR.createFromParcel(in)); + mPrimaryLabel = in.readString(); + mPrimaryIntent = in.readInt() == 0 ? null : Intent.CREATOR.createFromParcel(in); + mPrimaryOnClickListener = null; // not parcelable + mSecondaryIcons = bitmapsToDrawables(in.createTypedArrayList(Bitmap.CREATOR)); + mSecondaryLabels = in.createStringArrayList(); + mSecondaryIntents = in.createTypedArrayList(Intent.CREATOR); + mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); + mSignature = in.readString(); } } diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java index d7b07761a65..300aef2d172 100644 --- a/core/java/android/view/textclassifier/TextClassificationManager.java +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -19,6 +19,8 @@ package android.view.textclassifier; import android.annotation.Nullable; import android.annotation.SystemService; import android.content.Context; +import android.os.ServiceManager; +import android.service.textclassifier.TextClassifierService; import com.android.internal.util.Preconditions; @@ -28,23 +30,56 @@ import com.android.internal.util.Preconditions; @SystemService(Context.TEXT_CLASSIFICATION_SERVICE) public final class TextClassificationManager { - private final Object mTextClassifierLock = new Object(); + // TODO: Make this a configurable flag. + private static final boolean SYSTEM_TEXT_CLASSIFIER_ENABLED = true; + + private static final String LOG_TAG = "TextClassificationManager"; + + private final Object mLock = new Object(); private final Context mContext; private TextClassifier mTextClassifier; + private TextClassifier mSystemTextClassifier; /** @hide */ public TextClassificationManager(Context context) { mContext = Preconditions.checkNotNull(context); } + /** + * Returns the system's default TextClassifier. + * @hide + */ + // TODO: Unhide when this is ready. + public TextClassifier getSystemDefaultTextClassifier() { + synchronized (mLock) { + if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) { + try { + Log.d(LOG_TAG, "Initialized SystemTextClassifier"); + mSystemTextClassifier = new SystemTextClassifier(mContext); + } catch (ServiceManager.ServiceNotFoundException e) { + Log.e(LOG_TAG, "Could not initialize SystemTextClassifier", e); + } + } + if (mSystemTextClassifier == null) { + Log.d(LOG_TAG, "Using an in-process TextClassifier as the system default"); + mSystemTextClassifier = new TextClassifierImpl(mContext); + } + } + return mSystemTextClassifier; + } + /** * Returns the text classifier. */ public TextClassifier getTextClassifier() { - synchronized (mTextClassifierLock) { + synchronized (mLock) { if (mTextClassifier == null) { - mTextClassifier = new TextClassifierImpl(mContext); + if (isSystemTextClassifierEnabled()) { + mTextClassifier = getSystemDefaultTextClassifier(); + } else { + mTextClassifier = new TextClassifierImpl(mContext); + } } return mTextClassifier; } @@ -56,8 +91,13 @@ public final class TextClassificationManager { * Set to {@link TextClassifier#NO_OP} to disable text classifier features. */ public void setTextClassifier(@Nullable TextClassifier textClassifier) { - synchronized (mTextClassifierLock) { + synchronized (mLock) { mTextClassifier = textClassifier; } } + + private boolean isSystemTextClassifierEnabled() { + return SYSTEM_TEXT_CLASSIFIER_ENABLED + && TextClassifierService.getServiceComponentName(mContext) != null; + } } diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index 04ab4474a40..5dd9ac62fbb 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -23,9 +23,11 @@ import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.WorkerThread; import android.os.LocaleList; +import android.os.Looper; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; +import android.util.Slog; import com.android.internal.util.Preconditions; @@ -39,8 +41,8 @@ import java.util.List; /** * Interface for providing text classification related features. * - *

Unless otherwise stated, methods of this interface are blocking operations. - * Avoid calling them on the UI thread. + *

NOTE: Unless otherwise stated, methods of this interface are blocking + * operations. Call on a worker thread. */ public interface TextClassifier { @@ -107,6 +109,8 @@ public interface TextClassifier { * Returns suggested text selection start and end indices, recognized entity types, and their * associated confidence scores. The entity types are ordered from highest to lowest scoring. * + *

NOTE: Call on a worker thread. + * * @param text text providing context for the selected text (which is specified * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) * @param selectionStartIndex start index of the selected part of text @@ -125,7 +129,7 @@ public interface TextClassifier { @IntRange(from = 0) int selectionStartIndex, @IntRange(from = 0) int selectionEndIndex, @Nullable TextSelection.Options options) { - Utils.validateInput(text, selectionStartIndex, selectionEndIndex); + Utils.validate(text, selectionStartIndex, selectionEndIndex, false); return new TextSelection.Builder(selectionStartIndex, selectionEndIndex).build(); } @@ -137,6 +141,8 @@ public interface TextClassifier { * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method * calls this method, a stack overflow error will happen. * + *

NOTE: Call on a worker thread. + * * @param text text providing context for the selected text (which is specified * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) * @param selectionStartIndex start index of the selected part of text @@ -161,6 +167,8 @@ public interface TextClassifier { * See {@link #suggestSelection(CharSequence, int, int)} or * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. * + *

NOTE: Call on a worker thread. + * *

NOTE: Do not implement. The default implementation of this method calls * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. If that method * calls this method, a stack overflow error will happen. @@ -182,6 +190,8 @@ public interface TextClassifier { * Classifies the specified text and returns a {@link TextClassification} object that can be * used to generate a widget for handling the classified text. * + *

NOTE: Call on a worker thread. + * * @param text text providing context for the text to classify (which is specified * by the sub sequence starting at startIndex and ending at endIndex) * @param startIndex start index of the text to classify @@ -200,7 +210,7 @@ public interface TextClassifier { @IntRange(from = 0) int startIndex, @IntRange(from = 0) int endIndex, @Nullable TextClassification.Options options) { - Utils.validateInput(text, startIndex, endIndex); + Utils.validate(text, startIndex, endIndex, false); return TextClassification.EMPTY; } @@ -208,6 +218,8 @@ public interface TextClassifier { * Classifies the specified text and returns a {@link TextClassification} object that can be * used to generate a widget for handling the classified text. * + *

NOTE: Call on a worker thread. + * *

NOTE: Do not implement. The default implementation of this method calls * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method * calls this method, a stack overflow error will happen. @@ -235,6 +247,8 @@ public interface TextClassifier { * See {@link #classifyText(CharSequence, int, int, TextClassification.Options)} or * {@link #classifyText(CharSequence, int, int)}. * + *

NOTE: Call on a worker thread. + * *

NOTE: Do not implement. The default implementation of this method calls * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. If that method * calls this method, a stack overflow error will happen. @@ -253,10 +267,10 @@ public interface TextClassifier { } /** - * Returns a {@link TextLinks} that may be applied to the text to annotate it with links - * information. + * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with + * links information. * - * If no options are supplied, default values will be used, determined by the TextClassifier. + *

NOTE: Call on a worker thread. * * @param text the text to generate annotations for * @param options configuration for link generation @@ -268,13 +282,15 @@ public interface TextClassifier { @WorkerThread default TextLinks generateLinks( @NonNull CharSequence text, @Nullable TextLinks.Options options) { - Utils.validateInput(text); + Utils.validate(text, false); return new TextLinks.Builder(text.toString()).build(); } /** - * Returns a {@link TextLinks} that may be applied to the text to annotate it with links - * information. + * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with + * links information. + * + *

NOTE: Call on a worker thread. * *

NOTE: Do not implement. The default implementation of this method calls * {@link #generateLinks(CharSequence, TextLinks.Options)}. If that method calls this method, @@ -296,6 +312,7 @@ public interface TextClassifier { * * @see #ENTITY_PRESET_ALL * @see #ENTITY_PRESET_NONE + * @see #ENTITY_PRESET_BASE */ default Collection getEntitiesForPreset(@EntityPreset int entityPreset) { return Collections.EMPTY_LIST; @@ -424,19 +441,28 @@ public interface TextClassifier { * endIndex is greater than text.length() or is not greater than startIndex; * options is null */ - static void validateInput( - @NonNull CharSequence text, int startIndex, int endIndex) { + public static void validate( + @NonNull CharSequence text, int startIndex, int endIndex, + boolean allowInMainThread) { Preconditions.checkArgument(text != null); Preconditions.checkArgument(startIndex >= 0); Preconditions.checkArgument(endIndex <= text.length()); Preconditions.checkArgument(endIndex > startIndex); + checkMainThread(allowInMainThread); } /** * @throws IllegalArgumentException if text is null or options is null */ - static void validateInput(@NonNull CharSequence text) { + public static void validate(@NonNull CharSequence text, boolean allowInMainThread) { Preconditions.checkArgument(text != null); + checkMainThread(allowInMainThread); + } + + private static void checkMainThread(boolean allowInMainThread) { + if (!allowInMainThread && Looper.myLooper() == Looper.getMainLooper()) { + Slog.w(DEFAULT_LOG_TAG, "TextClassifier called on main thread"); + } } } } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 6a44fb38ee1..8f285bdf42d 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -66,7 +66,7 @@ import java.util.regex.Pattern; * * @hide */ -final class TextClassifierImpl implements TextClassifier { +public final class TextClassifierImpl implements TextClassifier { private static final String LOG_TAG = DEFAULT_LOG_TAG; private static final String MODEL_DIR = "/etc/textclassifier/"; @@ -90,30 +90,33 @@ final class TextClassifierImpl implements TextClassifier { TextClassifier.TYPE_URL)); private final Context mContext; + private final TextClassifier mFallback; private final MetricsLogger mMetricsLogger = new MetricsLogger(); - private final Object mSmartSelectionLock = new Object(); - @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. + private final Object mLock = new Object(); + @GuardedBy("mLock") // Do not access outside this lock. private Map mModelFilePaths; - @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. + @GuardedBy("mLock") // Do not access outside this lock. private Locale mLocale; - @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. + @GuardedBy("mLock") // Do not access outside this lock. private int mVersion; - @GuardedBy("mSmartSelectionLock") // Do not access outside this lock. + @GuardedBy("mLock") // Do not access outside this lock. private SmartSelection mSmartSelection; private TextClassifierConstants mSettings; - TextClassifierImpl(Context context) { + public TextClassifierImpl(Context context) { mContext = Preconditions.checkNotNull(context); + mFallback = TextClassifier.NO_OP; } + /** @inheritDoc */ @Override public TextSelection suggestSelection( @NonNull CharSequence text, int selectionStartIndex, int selectionEndIndex, @Nullable TextSelection.Options options) { - Utils.validateInput(text, selectionStartIndex, selectionEndIndex); + Utils.validate(text, selectionStartIndex, selectionEndIndex, false /* allowInMainThread */); try { if (text.length() > 0) { final LocaleList locales = (options == null) ? null : options.getDefaultLocales(); @@ -159,15 +162,16 @@ final class TextClassifierImpl implements TextClassifier { t); } // Getting here means something went wrong, return a NO_OP result. - return TextClassifier.NO_OP.suggestSelection( + return mFallback.suggestSelection( text, selectionStartIndex, selectionEndIndex, options); } + /** @inheritDoc */ @Override public TextClassification classifyText( @NonNull CharSequence text, int startIndex, int endIndex, @Nullable TextClassification.Options options) { - Utils.validateInput(text, startIndex, endIndex); + Utils.validate(text, startIndex, endIndex, false /* allowInMainThread */); try { if (text.length() > 0) { final String string = text.toString(); @@ -186,13 +190,14 @@ final class TextClassifierImpl implements TextClassifier { Log.e(LOG_TAG, "Error getting text classification info.", t); } // Getting here means something went wrong, return a NO_OP result. - return TextClassifier.NO_OP.classifyText(text, startIndex, endIndex, options); + return mFallback.classifyText(text, startIndex, endIndex, options); } + /** @inheritDoc */ @Override public TextLinks generateLinks( @NonNull CharSequence text, @Nullable TextLinks.Options options) { - Utils.validateInput(text); + Utils.validate(text, false /* allowInMainThread */); final String textString = text.toString(); final TextLinks.Builder builder = new TextLinks.Builder(textString); @@ -223,7 +228,7 @@ final class TextClassifierImpl implements TextClassifier { // Avoid throwing from this method. Log the error. Log.e(LOG_TAG, "Error getting links info.", t); } - return builder.build(); + return mFallback.generateLinks(text, options); } @Override @@ -240,6 +245,7 @@ final class TextClassifierImpl implements TextClassifier { } } + /** @hide */ @Override public void logEvent(String source, String event) { if (LOG_TAG.equals(source)) { @@ -247,6 +253,7 @@ final class TextClassifierImpl implements TextClassifier { } } + /** @hide */ @Override public TextClassifierConstants getSettings() { if (mSettings == null) { @@ -257,7 +264,7 @@ final class TextClassifierImpl implements TextClassifier { } private SmartSelection getSmartSelection(LocaleList localeList) throws FileNotFoundException { - synchronized (mSmartSelectionLock) { + synchronized (mLock) { localeList = localeList == null ? LocaleList.getEmptyLocaleList() : localeList; final Locale locale = findBestSupportedLocaleLocked(localeList); if (locale == null) { @@ -277,7 +284,7 @@ final class TextClassifierImpl implements TextClassifier { } private String getSignature(String text, int start, int end) { - synchronized (mSmartSelectionLock) { + synchronized (mLock) { final String versionInfo = (mLocale != null) ? String.format(Locale.US, "%s_v%d", mLocale.toLanguageTag(), mVersion) : ""; @@ -286,7 +293,7 @@ final class TextClassifierImpl implements TextClassifier { } } - @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. + @GuardedBy("mLock") // Do not call outside this lock. private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException { ParcelFileDescriptor updateFd; int updateVersion = -1; @@ -353,7 +360,7 @@ final class TextClassifierImpl implements TextClassifier { } } - @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. + @GuardedBy("mLock") // Do not call outside this lock. private void destroySmartSelectionIfExistsLocked() { if (mSmartSelection != null) { mSmartSelection.close(); @@ -361,7 +368,7 @@ final class TextClassifierImpl implements TextClassifier { } } - @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. + @GuardedBy("mLock") // Do not call outside this lock. @Nullable private Locale findBestSupportedLocaleLocked(LocaleList localeList) { // Specified localeList takes priority over the system default, so it is listed first. @@ -379,7 +386,7 @@ final class TextClassifierImpl implements TextClassifier { return Locale.lookup(languageRangeList, supportedLocales); } - @GuardedBy("mSmartSelectionLock") // Do not call outside this lock. + @GuardedBy("mLock") // Do not call outside this lock. private Map getFactoryModelFilePathsLocked() { if (mModelFilePaths == null) { final Map modelFilePaths = new HashMap<>(); diff --git a/core/java/android/view/textclassifier/TextLinks.aidl b/core/java/android/view/textclassifier/TextLinks.aidl new file mode 100644 index 00000000000..1bbb7984552 --- /dev/null +++ b/core/java/android/view/textclassifier/TextLinks.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +parcelable TextLinks; +parcelable TextLinks.Options; \ No newline at end of file diff --git a/core/java/android/view/textclassifier/TextSelection.aidl b/core/java/android/view/textclassifier/TextSelection.aidl new file mode 100644 index 00000000000..dab1aefa353 --- /dev/null +++ b/core/java/android/view/textclassifier/TextSelection.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +parcelable TextSelection; +parcelable TextSelection.Options; \ No newline at end of file diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java index 774d42db67a..1c93be75a3a 100644 --- a/core/java/android/view/textclassifier/TextSelection.java +++ b/core/java/android/view/textclassifier/TextSelection.java @@ -34,7 +34,7 @@ import java.util.Map; /** * Information about where text selection should be. */ -public final class TextSelection { +public final class TextSelection implements Parcelable { private final int mStartIndex; private final int mEndIndex; @@ -112,22 +112,6 @@ public final class TextSelection { mStartIndex, mEndIndex, mEntityConfidence, mSignature); } - /** Helper for parceling via #ParcelableWrapper. */ - private void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mStartIndex); - dest.writeInt(mEndIndex); - mEntityConfidence.writeToParcel(dest, flags); - dest.writeString(mSignature); - } - - /** Helper for unparceling via #ParcelableWrapper. */ - private TextSelection(Parcel in) { - mStartIndex = in.readInt(); - mEndIndex = in.readInt(); - mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); - mSignature = in.readString(); - } - /** * Builder used to build {@link TextSelection} objects. */ @@ -272,46 +256,36 @@ public final class TextSelection { } } - /** - * Parcelable wrapper for TextSelection objects. - * @hide - */ - public static final class ParcelableWrapper implements Parcelable { - - @NonNull private TextSelection mTextSelection; - - public ParcelableWrapper(@NonNull TextSelection textSelection) { - Preconditions.checkNotNull(textSelection); - mTextSelection = textSelection; - } - - @NonNull - public TextSelection getTextSelection() { - return mTextSelection; - } - - @Override - public int describeContents() { - return 0; - } + @Override + public int describeContents() { + return 0; + } - @Override - public void writeToParcel(Parcel dest, int flags) { - mTextSelection.writeToParcel(dest, flags); - } + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mStartIndex); + dest.writeInt(mEndIndex); + mEntityConfidence.writeToParcel(dest, flags); + dest.writeString(mSignature); + } - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public ParcelableWrapper createFromParcel(Parcel in) { - return new ParcelableWrapper(new TextSelection(in)); - } + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public TextSelection createFromParcel(Parcel in) { + return new TextSelection(in); + } - @Override - public ParcelableWrapper[] newArray(int size) { - return new ParcelableWrapper[size]; - } - }; + @Override + public TextSelection[] newArray(int size) { + return new TextSelection[size]; + } + }; + private TextSelection(Parcel in) { + mStartIndex = in.readInt(); + mEndIndex = in.readInt(); + mEntityConfidence = EntityConfidence.CREATOR.createFromParcel(in); + mSignature = in.readString(); } } diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 3bfa520cd94..3f5584e6d3f 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -65,6 +65,7 @@ public final class SelectionActionModeHelper { private static final String LOG_TAG = "SelectActionModeHelper"; + // TODO: Make this a configurable flag. private static final boolean SMART_SELECT_ANIMATION_ENABLED = true; private final Editor mEditor; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2779cd6846d..a2581fdd303 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2730,6 +2730,14 @@ + + + diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 38f890a1e95..0215e2156e0 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3126,6 +3126,15 @@ --> + + + true diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e8ab0be78b3..ab2bad505bd 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3102,6 +3102,7 @@ + diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java index 8a81743c815..cf41eb89ba2 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java @@ -86,15 +86,11 @@ public class TextClassificationTest { .setSignature(signature) .build(); - // Parcel and unparcel using ParcelableWrapper. - final TextClassification.ParcelableWrapper parcelableReference = new TextClassification - .ParcelableWrapper(reference); + // Parcel and unparcel final Parcel parcel = Parcel.obtain(); - parcelableReference.writeToParcel(parcel, parcelableReference.describeContents()); + reference.writeToParcel(parcel, reference.describeContents()); parcel.setDataPosition(0); - final TextClassification result = - TextClassification.ParcelableWrapper.CREATOR.createFromParcel( - parcel).getTextClassification(); + final TextClassification result = TextClassification.CREATOR.createFromParcel(parcel); assertEquals(text, result.getText()); assertEquals(signature, result.getSignature()); diff --git a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java index e9202361c84..a6ea0211fbc 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextSelectionTest.java @@ -45,15 +45,11 @@ public class TextSelectionTest { .setSignature(signature) .build(); - // Parcel and unparcel using ParcelableWrapper. - final TextSelection.ParcelableWrapper parcelableReference = new TextSelection - .ParcelableWrapper(reference); + // Parcel and unparcel final Parcel parcel = Parcel.obtain(); - parcelableReference.writeToParcel(parcel, parcelableReference.describeContents()); + reference.writeToParcel(parcel, reference.describeContents()); parcel.setDataPosition(0); - final TextSelection result = - TextSelection.ParcelableWrapper.CREATOR.createFromParcel( - parcel).getTextSelection(); + final TextSelection result = TextSelection.CREATOR.createFromParcel(parcel); assertEquals(startIndex, result.getSelectionStartIndex()); assertEquals(endIndex, result.getSelectionEndIndex()); diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java new file mode 100644 index 00000000000..853c7eb51b8 --- /dev/null +++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.textclassifier; + +import android.annotation.NonNull; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; +import android.service.textclassifier.ITextClassifierService; +import android.service.textclassifier.ITextClassificationCallback; +import android.service.textclassifier.ITextLinksCallback; +import android.service.textclassifier.ITextSelectionCallback; +import android.service.textclassifier.TextClassifierService; +import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextLinks; +import android.view.textclassifier.TextSelection; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; +import com.android.server.SystemService; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +/** + * A manager for TextClassifier services. + * Apps bind to the TextClassificationManagerService for text classification. This service + * reroutes calls to it to a {@link TextClassifierService} that it manages. + */ +public final class TextClassificationManagerService extends ITextClassifierService.Stub { + + private static final String LOG_TAG = "TextClassificationManagerService"; + + // How long after the last interaction with the service we would unbind + private static final long TIMEOUT_IDLE_BIND_MILLIS = TimeUnit.MINUTES.toMillis(1); + + public static final class Lifecycle extends SystemService { + + private final TextClassificationManagerService mManagerService; + + public Lifecycle(Context context) { + super(context); + mManagerService = new TextClassificationManagerService(context); + } + + @Override + public void onStart() { + try { + publishBinderService(Context.TEXT_CLASSIFICATION_SERVICE, mManagerService); + } catch (Throwable t) { + // Starting this service is not critical to the running of this device and should + // therefore not crash the device. If it fails, log the error and continue. + Slog.e(LOG_TAG, "Could not start the TextClassificationManagerService.", t); + } + } + } + + private final Context mContext; + private final Handler mHandler; + private final Intent mServiceIntent; + private final ServiceConnection mConnection; + private final Runnable mUnbind; + private final Object mLock; + @GuardedBy("mLock") + private final Queue mPendingRequests; + + @GuardedBy("mLock") + private ITextClassifierService mService; + @GuardedBy("mLock") + private boolean mBinding; + + private TextClassificationManagerService(Context context) { + mContext = Preconditions.checkNotNull(context); + mHandler = new Handler(); + mServiceIntent = new Intent(TextClassifierService.SERVICE_INTERFACE) + .setComponent(TextClassifierService.getServiceComponentName(mContext)); + mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + mService = ITextClassifierService.Stub.asInterface(service); + setBindingLocked(false); + handlePendingRequestsLocked(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + cleanupService(); + } + + @Override + public void onBindingDied(ComponentName name) { + cleanupService(); + } + + @Override + public void onNullBinding(ComponentName name) { + cleanupService(); + } + + private void cleanupService() { + synchronized (mLock) { + mService = null; + setBindingLocked(false); + handlePendingRequestsLocked(); + } + } + }; + mPendingRequests = new LinkedList<>(); + mUnbind = this::unbind; + mLock = new Object(); + } + + @Override + public void onSuggestSelection( + CharSequence text, int selectionStartIndex, int selectionEndIndex, + TextSelection.Options options, ITextSelectionCallback callback) + throws RemoteException { + // TODO(b/72481438): All remote calls need to take userId. + validateInput(text, selectionStartIndex, selectionEndIndex, callback); + + if (!bind()) { + callback.onFailure(); + return; + } + + synchronized (mLock) { + if (isBoundLocked()) { + mService.onSuggestSelection( + text, selectionStartIndex, selectionEndIndex, options, callback); + scheduleUnbindLocked(); + } else { + final Callable request = () -> { + onSuggestSelection( + text, selectionStartIndex, selectionEndIndex, + options, callback); + return null; + }; + final Callable onServiceFailure = () -> { + callback.onFailure(); + return null; + }; + enqueueRequestLocked(request, onServiceFailure, callback.asBinder()); + } + } + } + + @Override + public void onClassifyText( + CharSequence text, int startIndex, int endIndex, + TextClassification.Options options, ITextClassificationCallback callback) + throws RemoteException { + validateInput(text, startIndex, endIndex, callback); + + if (!bind()) { + callback.onFailure(); + return; + } + + synchronized (mLock) { + if (isBoundLocked()) { + mService.onClassifyText(text, startIndex, endIndex, options, callback); + scheduleUnbindLocked(); + } else { + final Callable request = () -> { + onClassifyText(text, startIndex, endIndex, options, callback); + return null; + }; + final Callable onServiceFailure = () -> { + callback.onFailure(); + return null; + }; + enqueueRequestLocked(request, onServiceFailure, callback.asBinder()); + } + } + } + + @Override + public void onGenerateLinks( + CharSequence text, TextLinks.Options options, ITextLinksCallback callback) + throws RemoteException { + validateInput(text, callback); + + if (!bind()) { + callback.onFailure(); + return; + } + + synchronized (mLock) { + if (isBoundLocked()) { + mService.onGenerateLinks(text, options, callback); + scheduleUnbindLocked(); + } else { + final Callable request = () -> { + onGenerateLinks(text, options, callback); + return null; + }; + final Callable onServiceFailure = () -> { + callback.onFailure(); + return null; + }; + enqueueRequestLocked(request, onServiceFailure, callback.asBinder()); + } + } + } + + /** + * @return true if the service is bound or in the process of being bound. + * Returns false otherwise. + */ + private boolean bind() { + synchronized (mLock) { + if (isBoundLocked() || isBindingLocked()) { + return true; + } + + // TODO: Handle bind timeout. + final boolean willBind; + final long identity = Binder.clearCallingIdentity(); + try { + Slog.d(LOG_TAG, "Binding to " + mServiceIntent.getComponent()); + willBind = mContext.bindServiceAsUser( + mServiceIntent, mConnection, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, + Binder.getCallingUserHandle()); + setBindingLocked(willBind); + } finally { + Binder.restoreCallingIdentity(identity); + } + return willBind; + } + } + + @GuardedBy("mLock") + private boolean isBoundLocked() { + return mService != null; + } + + @GuardedBy("mLock") + private boolean isBindingLocked() { + return mBinding; + } + + @GuardedBy("mLock") + private void setBindingLocked(boolean binding) { + mBinding = binding; + } + + private void unbind() { + synchronized (mLock) { + if (!isBoundLocked()) { + return; + } + + Slog.d(LOG_TAG, "Unbinding from " + mServiceIntent.getComponent()); + mContext.unbindService(mConnection); + + synchronized (mLock) { + mService = null; + } + } + } + + @GuardedBy("mLock") + private void scheduleUnbindLocked() { + mHandler.removeCallbacks(mUnbind); + mHandler.postDelayed(mUnbind, TIMEOUT_IDLE_BIND_MILLIS); + } + + @GuardedBy("mLock") + private void enqueueRequestLocked( + Callable request, Callable onServiceFailure, IBinder binder) { + mPendingRequests.add(new PendingRequest(request, onServiceFailure, binder)); + } + + @GuardedBy("mLock") + private void handlePendingRequestsLocked() { + // TODO(b/72481146): Implement PendingRequest similar to that in RemoteFillService. + final PendingRequest[] pendingRequests = + mPendingRequests.toArray(new PendingRequest[mPendingRequests.size()]); + for (PendingRequest pendingRequest : pendingRequests) { + if (isBoundLocked()) { + pendingRequest.executeLocked(); + } else { + pendingRequest.notifyServiceFailureLocked(); + } + } + } + + private final class PendingRequest implements IBinder.DeathRecipient { + + private final Callable mRequest; + private final Callable mOnServiceFailure; + private final IBinder mBinder; + + /** + * Initializes a new pending request. + * + * @param request action to perform when the service is bound + * @param onServiceFailure action to perform when the service dies or disconnects + * @param binder binder to the process that made this pending request + */ + PendingRequest( + @NonNull Callable request, @NonNull Callable onServiceFailure, + @NonNull IBinder binder) { + mRequest = Preconditions.checkNotNull(request); + mOnServiceFailure = Preconditions.checkNotNull(onServiceFailure); + mBinder = Preconditions.checkNotNull(binder); + try { + mBinder.linkToDeath(this, 0); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @GuardedBy("mLock") + void executeLocked() { + removeLocked(); + try { + mRequest.call(); + } catch (Exception e) { + Slog.d(LOG_TAG, "Error handling pending request: " + e.getMessage()); + } + } + + @GuardedBy("mLock") + void notifyServiceFailureLocked() { + removeLocked(); + try { + mOnServiceFailure.call(); + } catch (Exception e) { + Slog.d(LOG_TAG, "Error notifying callback of service failure: " + + e.getMessage()); + } + } + + @Override + public void binderDied() { + synchronized (mLock) { + // No need to handle this pending request anymore. Remove. + removeLocked(); + } + } + + @GuardedBy("mLock") + private void removeLocked() { + mPendingRequests.remove(this); + mBinder.unlinkToDeath(this, 0); + } + } + + private static void validateInput( + CharSequence text, int startIndex, int endIndex, Object callback) + throws RemoteException { + try { + TextClassifier.Utils.validate(text, startIndex, endIndex, true /* allowInMainThread */); + Preconditions.checkNotNull(callback); + } catch (IllegalArgumentException | NullPointerException e) { + throw new RemoteException(e.getMessage()); + } + } + + private static void validateInput(CharSequence text, Object callback) throws RemoteException { + try { + TextClassifier.Utils.validate(text, true /* allowInMainThread */); + Preconditions.checkNotNull(callback); + } catch (IllegalArgumentException | NullPointerException e) { + throw new RemoteException(e.getMessage()); + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index f95c6f042fe..210fd473ccd 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -111,6 +111,7 @@ import com.android.server.stats.StatsCompanionService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; import com.android.server.telecom.TelecomLoaderService; +import com.android.server.textclassifier.TextClassificationManagerService; import com.android.server.trust.TrustManagerService; import com.android.server.tv.TvInputManagerService; import com.android.server.tv.TvRemoteService; @@ -733,6 +734,8 @@ public final class SystemServer { false); boolean disableTextServices = SystemProperties.getBoolean("config.disable_textservices", false); + boolean disableSystemTextClassifier = SystemProperties.getBoolean( + "config.disable_systemtextclassifier", false); boolean disableConsumerIr = SystemProperties.getBoolean("config.disable_consumerir", false); boolean disableVrManager = SystemProperties.getBoolean("config.disable_vrmanager", false); boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice", @@ -1066,6 +1069,12 @@ public final class SystemServer { traceEnd(); } + if (!disableSystemTextClassifier) { + traceBeginAndSlog("StartTextClassificationManagerService"); + mSystemServiceManager.startService(TextClassificationManagerService.Lifecycle.class); + traceEnd(); + } + traceBeginAndSlog("StartNetworkScoreService"); try { networkScore = new NetworkScoreService(context); -- GitLab From a0a5bced02d9a10496454d0f759ad4325f12a61a Mon Sep 17 00:00:00 2001 From: Hakan Lindh Date: Mon, 29 Jan 2018 16:25:16 -0800 Subject: [PATCH 064/416] Fix: Replacing const for test to work properly Removing device capabilities check since it's already there in CTS. Bug: 72504638 Test: manual Change-Id: Idea5e762bf8f13782506a22630952a1a24b21344 --- .../com/android/mediaframeworktest/helpers/StaticMetadata.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java index 5680d9f9e4c..b3f443b30a7 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/StaticMetadata.java @@ -1860,9 +1860,6 @@ public class StaticMetadata { return new ArrayList(); } - checkArrayValuesInRange(key, availableCaps, - CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE, - CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO); capList = Arrays.asList(CameraTestUtils.toObject(availableCaps)); return capList; } -- GitLab From 854b697f613f1ae876a61526a686b935557e9f43 Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Tue, 16 Jan 2018 13:35:36 -0800 Subject: [PATCH 065/416] Remove feature flag for settings app info v2 Bug: 69384089 Test: rebuild Change-Id: I121dea83742f28072a3275d5e02444cf5e89a0b7 --- core/java/android/util/FeatureFlagUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 8af4b7150a3..57d23ced786 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -38,7 +38,6 @@ public class FeatureFlagUtils { static { DEFAULT_FLAGS = new HashMap<>(); DEFAULT_FLAGS.put("device_info_v2", "true"); - DEFAULT_FLAGS.put("settings_app_info_v2", "true"); DEFAULT_FLAGS.put("settings_connected_device_v2", "true"); DEFAULT_FLAGS.put("settings_battery_v2", "true"); DEFAULT_FLAGS.put("settings_battery_display_app_list", "false"); -- GitLab From dde80a9cf4111cc4dee1ff3638e383fa59b952d2 Mon Sep 17 00:00:00 2001 From: Jin Seok Park Date: Mon, 29 Jan 2018 19:23:06 +0900 Subject: [PATCH 066/416] Remove show/hide API This CL removes the show/hide API from MediaControlView2 and instead provides the developer with the same function by calling setVisibility(View.VISIBLE | View.GONE), and calling the new APIs set/getTimeout() and requestPlayButtonFocus(). The original Runnable code has been moved to onVisibilityAggregated() as per API council's request. Test: build Change-Id: If53fb8849b4e086619a9c93c85e61da70272976e --- .../android/widget/MediaControlView2.java | 66 ++++++++++++------- .../update/MediaControlView2Provider.java | 7 +- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java index a4da05f8b98..39c23b413cc 100644 --- a/core/java/android/widget/MediaControlView2.java +++ b/core/java/android/widget/MediaControlView2.java @@ -45,6 +45,19 @@ import java.lang.annotation.RetentionPolicy; * developer needs to manually retrieve a MediaController instance and set it to MediaControlView2 * by calling setController(MediaController controller). * + *

+ * There is no separate method that handles the show/hide behavior for MediaControlView2. Instead, + * one can directly change the visibility of this view by calling View.setVisibility(int). The + * values supported are View.VISIBLE and View.GONE. + * In addition, the following customizations are supported: + * 1. Modify default timeout value of 2 seconds by calling setTimeout(long). + * 2. Set focus to the play/pause button by calling requestPlayButtonFocus(). + * + *

+ * It is also possible to add custom buttons with custom icons and actions inside MediaControlView2. + * Those buttons will be shown when the overflow button is clicked. + * See {@link VideoView2#setCustomActions} for more details on how to add. + * * TODO PUBLIC API * @hide */ @@ -164,22 +177,6 @@ public class MediaControlView2 extends FrameLayout { mProvider.setController_impl(controller); } - /** - * Shows the control view on screen. It will disappear automatically after 3 seconds of - * inactivity. - */ - public void show() { - mProvider.show_impl(); - } - - /** - * Shows the control view on screen. It will disappear automatically after {@code timeout} - * milliseconds of inactivity. - */ - public void show(long timeout) { - mProvider.show_impl(timeout); - } - /** * Returns whether the control view is currently shown or hidden. */ @@ -187,13 +184,6 @@ public class MediaControlView2 extends FrameLayout { return mProvider.isShowing_impl(); } - /** - * Hide the control view from the screen. - */ - public void hide() { - mProvider.hide_impl(); - } - /** * Changes the visibility state of an individual button. Default value is View.Visible. * @@ -217,6 +207,36 @@ public class MediaControlView2 extends FrameLayout { mProvider.setButtonVisibility_impl(button, visibility); } + /** + * Requests focus for the play/pause button. + */ + public void requestPlayButtonFocus() { + mProvider.requestPlayButtonFocus_impl(); + } + + /** + * Sets a new timeout value (in milliseconds) for showing MediaControlView2. The default value + * is set as 2 seconds. + * @param timeout the + */ + public void setTimeout(long timeout) { + mProvider.setTimeout_impl(timeout); + } + + /** + * Retrieves current timeout value (in milliseconds) for showing MediaControlView2. The default + * value is set as 2 seconds. + */ + public long getTimeout() { + return mProvider.getTimeout_impl(); + } + + @Override + public void onVisibilityAggregated(boolean isVisible) { + + mProvider.onVisibilityAggregated_impl(isVisible); + } + @Override protected void onAttachedToWindow() { mProvider.onAttachedToWindow_impl(); diff --git a/media/java/android/media/update/MediaControlView2Provider.java b/media/java/android/media/update/MediaControlView2Provider.java index 4464f8f2ac6..95fe3631716 100644 --- a/media/java/android/media/update/MediaControlView2Provider.java +++ b/media/java/android/media/update/MediaControlView2Provider.java @@ -36,9 +36,10 @@ import android.view.View; // TODO @SystemApi public interface MediaControlView2Provider extends ViewProvider { void setController_impl(MediaController controller); - void show_impl(); - void show_impl(long timeout); boolean isShowing_impl(); - void hide_impl(); void setButtonVisibility_impl(int button, int visibility); + void requestPlayButtonFocus_impl(); + void onVisibilityAggregated_impl(boolean isVisible); + void setTimeout_impl(long timeout); + long getTimeout_impl(); } -- GitLab From 017e7f90eea67b0ecd002d1ab193f60238ad0555 Mon Sep 17 00:00:00 2001 From: Nathan Harold Date: Mon, 29 Jan 2018 17:17:10 -0800 Subject: [PATCH 067/416] Update hashCode in CellSignalStrength classes The CellSignalStrength hashCode function was using a fairly ineffective method of hashing. An External reporter requested that we fix it. This CL moves to using the Objects.hash() implementation. Bug: 22479413 Test: compilation Change-Id: Ic017ba54ef757fd3ec3e5000ac61108dd836bd8a --- .../java/android/telephony/CellSignalStrengthCdma.java | 6 +++--- .../java/android/telephony/CellSignalStrengthGsm.java | 5 +++-- .../java/android/telephony/CellSignalStrengthLte.java | 7 +++---- .../java/android/telephony/CellSignalStrengthWcdma.java | 5 +++-- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java index 047436207ad..dfaaab91801 100644 --- a/telephony/java/android/telephony/CellSignalStrengthCdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java @@ -20,6 +20,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import java.util.Objects; + /** * Signal strength related information. */ @@ -293,9 +295,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements @Override public int hashCode() { - int primeNum = 31; - return ((mCdmaDbm * primeNum) + (mCdmaEcio * primeNum) - + (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum)); + return Objects.hash(mCdmaDbm, mCdmaEcio, mEvdoDbm, mEvdoEcio, mEvdoSnr); } @Override diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index 4137853e79a..f68d2cad122 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -20,6 +20,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import java.util.Objects; + /** * GSM signal strength related information. */ @@ -185,8 +187,7 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P @Override public int hashCode() { - int primeNum = 31; - return (mSignalStrength * primeNum) + (mBitErrorRate * primeNum); + return Objects.hash(mSignalStrength, mBitErrorRate, mTimingAdvance); } @Override diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index 0d07a40822d..6ffc8b6e497 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -20,6 +20,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import java.util.Objects; + /** * LTE signal strength related information. */ @@ -231,10 +233,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P @Override public int hashCode() { - int primeNum = 31; - return (mSignalStrength * primeNum) + (mRsrp * primeNum) - + (mRsrq * primeNum) + (mRssnr * primeNum) + (mCqi * primeNum) - + (mTimingAdvance * primeNum); + return Objects.hash(mSignalStrength, mRsrp, mRsrq, mRssnr, mCqi, mTimingAdvance); } @Override diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java index b94b01da87b..2cd56b8500a 100644 --- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java @@ -20,6 +20,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.telephony.Rlog; +import java.util.Objects; + /** * Wcdma signal strength related information. */ @@ -156,8 +158,7 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements @Override public int hashCode() { - int primeNum = 31; - return (mSignalStrength * primeNum) + (mBitErrorRate * primeNum); + return Objects.hash(mSignalStrength, mBitErrorRate); } @Override -- GitLab From 5b7f426ff04820f81877ccb696bf6245dede89e7 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Fri, 26 Jan 2018 11:03:54 -0500 Subject: [PATCH 068/416] Use a separate thread to decode AnimatedImageDrawable Bug: 63908092 Test: Manual: Ie18811ba29a1db163aca08472b04ae185e9344f0 Depends on https://skia-review.googlesource.com/#/c/skia/+/101544. That change removes the Skia class's time checks, and leaving it up to the client to keep track of the time. In this case, the client wants to keep track of the time because it only wants to update while it is being drawn. If it goes off screen (for example), it will just resume where it left off when it returns on screen. This allows for smooth animations. If an AnimatedImageDrawable is being drawn to a SkiaRecordingCanvas, decode on the new (lazily-created) AnimatedImageThread. When running, always decode one frame ahead on the AnimatedImageThread so that it will be ready when it is time to display. During prepareTree, update the time and check whether there is a new frame ready to draw or the next frame needs to be decoded. In either case, return true. The next frame to be decoded will be triggered by onDraw. Change-Id: If447976e9df417060a950f658dbca9cf7980dd02 --- .../graphics/AnimatedImageDrawable.cpp | 18 +- .../drawable/AnimatedImageDrawable.java | 48 +++- libs/hwui/Android.bp | 1 + libs/hwui/hwui/AnimatedImageDrawable.cpp | 247 ++++++++++++------ libs/hwui/hwui/AnimatedImageDrawable.h | 94 ++++--- libs/hwui/hwui/AnimatedImageThread.cpp | 45 ++++ libs/hwui/hwui/AnimatedImageThread.h | 47 ++++ libs/hwui/pipeline/skia/SkiaDisplayList.cpp | 2 - libs/hwui/pipeline/skia/SkiaPipeline.cpp | 10 - libs/hwui/pipeline/skia/SkiaPipeline.h | 11 - 10 files changed, 370 insertions(+), 153 deletions(-) create mode 100644 libs/hwui/hwui/AnimatedImageThread.cpp create mode 100644 libs/hwui/hwui/AnimatedImageThread.h diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp index 0e562c03b76..c88cf5cff04 100644 --- a/core/jni/android/graphics/AnimatedImageDrawable.cpp +++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp @@ -16,16 +16,16 @@ #include "GraphicsJNI.h" #include "ImageDecoder.h" -#include "core_jni_helpers.h" #include "Utils.h" +#include "core_jni_helpers.h" -#include -#include #include #include #include #include #include +#include +#include using namespace android; @@ -85,6 +85,9 @@ static jlong AnimatedImageDrawable_nGetNativeFinalizer(JNIEnv* /*env*/, jobject return static_cast(reinterpret_cast(&AnimatedImageDrawable_destruct)); } +// Java's FINISHED relies on this being -1 +static_assert(SkAnimatedImage::kFinished == -1); + static jlong AnimatedImageDrawable_nDraw(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jlong canvasPtr) { auto* drawable = reinterpret_cast(nativePtr); @@ -115,9 +118,6 @@ static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, return drawable->isRunning(); } -// Java's NOT_RUNNING relies on this being -2.0. -static_assert(SkAnimatedImage::kNotRunning == -2.0); - static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { auto* drawable = reinterpret_cast(nativePtr); return drawable->start(); @@ -172,6 +172,11 @@ static long AnimatedImageDrawable_nNativeByteSize(JNIEnv* env, jobject /*clazz*/ return sizeof(drawable); } +static void AnimatedImageDrawable_nMarkInvisible(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { + auto* drawable = reinterpret_cast(nativePtr); + drawable->markInvisible(); +} + static const JNINativeMethod gAnimatedImageDrawableMethods[] = { { "nCreate", "(JLandroid/graphics/ImageDecoder;IILandroid/graphics/Rect;)J", (void*) AnimatedImageDrawable_nCreate }, { "nGetNativeFinalizer", "()J", (void*) AnimatedImageDrawable_nGetNativeFinalizer }, @@ -185,6 +190,7 @@ static const JNINativeMethod gAnimatedImageDrawableMethods[] = { { "nSetLoopCount", "(JI)V", (void*) AnimatedImageDrawable_nSetLoopCount }, { "nSetOnAnimationEndListener", "(JLandroid/graphics/drawable/AnimatedImageDrawable;)V", (void*) AnimatedImageDrawable_nSetOnAnimationEndListener }, { "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize }, + { "nMarkInvisible", "(J)V", (void*) AnimatedImageDrawable_nMarkInvisible }, }; int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) { diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java index 0ec19f9a4ae..4328109937c 100644 --- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java @@ -16,6 +16,8 @@ package android.graphics.drawable; +import dalvik.annotation.optimization.FastNative; + import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; @@ -60,7 +62,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { private int mIntrinsicHeight; private boolean mStarting; - private boolean mRunning; private Handler mHandler; @@ -222,8 +223,8 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { return mIntrinsicHeight; } - // nDraw returns -2 if the animation is not running. - private static final int NOT_RUNNING = -2; + // nDraw returns -1 if the animation has finished. + private static final int FINISHED = -1; @Override public void draw(@NonNull Canvas canvas) { @@ -235,8 +236,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { mStarting = false; postOnAnimationStart(); - - mRunning = true; } long nextUpdate = nDraw(mState.mNativePtr, canvas.getNativeCanvasWrapper()); @@ -244,12 +243,9 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { // will manage the animation if (nextUpdate > 0) { scheduleSelf(mRunnable, nextUpdate); - } else if (nextUpdate == NOT_RUNNING) { - // -2 means the animation ended, when drawn in software mode. - if (mRunning) { - postOnAnimationEnd(); - mRunning = false; - } + } else if (nextUpdate == FINISHED) { + // This means the animation was drawn in software mode and ended. + postOnAnimationEnd(); } } @@ -292,6 +288,19 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { return PixelFormat.TRANSLUCENT; } + @Override + public boolean setVisible(boolean visible, boolean restart) { + if (!super.setVisible(visible, restart)) { + return false; + } + + if (!visible) { + nMarkInvisible(mState.mNativePtr); + } + + return true; + } + // Animatable overrides /** * Return whether the animation is currently running. @@ -301,7 +310,10 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { */ @Override public boolean isRunning() { - return mRunning; + if (mState == null) { + throw new IllegalStateException("called isRunning on empty AnimatedImageDrawable"); + } + return nIsRunning(mState.mNativePtr); } /** @@ -336,7 +348,6 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { throw new IllegalStateException("called stop on empty AnimatedImageDrawable"); } nStop(mState.mNativePtr); - mRunning = false; } // Animatable2 overrides @@ -405,18 +416,29 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { private static native long nCreate(long nativeImageDecoder, @Nullable ImageDecoder decoder, int width, int height, Rect cropRect) throws IOException; + @FastNative private static native long nGetNativeFinalizer(); private static native long nDraw(long nativePtr, long canvasNativePtr); + @FastNative private static native void nSetAlpha(long nativePtr, int alpha); + @FastNative private static native int nGetAlpha(long nativePtr); + @FastNative private static native void nSetColorFilter(long nativePtr, long nativeFilter); + @FastNative private static native boolean nIsRunning(long nativePtr); // Return whether the animation started. + @FastNative private static native boolean nStart(long nativePtr); + @FastNative private static native void nStop(long nativePtr); + @FastNative private static native void nSetLoopCount(long nativePtr, int loopCount); // Pass the drawable down to native so it can call onAnimationEnd. private static native void nSetOnAnimationEndListener(long nativePtr, @Nullable AnimatedImageDrawable drawable); + @FastNative private static native long nNativeByteSize(long nativePtr); + @FastNative + private static native void nMarkInvisible(long nativePtr); } diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 17f9b7cd62f..3323bce8b5a 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -138,6 +138,7 @@ cc_defaults { srcs: [ "hwui/AnimatedImageDrawable.cpp", + "hwui/AnimatedImageThread.cpp", "hwui/Bitmap.cpp", "font/CacheTexture.cpp", "font/Font.cpp", diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp index e01bf3d003e..264b95e0e6d 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -15,21 +15,21 @@ */ #include "AnimatedImageDrawable.h" +#include "AnimatedImageThread.h" -#include "thread/Task.h" -#include "thread/TaskManager.h" -#include "thread/TaskProcessor.h" #include "utils/TraceUtils.h" #include #include -#include #include +#include namespace android { AnimatedImageDrawable::AnimatedImageDrawable(sk_sp animatedImage) - : mSkAnimatedImage(std::move(animatedImage)) { } + : mSkAnimatedImage(std::move(animatedImage)) { + mTimeToShowNextSnapshot = mSkAnimatedImage->currentFrameDuration(); +} void AnimatedImageDrawable::syncProperties() { mAlpha = mStagingAlpha; @@ -37,88 +37,78 @@ void AnimatedImageDrawable::syncProperties() { } bool AnimatedImageDrawable::start() { - SkAutoExclusive lock(mLock); - if (mSkAnimatedImage->isRunning()) { + if (mRunning) { return false; } - if (!mSnapshot) { - mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot()); - } - - // While stopped, update() does not decode, but it does advance the time. - // This prevents us from skipping ahead when we resume. - const double currentTime = SkTime::GetMSecs(); - mSkAnimatedImage->update(currentTime); - mSkAnimatedImage->start(); - return mSkAnimatedImage->isRunning(); + mRunning = true; + return true; } void AnimatedImageDrawable::stop() { - SkAutoExclusive lock(mLock); - mSkAnimatedImage->stop(); + mRunning = false; } bool AnimatedImageDrawable::isRunning() { - return mSkAnimatedImage->isRunning(); + return mRunning; } -// This is really a Task but that doesn't really work when Future<> -// expects to be able to get/set a value -class AnimatedImageDrawable::AnimatedImageTask : public uirenderer::Task { -public: - AnimatedImageTask(AnimatedImageDrawable* animatedImageDrawable) - : mAnimatedImageDrawable(sk_ref_sp(animatedImageDrawable)) {} - - sk_sp mAnimatedImageDrawable; - bool mIsCompleted = false; -}; - -class AnimatedImageDrawable::AnimatedImageTaskProcessor : public uirenderer::TaskProcessor { -public: - explicit AnimatedImageTaskProcessor(uirenderer::TaskManager* taskManager) - : uirenderer::TaskProcessor(taskManager) {} - ~AnimatedImageTaskProcessor() {} - - virtual void onProcess(const sp>& task) override { - ATRACE_NAME("Updating AnimatedImageDrawables"); - AnimatedImageTask* t = static_cast(task.get()); - t->mAnimatedImageDrawable->update(); - t->mIsCompleted = true; - task->setResult(true); - }; -}; - -void AnimatedImageDrawable::scheduleUpdate(uirenderer::TaskManager* taskManager) { - if (!mSkAnimatedImage->isRunning() - || (mDecodeTask.get() != nullptr && !mDecodeTask->mIsCompleted)) { - return; - } - - if (!mDecodeTaskProcessor.get()) { - mDecodeTaskProcessor = new AnimatedImageTaskProcessor(taskManager); - } - - // TODO get one frame ahead and only schedule updates when you need to replenish - mDecodeTask = new AnimatedImageTask(this); - mDecodeTaskProcessor->add(mDecodeTask); +bool AnimatedImageDrawable::nextSnapshotReady() const { + return mNextSnapshot.valid() && + mNextSnapshot.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } -void AnimatedImageDrawable::update() { - SkAutoExclusive lock(mLock); +// Only called on the RenderThread while UI thread is locked. +bool AnimatedImageDrawable::isDirty() { + const double currentTime = SkTime::GetMSecs(); + const double lastWallTime = mLastWallTime; - if (!mSkAnimatedImage->isRunning()) { - return; + mLastWallTime = currentTime; + if (!mRunning) { + mDidDraw = false; + return false; } - const double currentTime = SkTime::GetMSecs(); - if (currentTime >= mNextFrameTime) { - mNextFrameTime = mSkAnimatedImage->update(currentTime); - mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot()); - mIsDirty = true; + std::unique_lock lock{mSwapLock}; + if (mDidDraw) { + mCurrentTime += currentTime - lastWallTime; + mDidDraw = false; } + + if (!mNextSnapshot.valid()) { + // Need to trigger onDraw in order to start decoding the next frame. + return true; + } + + return nextSnapshotReady() && mCurrentTime >= mTimeToShowNextSnapshot; } +// Only called on the AnimatedImageThread. +AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() { + Snapshot snap; + { + std::unique_lock lock{mImageLock}; + snap.mDuration = mSkAnimatedImage->decodeNextFrame(); + snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); + } + + return snap; +} + +// Only called on the AnimatedImageThread. +AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() { + Snapshot snap; + { + std::unique_lock lock{mImageLock}; + mSkAnimatedImage->reset(); + snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot()); + snap.mDuration = mSkAnimatedImage->currentFrameDuration(); + } + + return snap; +} + +// Only called on the RenderThread. void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { SkTLazy lazyPaint; if (mAlpha != SK_AlphaOPAQUE || mColorFilter.get()) { @@ -128,25 +118,71 @@ void AnimatedImageDrawable::onDraw(SkCanvas* canvas) { lazyPaint.get()->setFilterQuality(kLow_SkFilterQuality); } - SkAutoExclusive lock(mLock); - if (mSnapshot) { - canvas->drawPicture(mSnapshot, nullptr, lazyPaint.getMaybeNull()); - } else { - // TODO: we could potentially keep the cached surface around if there is a paint and we know - // the drawable is attached to the view system + mDidDraw = true; + + bool drewDirectly = false; + if (!mSnapshot.mPic) { + // The image is not animating, and never was. Draw directly from + // mSkAnimatedImage. SkAutoCanvasRestore acr(canvas, false); if (lazyPaint.isValid()) { canvas->saveLayer(mSkAnimatedImage->getBounds(), lazyPaint.get()); } + + std::unique_lock lock{mImageLock}; mSkAnimatedImage->draw(canvas); + drewDirectly = true; } - mIsDirty = false; + if (mRunning && mFinished) { + auto& thread = uirenderer::AnimatedImageThread::getInstance(); + mNextSnapshot = thread.reset(sk_ref_sp(this)); + mFinished = false; + } + + bool finalFrame = false; + if (mRunning && nextSnapshotReady()) { + std::unique_lock lock{mSwapLock}; + if (mCurrentTime >= mTimeToShowNextSnapshot) { + mSnapshot = mNextSnapshot.get(); + const double timeToShowCurrentSnap = mTimeToShowNextSnapshot; + if (mSnapshot.mDuration == SkAnimatedImage::kFinished) { + finalFrame = true; + mRunning = false; + mFinished = true; + } else { + mTimeToShowNextSnapshot += mSnapshot.mDuration; + if (mCurrentTime >= mTimeToShowNextSnapshot) { + // This would mean showing the current frame very briefly. It's + // possible that not being displayed for a time resulted in + // mCurrentTime being far ahead. Prevent showing many frames + // rapidly by going back to the beginning of this frame time. + mCurrentTime = timeToShowCurrentSnap; + } + } + } + } + + if (mRunning && !mNextSnapshot.valid()) { + auto& thread = uirenderer::AnimatedImageThread::getInstance(); + mNextSnapshot = thread.decodeNextFrame(sk_ref_sp(this)); + } + + if (!drewDirectly) { + // No other thread will modify mCurrentSnap so this should be safe to + // use without locking. + canvas->drawPicture(mSnapshot.mPic, nullptr, lazyPaint.getMaybeNull()); + } + + if (finalFrame) { + if (mEndListener) { + mEndListener->onAnimationEnd(); + mEndListener = nullptr; + } + } } double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { - // update the drawable with the current time - double nextUpdate = mSkAnimatedImage->update(SkTime::GetMSecs()); SkAutoCanvasRestore acr(canvas, false); if (mStagingAlpha != SK_AlphaOPAQUE || mStagingColorFilter.get()) { SkPaint paint; @@ -154,8 +190,59 @@ double AnimatedImageDrawable::drawStaging(SkCanvas* canvas) { paint.setColorFilter(mStagingColorFilter); canvas->saveLayer(mSkAnimatedImage->getBounds(), &paint); } - canvas->drawDrawable(mSkAnimatedImage.get()); - return nextUpdate; + + if (mFinished && !mRunning) { + // Continue drawing the last frame, and return 0 to indicate no need to + // redraw. + std::unique_lock lock{mImageLock}; + canvas->drawDrawable(mSkAnimatedImage.get()); + return 0.0; + } + + bool update = false; + { + const double currentTime = SkTime::GetMSecs(); + std::unique_lock lock{mSwapLock}; + // mLastWallTime starts off at 0. If it is still 0, just update it to + // the current time and avoid updating + if (mLastWallTime == 0.0) { + mCurrentTime = currentTime; + } else if (mRunning) { + if (mFinished) { + mCurrentTime = currentTime; + { + std::unique_lock lock{mImageLock}; + mSkAnimatedImage->reset(); + } + mTimeToShowNextSnapshot = currentTime + mSkAnimatedImage->currentFrameDuration(); + } else { + mCurrentTime += currentTime - mLastWallTime; + update = mCurrentTime >= mTimeToShowNextSnapshot; + } + } + mLastWallTime = currentTime; + } + + double duration = 0.0; + { + std::unique_lock lock{mImageLock}; + if (update) { + duration = mSkAnimatedImage->decodeNextFrame(); + } + + canvas->drawDrawable(mSkAnimatedImage.get()); + } + + std::unique_lock lock{mSwapLock}; + if (update) { + if (duration == SkAnimatedImage::kFinished) { + mRunning = false; + mFinished = true; + } else { + mTimeToShowNextSnapshot += duration; + } + } + return mTimeToShowNextSnapshot; } -}; // namespace android +} // namespace android diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h index df8a5e48d18..9d84ed5f447 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.h +++ b/libs/hwui/hwui/AnimatedImageDrawable.h @@ -17,22 +17,20 @@ #pragma once #include +#include #include #include #include #include #include -#include +#include -class SkPicture; +#include +#include namespace android { -namespace uirenderer { -class TaskManager; -} - class OnAnimationEndListener { public: virtual ~OnAnimationEndListener() {} @@ -41,68 +39,102 @@ public: }; /** - * Native component of android.graphics.drawable.AnimatedImageDrawables.java. This class can be - * drawn into Canvas.h and maintains the state needed to drive the animation from the RenderThread. + * Native component of android.graphics.drawable.AnimatedImageDrawables.java. + * This class can be drawn into Canvas.h and maintains the state needed to drive + * the animation from the RenderThread. */ class ANDROID_API AnimatedImageDrawable : public SkDrawable { public: AnimatedImageDrawable(sk_sp animatedImage); /** - * This returns true if the animation has updated and signals that the next draw will contain - * new content. + * This updates the internal time and returns true if the animation needs + * to be redrawn. + * + * This is called on RenderThread, while the UI thread is locked. */ - bool isDirty() const { return mIsDirty; } + bool isDirty(); int getStagingAlpha() const { return mStagingAlpha; } void setStagingAlpha(int alpha) { mStagingAlpha = alpha; } void setStagingColorFilter(sk_sp filter) { mStagingColorFilter = filter; } void syncProperties(); - virtual SkRect onGetBounds() override { - return mSkAnimatedImage->getBounds(); - } + virtual SkRect onGetBounds() override { return mSkAnimatedImage->getBounds(); } + // Draw to software canvas, and return time to next draw. double drawStaging(SkCanvas* canvas); - // Returns true if the animation was started; false otherwise (e.g. it was already running) + // Returns true if the animation was started; false otherwise (e.g. it was + // already running) bool start(); void stop(); bool isRunning(); - void setRepetitionCount(int count) { - mSkAnimatedImage->setRepetitionCount(count); - } + void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); } void setOnAnimationEndListener(std::unique_ptr listener) { mEndListener = std::move(listener); } - void scheduleUpdate(uirenderer::TaskManager* taskManager); + void markInvisible() { mDidDraw = false; } + + struct Snapshot { + sk_sp mPic; + int mDuration; + + Snapshot() = default; + + Snapshot(Snapshot&&) = default; + Snapshot& operator=(Snapshot&&) = default; + + PREVENT_COPY_AND_ASSIGN(Snapshot); + }; + + // These are only called on AnimatedImageThread. + Snapshot decodeNextFrame(); + Snapshot reset(); protected: virtual void onDraw(SkCanvas* canvas) override; private: - void update(); - sk_sp mSkAnimatedImage; - sk_sp mSnapshot; - SkMutex mLock; + bool mRunning = false; + bool mFinished = false; + + // A snapshot of the current frame to draw. + Snapshot mSnapshot; + + std::future mNextSnapshot; + + bool nextSnapshotReady() const; + + // When to switch from mSnapshot to mNextSnapshot. + double mTimeToShowNextSnapshot = 0.0; + + // The current time for the drawable itself. + double mCurrentTime = 0.0; + + // The wall clock of the last time we called isDirty. + double mLastWallTime = 0.0; + + // Whether we drew since the last call to isDirty. + bool mDidDraw = false; + + // Locked when assigning snapshots and times. Operations while this is held + // should be short. + std::mutex mSwapLock; + + // Locked when mSkAnimatedImage is being updated or drawn. + std::mutex mImageLock; int mStagingAlpha = SK_AlphaOPAQUE; sk_sp mStagingColorFilter; int mAlpha = SK_AlphaOPAQUE; sk_sp mColorFilter; - double mNextFrameTime = 0.0; - bool mIsDirty = false; - - class AnimatedImageTask; - class AnimatedImageTaskProcessor; - sp mDecodeTask; - sp mDecodeTaskProcessor; std::unique_ptr mEndListener; }; -}; // namespace android +} // namespace android diff --git a/libs/hwui/hwui/AnimatedImageThread.cpp b/libs/hwui/hwui/AnimatedImageThread.cpp new file mode 100644 index 00000000000..c8990039875 --- /dev/null +++ b/libs/hwui/hwui/AnimatedImageThread.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AnimatedImageThread.h" + +#include + +namespace android { +namespace uirenderer { + +AnimatedImageThread& AnimatedImageThread::getInstance() { + static AnimatedImageThread* sInstance = new AnimatedImageThread(); + return *sInstance; +} + +AnimatedImageThread::AnimatedImageThread() { + setpriority(PRIO_PROCESS, 0, PRIORITY_NORMAL + PRIORITY_MORE_FAVORABLE); + start("AnimatedImageThread"); +} + +std::future AnimatedImageThread::decodeNextFrame( + const sk_sp& drawable) { + return queue().async([drawable]() { return drawable->decodeNextFrame(); }); +} + +std::future AnimatedImageThread::reset( + const sk_sp& drawable) { + return queue().async([drawable]() { return drawable->reset(); }); +} + +} // namespace uirenderer +} // namespace android diff --git a/libs/hwui/hwui/AnimatedImageThread.h b/libs/hwui/hwui/AnimatedImageThread.h new file mode 100644 index 00000000000..9e3537430d5 --- /dev/null +++ b/libs/hwui/hwui/AnimatedImageThread.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANIMATEDIMAGETHREAD_H_ +#define ANIMATEDIMAGETHREAD_H_ + +#include "AnimatedImageDrawable.h" +#include "thread/ThreadBase.h" + +#include + +namespace android { + +namespace uirenderer { + +class AnimatedImageThread : private ThreadBase { + PREVENT_COPY_AND_ASSIGN(AnimatedImageThread); + +public: + static AnimatedImageThread& getInstance(); + + std::future decodeNextFrame( + const sk_sp&); + std::future reset(const sk_sp&); + +private: + AnimatedImageThread(); +}; + +} // namespace uirenderer + +} // namespace android + +#endif // ANIMATEDIMAGETHREAD_H_ diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp index cf0b6a4d1dc..aa14699ae4c 100644 --- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp +++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp @@ -98,8 +98,6 @@ bool SkiaDisplayList::prepareListAndChildren( isDirty = true; } if (animatedImage->isRunning()) { - static_cast(info.canvasContext.getRenderPipeline()) - ->scheduleDeferredUpdate(animatedImage); info.out.hasAnimations = true; } } diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 534782a5dc0..9db39d954e4 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -40,7 +40,6 @@ uint8_t SkiaPipeline::mSpotShadowAlpha = 0; Vector3 SkiaPipeline::mLightCenter = {FLT_MIN, FLT_MIN, FLT_MIN}; SkiaPipeline::SkiaPipeline(RenderThread& thread) : mRenderThread(thread) { - mAnimatedImageDrawables.reserve(30); mVectorDrawables.reserve(30); } @@ -327,15 +326,6 @@ void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& cli ATRACE_NAME("flush commands"); surface->getCanvas()->flush(); - - // TODO move to another method - if (!mAnimatedImageDrawables.empty()) { - ATRACE_NAME("Update AnimatedImageDrawables"); - for (auto animatedImage : mAnimatedImageDrawables) { - animatedImage->scheduleUpdate(getTaskManager()); - } - mAnimatedImageDrawables.clear(); - } } namespace { diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h index cc75e9c5b38..3800194440f 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.h +++ b/libs/hwui/pipeline/skia/SkiaPipeline.h @@ -55,12 +55,6 @@ public: std::vector* getVectorDrawables() { return &mVectorDrawables; } - void scheduleDeferredUpdate(AnimatedImageDrawable* imageDrawable) { - mAnimatedImageDrawables.push_back(imageDrawable); - } - - std::vector* getAnimatingImages() { return &mAnimatedImageDrawables; } - static void destroyLayer(RenderNode* node); static void prepareToDraw(const renderthread::RenderThread& thread, Bitmap* bitmap); @@ -144,11 +138,6 @@ private: */ std::vector mVectorDrawables; - /** - * populated by prepareTree with images with active animations - */ - std::vector mAnimatedImageDrawables; - // Block of properties used only for debugging to record a SkPicture and save it in a file. /** * mCapturedFile is used to enforce we don't capture more than once for a given name (cause -- GitLab From 1996dbb19cd43d0ffa034cafe460fe27342e584e Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Mon, 29 Jan 2018 19:51:35 -0500 Subject: [PATCH 069/416] Make AnimatedImageDrawable.start reset Bug: b/63908092 Test: Manual: Ie18811ba29a1db163aca08472b04ae185e9344f0 If the animation has already started and stopped (via stop()), restart the animatino on a call to start(). Change-Id: I0a14a1e643f32469fe5519949ee8ef046107e9a8 --- .../java/android/graphics/drawable/AnimatedImageDrawable.java | 3 ++- libs/hwui/hwui/AnimatedImageDrawable.cpp | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java index 4328109937c..bd49b87ec20 100644 --- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java @@ -319,7 +319,8 @@ public class AnimatedImageDrawable extends Drawable implements Animatable2 { /** * Start the animation. * - *

Does nothing if the animation is already running. + *

Does nothing if the animation is already running. If the animation is stopped, + * this will reset it.

* *

If the animation starts, this will call * {@link Animatable2.AnimationCallback#onAnimationStart}.

diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp index 264b95e0e6d..5356d3bfc7c 100644 --- a/libs/hwui/hwui/AnimatedImageDrawable.cpp +++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp @@ -41,6 +41,9 @@ bool AnimatedImageDrawable::start() { return false; } + // This will trigger a reset. + mFinished = true; + mRunning = true; return true; } -- GitLab From 9d16dff058589e76f135fe7b30fc598bcbe995b6 Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Mon, 29 Jan 2018 18:12:43 -0800 Subject: [PATCH 070/416] Fixed Activity.dump() to lazy load AutofillManager if needed. Bug: N/A Test: manual verification Change-Id: I2ab74caab460e41ed6c08882af3d504d3322a577 --- core/java/android/app/Activity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index fd7ad88b4a2..c55b55f3bd2 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -6340,7 +6340,7 @@ public class Activity extends ContextThemeWrapper mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix); - final AutofillManager afm = mAutofillManager; + final AutofillManager afm = getAutofillManager(); if (afm != null) { afm.dump(prefix, writer); } else { -- GitLab From 36b1c16db5ba650cf24995f7ab7810f92db8e0a0 Mon Sep 17 00:00:00 2001 From: Nathan Harold Date: Mon, 29 Jan 2018 11:36:03 -0800 Subject: [PATCH 071/416] Update Docstring for SmsMessage.getOriginatingAddress There was a public request for clarification on the address format for getOriginatingAddress. I did a little research and have added the answer to the docstring. Bug: 64697463 Test: compilation Change-Id: Icf37af0a5940a6fb7798d7c7cafe7b97683bb689 --- telephony/java/android/telephony/SmsMessage.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index a6dbc06647d..577ea7dfc00 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -18,8 +18,8 @@ package android.telephony; import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; +import android.annotation.Nullable; import android.annotation.StringDef; -import android.app.PendingIntent; import android.content.res.Resources; import android.os.Binder; import android.text.TextUtils; @@ -544,8 +544,16 @@ public class SmsMessage { /** * Returns the originating address (sender) of this SMS message in String - * form or null if unavailable + * form or null if unavailable. + * + *

If the address is a GSM-formatted address, it will be in a format specified by 3GPP + * 23.040 Sec 9.1.2.5. If it is a CDMA address, it will be a format specified by 3GPP2 + * C.S005-D Table 2.7.1.3.2.4-2. The choice of format is carrier-specific, so callers of the + * should be careful to avoid assumptions about the returned content. + * + * @return a String representation of the address; null if unavailable. */ + @Nullable public String getOriginatingAddress() { return mWrappedSmsMessage.getOriginatingAddress(); } -- GitLab From 07f21cf510610f9d468c671f071d58f4a1e5ef79 Mon Sep 17 00:00:00 2001 From: Nathan Harold Date: Mon, 29 Jan 2018 11:36:03 -0800 Subject: [PATCH 072/416] Update Docstring for SmsMessage.getOriginatingAddress There was a public request for clarification on the address format for getOriginatingAddress. I did a little research and have added the answer to the docstring. Bug: 64697463 Test: compilation Merged-In: Icf37af0a5940a6fb7798d7c7cafe7b97683bb689 Change-Id: Icf37af0a5940a6fb7798d7c7cafe7b97683bb689 --- telephony/java/android/telephony/SmsMessage.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index dcdda8685e0..6e083c21cd5 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -16,6 +16,7 @@ package android.telephony; +import android.annotation.Nullable; import android.os.Binder; import android.os.Parcel; import android.content.res.Resources; @@ -534,8 +535,16 @@ public class SmsMessage { /** * Returns the originating address (sender) of this SMS message in String - * form or null if unavailable + * form or null if unavailable. + * + *

If the address is a GSM-formatted address, it will be in a format specified by 3GPP + * 23.040 Sec 9.1.2.5. If it is a CDMA address, it will be a format specified by 3GPP2 + * C.S005-D Table 2.7.1.3.2.4-2. The choice of format is carrier-specific, so callers of the + * should be careful to avoid assumptions about the returned content. + * + * @return a String representation of the address; null if unavailable. */ + @Nullable public String getOriginatingAddress() { return mWrappedSmsMessage.getOriginatingAddress(); } -- GitLab From 6483ea4e0a52a4cdae724344973b4f7b0927b02e Mon Sep 17 00:00:00 2001 From: Tej Singh Date: Thu, 25 Jan 2018 17:45:49 -0800 Subject: [PATCH 073/416] Atom: BootSequenceReported Update to atoms.proto to include boot sequence reported Test: manual Change-Id: Ie6b7021528ef81b95969ae90f130f5f0ad0eb9a5 --- cmds/statsd/src/atoms.proto | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index ba4f48008e7..fae73c2d60f 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -94,6 +94,7 @@ message Atom { LmkStateChanged lmk_state_changed = 54; AppStartMemoryStateCaptured app_start_memory_state_captured = 55; ShutdownSequenceReported shutdown_sequence_reported = 56; + BootSequenceReported boot_sequence_reported = 57; // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15. } @@ -690,6 +691,35 @@ message ShutdownSequenceReported { optional int64 duration_ms = 4; } + +/** + * Logs boot reason and duration. + * + * Logged from: + * system/core/bootstat/bootstat.cpp + */ +message BootSequenceReported { + // Reason for bootloader boot. Eg. reboot. See bootstat.cpp for larger list + // Default: "" if not available. + optional string bootloader_reason = 1; + + // Reason for system boot. Eg. bootloader, reboot,userrequested + // Default: "" if not available. + optional string system_reason = 2; + + // End of boot time in ms from unix epoch using system wall clock. + optional int64 end_time_ms = 3; + + // Total boot duration in ms. + optional int64 total_duration_ms = 4; + + // Bootloader duration in ms. + optional int64 bootloader_duration_ms = 5; + + // Time since last boot in ms. Default: 0 if not available. + optional int64 time_since_last_boot = 6; +} + /** * Logs phone signal strength changes. * -- GitLab From fe46402e227780750a6a160b008b94fb62e8e2c3 Mon Sep 17 00:00:00 2001 From: Sungsoo Lim Date: Tue, 30 Jan 2018 11:07:37 +0900 Subject: [PATCH 074/416] Make MediaSession2.CommandGroup updatable Bug: 72665979 Test: build Change-Id: I1ba53c0bd5eb8b72847733693f4975d53e97f2d9 --- media/java/android/media/MediaSession2.java | 96 ++++++------------- .../media/update/MediaSession2Provider.java | 26 +++-- .../android/media/update/StaticProvider.java | 11 ++- 3 files changed, 52 insertions(+), 81 deletions(-) diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 7eebcdcb0af..0ea1e860416 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -30,20 +30,18 @@ import android.media.session.MediaSession.Callback; import android.media.session.PlaybackState; import android.media.update.ApiLoader; import android.media.update.MediaSession2Provider; +import android.media.update.MediaSession2Provider.CommandGroupProvider; import android.media.update.MediaSession2Provider.CommandProvider; import android.media.update.MediaSession2Provider.ControllerInfoProvider; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IInterface; -import android.os.Parcelable; import android.os.ResultReceiver; import android.text.TextUtils; -import android.util.ArraySet; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -156,14 +154,6 @@ public class MediaSession2 implements AutoCloseable { return mProvider.toBundle_impl(); } - /** - * @return a new Command instance from the Bundle - * @hide - */ - public static Command fromBundle(@NonNull Context context, Bundle command) { - return ApiLoader.getProvider(context).fromBundle_MediaSession2Command(context, command); - } - @Override public boolean equals(Object obj) { if (!(obj instanceof Command)) { @@ -176,63 +166,55 @@ public class MediaSession2 implements AutoCloseable { public int hashCode() { return mProvider.hashCode_impl(); } + + /** + * @return a new Command instance from the Bundle + * @hide + */ + public static Command fromBundle(@NonNull Context context, Bundle command) { + return ApiLoader.getProvider(context).fromBundle_MediaSession2Command(context, command); + } } /** * Represent set of {@link Command}. */ - // TODO(jaewan): Move this to updatable public static class CommandGroup { - private static final String KEY_COMMANDS = - "android.media.mediasession2.commandgroup.commands"; - private ArraySet mCommands = new ArraySet<>(); - private final Context mContext; + private final CommandGroupProvider mProvider; public CommandGroup(Context context) { - mContext = context; + mProvider = ApiLoader.getProvider(context) + .createMediaSession2CommandGroup(context, this, null); } public CommandGroup(Context context, CommandGroup others) { - this(context); - mCommands.addAll(others.mCommands); + mProvider = ApiLoader.getProvider(context) + .createMediaSession2CommandGroup(context, this, others); } public void addCommand(Command command) { - mCommands.add(command); + mProvider.addCommand_impl(command); } public void addAllPredefinedCommands() { - // TODO(jaewan): Is there any better way than this? - mCommands.add(new Command(mContext, COMMAND_CODE_PLAYBACK_START)); - mCommands.add(new Command(mContext, COMMAND_CODE_PLAYBACK_PAUSE)); - mCommands.add(new Command(mContext, COMMAND_CODE_PLAYBACK_STOP)); - mCommands.add(new Command(mContext, COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM)); - mCommands.add(new Command(mContext, COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM)); - mCommands.add(new Command(mContext, COMMAND_CODE_PLAYBACK_PREPARE)); - mCommands.add(new Command(mContext, COMMAND_CODE_PLAYBACK_FAST_FORWARD)); - mCommands.add(new Command(mContext, COMMAND_CODE_PLAYBACK_REWIND)); - mCommands.add(new Command(mContext, COMMAND_CODE_PLAYBACK_SEEK_TO)); - mCommands.add(new Command(mContext, COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM)); + mProvider.addAllPredefinedCommands_impl(); } public void removeCommand(Command command) { - mCommands.remove(command); + mProvider.removeCommand_impl(command); } public boolean hasCommand(Command command) { - return mCommands.contains(command); + return mProvider.hasCommand_impl(command); } public boolean hasCommand(int code) { - if (code == COMMAND_CODE_CUSTOM) { - throw new IllegalArgumentException("Use hasCommand(Command) for custom command"); - } - for (int i = 0; i < mCommands.size(); i++) { - if (mCommands.valueAt(i).getCommandCode() == code) { - return true; - } - } - return false; + return mProvider.hasCommand_impl(code); + } + + @SystemApi + public CommandGroupProvider getProvider() { + return mProvider; } /** @@ -240,13 +222,7 @@ public class MediaSession2 implements AutoCloseable { * @hide */ public Bundle toBundle() { - ArrayList list = new ArrayList<>(); - for (int i = 0; i < mCommands.size(); i++) { - list.add(mCommands.valueAt(i).toBundle()); - } - Bundle bundle = new Bundle(); - bundle.putParcelableArrayList(KEY_COMMANDS, list); - return bundle; + return mProvider.toBundle_impl(); } /** @@ -254,26 +230,8 @@ public class MediaSession2 implements AutoCloseable { * @hide */ public static @Nullable CommandGroup fromBundle(Context context, Bundle commands) { - if (commands == null) { - return null; - } - List list = commands.getParcelableArrayList(KEY_COMMANDS); - if (list == null) { - return null; - } - CommandGroup commandGroup = new CommandGroup(context); - for (int i = 0; i < list.size(); i++) { - Parcelable parcelable = list.get(i); - if (!(parcelable instanceof Bundle)) { - continue; - } - Bundle commandBundle = (Bundle) parcelable; - Command command = Command.fromBundle(context, commandBundle); - if (command != null) { - commandGroup.addCommand(command); - } - } - return commandGroup; + return ApiLoader.getProvider(context) + .fromBundle_MediaSession2CommandGroup(context, commands); } } diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java index c0c0d80c01b..f9e29d96f74 100644 --- a/media/java/android/media/update/MediaSession2Provider.java +++ b/media/java/android/media/update/MediaSession2Provider.java @@ -16,7 +16,6 @@ package android.media.update; -import android.media.AudioAttributes; import android.media.MediaItem2; import android.media.MediaPlayerInterface; import android.media.MediaPlayerInterface.PlaybackListener; @@ -62,14 +61,6 @@ public interface MediaSession2Provider extends TransportControlProvider { void addPlaybackListener_impl(Executor executor, PlaybackListener listener); void removePlaybackListener_impl(PlaybackListener listener); - interface ControllerInfoProvider { - String getPackageName_impl(); - int getUid_impl(); - boolean isTrusted_impl(); - int hashCode_impl(); - boolean equals_impl(ControllerInfoProvider obj); - } - interface CommandProvider { int getCommandCode_impl(); String getCustomCommand_impl(); @@ -79,4 +70,21 @@ public interface MediaSession2Provider extends TransportControlProvider { boolean equals_impl(Object ob); int hashCode_impl(); } + + interface CommandGroupProvider { + void addCommand_impl(Command command); + void addAllPredefinedCommands_impl(); + void removeCommand_impl(Command command); + boolean hasCommand_impl(Command command); + boolean hasCommand_impl(int code); + Bundle toBundle_impl(); + } + + interface ControllerInfoProvider { + String getPackageName_impl(); + int getUid_impl(); + boolean isTrusted_impl(); + int hashCode_impl(); + boolean equals_impl(ControllerInfoProvider obj); + } } diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index ef8c9a25c9a..9648b272afa 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -37,6 +37,7 @@ import android.media.SessionPlayer2; import android.media.SessionToken2; import android.media.VolumeProvider; import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider; +import android.media.update.MediaSession2Provider.CommandGroupProvider; import android.media.update.MediaSession2Provider.CommandProvider; import android.media.update.MediaSession2Provider.ControllerInfoProvider; import android.os.Bundle; @@ -64,12 +65,16 @@ public interface StaticProvider { MediaSession2Provider createMediaSession2(Context context, MediaSession2 instance, MediaPlayerInterface player, String id, VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity, Executor executor, SessionCallback callback); - ControllerInfoProvider createMediaSession2ControllerInfoProvider(Context context, - MediaSession2.ControllerInfo instance, int uid, int pid, - String packageName, IInterface callback); CommandProvider createMediaSession2Command(MediaSession2.Command instance, int commandCode, String action, Bundle extra); MediaSession2.Command fromBundle_MediaSession2Command(Context context, Bundle bundle); + CommandGroupProvider createMediaSession2CommandGroup(Context context, + MediaSession2.CommandGroup instance, MediaSession2.CommandGroup others); + MediaSession2.CommandGroup fromBundle_MediaSession2CommandGroup(Context context, Bundle bundle); + ControllerInfoProvider createMediaSession2ControllerInfoProvider(Context context, + MediaSession2.ControllerInfo instance, int uid, int pid, + String packageName, IInterface callback); + MediaController2Provider createMediaController2(Context context, MediaController2 instance, SessionToken2 token, Executor executor, ControllerCallback callback); -- GitLab From 3ec5cc792e932dc668bf9fb2cf5e6c6288a7f9b4 Mon Sep 17 00:00:00 2001 From: Yi Jin Date: Fri, 26 Jan 2018 13:42:43 -0800 Subject: [PATCH 075/416] Modify SystemApi so it can be used by CTS to trigger incident report Bug: 72502621 Test: Cts/Gts tests covered, see the cls from the same topic Change-Id: Id0c1cc0fc0054e620de1349dab66513e554b1caa --- cmds/incidentd/src/Privacy.cpp | 7 +++- cmds/incidentd/src/Privacy.h | 11 ++--- cmds/incidentd/src/Reporter.cpp | 12 +++++- cmds/incidentd/src/Reporter.h | 3 ++ cmds/incidentd/src/Section.cpp | 19 +++++---- cmds/incidentd/tests/PrivacyBuffer_test.cpp | 5 ++- core/java/android/os/IncidentManager.java | 42 ++++++++++++++++---- core/java/android/os/IncidentReportArgs.java | 13 +++++- core/proto/android/os/incident.proto | 1 - 9 files changed, 87 insertions(+), 26 deletions(-) diff --git a/cmds/incidentd/src/Privacy.cpp b/cmds/incidentd/src/Privacy.cpp index 44adaecfe97..3f0e331c8b5 100644 --- a/cmds/incidentd/src/Privacy.cpp +++ b/cmds/incidentd/src/Privacy.cpp @@ -65,7 +65,7 @@ PrivacySpec::CheckPremission(const Privacy* privacy, const uint8_t defaultDest) bool PrivacySpec::RequireAll() const { return dest == android::os::DEST_LOCAL; } -PrivacySpec new_spec_from_args(int dest) +PrivacySpec PrivacySpec::new_spec(int dest) { switch (dest) { case android::os::DEST_AUTOMATIC: @@ -77,4 +77,7 @@ PrivacySpec new_spec_from_args(int dest) } } -PrivacySpec get_default_dropbox_spec() { return PrivacySpec(android::os::DEST_AUTOMATIC); } \ No newline at end of file +PrivacySpec PrivacySpec::get_default_dropbox_spec() +{ + return PrivacySpec(android::os::DEST_AUTOMATIC); +} diff --git a/cmds/incidentd/src/Privacy.h b/cmds/incidentd/src/Privacy.h index 9e15ff43be0..4f3db678f76 100644 --- a/cmds/incidentd/src/Privacy.h +++ b/cmds/incidentd/src/Privacy.h @@ -65,8 +65,6 @@ public: const uint8_t dest; PrivacySpec() : dest(DEST_DEFAULT_VALUE) {} - PrivacySpec(uint8_t dest) : dest(dest) {} - bool operator<(const PrivacySpec& other) const; // check permission of a policy, if returns true, don't strip the data. @@ -74,9 +72,12 @@ public: // if returns true, no data need to be stripped. bool RequireAll() const; -}; -PrivacySpec new_spec_from_args(int dest); -PrivacySpec get_default_dropbox_spec(); + // Constructs spec using static methods below. + static PrivacySpec new_spec(int dest); + static PrivacySpec get_default_dropbox_spec(); +private: + PrivacySpec(uint8_t dest) : dest(dest) {} +}; #endif // PRIVACY_H diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index bd559d6980f..b9f479bd683 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -64,7 +64,8 @@ ReportRequest::ok() ReportRequestSet::ReportRequestSet() :mRequests(), mSections(), - mMainFd(-1) + mMainFd(-1), + mMainDest(-1) { } @@ -86,6 +87,12 @@ ReportRequestSet::setMainFd(int fd) mMainFd = fd; } +void +ReportRequestSet::setMainDest(int dest) +{ + mMainDest = dest; +} + bool ReportRequestSet::containsSection(int id) { return mSections.containsSection(id); @@ -125,12 +132,14 @@ Reporter::runReport() status_t err = NO_ERROR; bool needMainFd = false; int mainFd = -1; + int mainDest = -1; HeaderSection headers; // See if we need the main file for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) { if ((*it)->fd < 0 && mainFd < 0) { needMainFd = true; + mainDest = (*it)->args.dest(); break; } } @@ -154,6 +163,7 @@ Reporter::runReport() // Add to the set batch.setMainFd(mainFd); + batch.setMainDest(mainDest); } // Tell everyone that we're starting. diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h index 2615c6202d3..f30ecf0dd64 100644 --- a/cmds/incidentd/src/Reporter.h +++ b/cmds/incidentd/src/Reporter.h @@ -53,6 +53,7 @@ public: void add(const sp& request); void setMainFd(int fd); + void setMainDest(int dest); typedef vector>::iterator iterator; @@ -61,10 +62,12 @@ public: int mainFd() { return mMainFd; } bool containsSection(int id); + int mainDest() { return mMainDest; } private: vector> mRequests; IncidentReportArgs mSections; int mMainFd; + int mMainDest; }; // ================================================================================ diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index 0827785811b..faeab87f817 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -152,36 +152,40 @@ write_report_requests(const int id, const FdBuffer& buffer, ReportRequestSet* re // The streaming ones, group requests by spec in order to save unnecessary strip operations map>> requestsBySpec; - for (ReportRequestSet::iterator it = requests->begin(); it != requests->end(); it++) { + for (auto it = requests->begin(); it != requests->end(); it++) { sp request = *it; if (!request->ok() || !request->args.containsSection(id)) { continue; // skip invalid request } - PrivacySpec spec = new_spec_from_args(request->args.dest()); + PrivacySpec spec = PrivacySpec::new_spec(request->args.dest()); requestsBySpec[spec].push_back(request); } - for (map>>::iterator mit = requestsBySpec.begin(); mit != requestsBySpec.end(); mit++) { + for (auto mit = requestsBySpec.begin(); mit != requestsBySpec.end(); mit++) { PrivacySpec spec = mit->first; err = privacyBuffer.strip(spec); if (err != NO_ERROR) return err; // it means the privacyBuffer data is corrupted. if (privacyBuffer.size() == 0) continue; - for (vector>::iterator it = mit->second.begin(); it != mit->second.end(); it++) { + for (auto it = mit->second.begin(); it != mit->second.end(); it++) { sp request = *it; err = write_section_header(request->fd, id, privacyBuffer.size()); if (err != NO_ERROR) { request->err = err; continue; } err = privacyBuffer.flush(request->fd); if (err != NO_ERROR) { request->err = err; continue; } writeable++; - ALOGD("Section %d flushed %zu bytes to fd %d with spec %d", id, privacyBuffer.size(), request->fd, spec.dest); + ALOGD("Section %d flushed %zu bytes to fd %d with spec %d", id, + privacyBuffer.size(), request->fd, spec.dest); } privacyBuffer.clear(); } // The dropbox file if (requests->mainFd() >= 0) { - err = privacyBuffer.strip(get_default_dropbox_spec()); + PrivacySpec spec = requests->mainDest() < 0 ? + PrivacySpec::get_default_dropbox_spec() : + PrivacySpec::new_spec(requests->mainDest()); + err = privacyBuffer.strip(spec); if (err != NO_ERROR) return err; // the buffer data is corrupted. if (privacyBuffer.size() == 0) goto DONE; @@ -190,7 +194,8 @@ write_report_requests(const int id, const FdBuffer& buffer, ReportRequestSet* re err = privacyBuffer.flush(requests->mainFd()); if (err != NO_ERROR) { requests->setMainFd(-1); goto DONE; } writeable++; - ALOGD("Section %d flushed %zu bytes to dropbox %d", id, privacyBuffer.size(), requests->mainFd()); + ALOGD("Section %d flushed %zu bytes to dropbox %d with spec %d", id, + privacyBuffer.size(), requests->mainFd(), spec.dest); } DONE: diff --git a/cmds/incidentd/tests/PrivacyBuffer_test.cpp b/cmds/incidentd/tests/PrivacyBuffer_test.cpp index 32b9e42d270..c7bfe555574 100644 --- a/cmds/incidentd/tests/PrivacyBuffer_test.cpp +++ b/cmds/incidentd/tests/PrivacyBuffer_test.cpp @@ -73,7 +73,7 @@ public: } void assertStrip(uint8_t dest, string expected, Privacy* policy) { - PrivacySpec spec(dest); + PrivacySpec spec = PrivacySpec::new_spec(dest); EncodedBuffer::iterator bufData = buffer.data(); PrivacyBuffer privacyBuf(policy, bufData); ASSERT_EQ(privacyBuf.strip(spec), NO_ERROR); @@ -224,7 +224,8 @@ TEST_F(PrivacyBufferTest, ClearAndStrip) { Privacy* list[] = { create_privacy(1, OTHER_TYPE, DEST_LOCAL), NULL }; EncodedBuffer::iterator bufData = buffer.data(); PrivacyBuffer privacyBuf(create_message_privacy(300, list), bufData); - PrivacySpec spec1(DEST_EXPLICIT), spec2(DEST_LOCAL); + PrivacySpec spec1 = PrivacySpec::new_spec(DEST_EXPLICIT); + PrivacySpec spec2 = PrivacySpec::new_spec(DEST_LOCAL); ASSERT_EQ(privacyBuf.strip(spec1), NO_ERROR); assertBuffer(privacyBuf, STRING_FIELD_0); diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java index 1336c66b713..9b6d6e56407 100644 --- a/core/java/android/os/IncidentManager.java +++ b/core/java/android/os/IncidentManager.java @@ -33,10 +33,12 @@ import android.util.Slog; @TestApi @SystemService(Context.INCIDENT_SERVICE) public class IncidentManager { - private static final String TAG = "incident"; + private static final String TAG = "IncidentManager"; private final Context mContext; + private IIncidentManager mService; + /** * @hide */ @@ -96,19 +98,45 @@ public class IncidentManager { reportIncidentInternal(args); } - private void reportIncidentInternal(IncidentReportArgs args) { - final IIncidentManager service = IIncidentManager.Stub.asInterface( - ServiceManager.getService(Context.INCIDENT_SERVICE)); - if (service == null) { - Slog.e(TAG, "reportIncident can't find incident binder service"); - return; + private class IncidentdDeathRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + synchronized (this) { + mService = null; + } } + } + private void reportIncidentInternal(IncidentReportArgs args) { try { + final IIncidentManager service = getIIncidentManagerLocked(); + if (service == null) { + Slog.e(TAG, "reportIncident can't find incident binder service"); + return; + } service.reportIncident(args); } catch (RemoteException ex) { Slog.e(TAG, "reportIncident failed", ex); } } + + private IIncidentManager getIIncidentManagerLocked() throws RemoteException { + if (mService != null) { + return mService; + } + + synchronized (this) { + if (mService != null) { + return mService; + } + mService = IIncidentManager.Stub.asInterface( + ServiceManager.getService(Context.INCIDENT_SERVICE)); + if (mService != null) { + mService.asBinder().linkToDeath(new IncidentdDeathRecipient(), 0); + } + return mService; + } + } + } diff --git a/core/java/android/os/IncidentReportArgs.java b/core/java/android/os/IncidentReportArgs.java index fd0ebcfea08..9fa129cb98b 100644 --- a/core/java/android/os/IncidentReportArgs.java +++ b/core/java/android/os/IncidentReportArgs.java @@ -32,6 +32,9 @@ import java.util.ArrayList; @TestApi public final class IncidentReportArgs implements Parcelable { + private static final int DEST_EXPLICIT = 100; + private static final int DEST_AUTO = 200; + private final IntArray mSections = new IntArray(); private final ArrayList mHeaders = new ArrayList(); private boolean mAll; @@ -41,6 +44,7 @@ public final class IncidentReportArgs implements Parcelable { * Construct an incident report args with no fields. */ public IncidentReportArgs() { + mDest = DEST_AUTO; } /** @@ -143,7 +147,14 @@ public final class IncidentReportArgs implements Parcelable { * @hide */ public void setPrivacyPolicy(int dest) { - mDest = dest; + switch (dest) { + case DEST_EXPLICIT: + case DEST_AUTO: + mDest = dest; + break; + default: + mDest = DEST_AUTO; + } } /** diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 3ec6f057af8..8a04bf7253e 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -16,7 +16,6 @@ syntax = "proto2"; option java_multiple_files = true; -option java_outer_classname = "IncidentProtoMetadata"; import "frameworks/base/core/proto/android/os/batterytype.proto"; import "frameworks/base/core/proto/android/os/cpufreq.proto"; -- GitLab From 78db3d0b0fd4bcae0ec92913e2b39491bffa409b Mon Sep 17 00:00:00 2001 From: yoshiki iguchi Date: Wed, 10 Jan 2018 20:31:17 +0900 Subject: [PATCH 076/416] Split HeadsUpManager implementation to HeadsUpManagerPhone This CL splits HeadsUpManager with the basic functionality and the phone (and car) related implementation. The former code leaves in HeadsUpManager class, and the later code is moved to separated HeadsUpManagerPhone class. This contains the following minor changes: - Move the utility static methods to HeadsUpUtil class. - Chanege the return types of HeadsUpManager#getAllEntries() and HeadsUpManager#getTopEntry() from Collection to Stream. - Add a private method: HeadsUpManagerPhone#getTopHeadsUpEntry() - Make the mPluse propertes boolean instead of Collection in AmbientState and NotificationStackScrollLayout classes. - Unify removeAllHeadsUpEntries() and releaseAllImmediately(), since they do same thing. - Move getTopHeadsUpPinnedHeight method from HeadsUpManager to NotificationStackScrollLayout class, since only this class uses it. - Add a simple test. Bug: 63874929 Bug: 62602530 Test: Compile and ran "runtest systemui" Change-Id: I3e5160803b9cdf1a164339557528ace0d35f8187 --- .../systemui/statusbar/car/CarStatusBar.java | 2 +- .../statusbar/phone/HeadsUpManagerPhone.java | 455 ++++++++++++++ .../statusbar/phone/HeadsUpTouchHelper.java | 6 +- .../phone/NotificationPanelView.java | 10 +- .../systemui/statusbar/phone/PanelView.java | 6 +- .../systemui/statusbar/phone/StatusBar.java | 35 +- .../statusbar/policy/HeadsUpManager.java | 566 ++++++------------ .../statusbar/policy/HeadsUpUtil.java | 47 ++ .../statusbar/stack/AmbientState.java | 15 +- .../stack/NotificationStackScrollLayout.java | 48 +- .../systemui/statusbar/stack/ViewState.java | 4 +- .../statusbar/NotificationTestHelper.java | 3 +- .../phone/HeadsUpManagerPhoneTest.java | 126 ++++ .../statusbar/phone/StatusBarTest.java | 8 +- 14 files changed, 882 insertions(+), 449 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 3ebeb4d45c2..3dfb9130af2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -379,7 +379,7 @@ public class CarStatusBar extends StatusBar implements // Because space is usually constrained in the auto use-case, there should not be a // pinned notification when the shade has been expanded. Ensure this by removing all heads- // up notifications. - mHeadsUpManager.removeAllHeadsUpEntries(); + mHeadsUpManager.releaseAllImmediately(); super.animateExpandNotificationsPanel(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java new file mode 100644 index 00000000000..c45c5386eda --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.support.v4.util.ArraySet; +import android.util.Log; +import android.util.Pools; +import android.view.View; +import android.view.ViewTreeObserver; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Dumpable; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashSet; +import java.util.Stack; + +/** + * A implementation of HeadsUpManager for phone and car. + */ +public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, + ViewTreeObserver.OnComputeInternalInsetsListener, VisualStabilityManager.Callback, + OnHeadsUpChangedListener { + private static final String TAG = "HeadsUpManagerPhone"; + private static final boolean DEBUG = false; + + private final View mStatusBarWindowView; + private final int mStatusBarHeight; + private final NotificationGroupManager mGroupManager; + private final StatusBar mBar; + private final VisualStabilityManager mVisualStabilityManager; + + private boolean mReleaseOnExpandFinish; + private boolean mTrackingHeadsUp; + private HashSet mSwipedOutKeys = new HashSet<>(); + private HashSet mEntriesToRemoveAfterExpand = new HashSet<>(); + private ArraySet mEntriesToRemoveWhenReorderingAllowed + = new ArraySet<>(); + private boolean mIsExpanded; + private int[] mTmpTwoArray = new int[2]; + private boolean mHeadsUpGoingAway; + private boolean mWaitingOnCollapseWhenGoingAway; + private boolean mIsObserving; + private int mStatusBarState; + + private final Pools.Pool mEntryPool = new Pools.Pool() { + private Stack mPoolObjects = new Stack<>(); + + @Override + public HeadsUpEntryPhone acquire() { + if (!mPoolObjects.isEmpty()) { + return mPoolObjects.pop(); + } + return new HeadsUpEntryPhone(); + } + + @Override + public boolean release(@NonNull HeadsUpEntryPhone instance) { + instance.reset(); + mPoolObjects.push(instance); + return true; + } + }; + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Constructor: + + public HeadsUpManagerPhone(@NonNull final Context context, @NonNull View statusBarWindowView, + @NonNull NotificationGroupManager groupManager, @NonNull StatusBar bar, + @NonNull VisualStabilityManager visualStabilityManager) { + super(context); + + mStatusBarWindowView = statusBarWindowView; + mGroupManager = groupManager; + mBar = bar; + mVisualStabilityManager = visualStabilityManager; + + Resources resources = mContext.getResources(); + mStatusBarHeight = resources.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + + addListener(new OnHeadsUpChangedListener() { + @Override + public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) { + if (DEBUG) Log.w(TAG, "onHeadsUpPinnedModeChanged"); + updateTouchableRegionListener(); + } + }); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Public methods: + + /** + * Decides whether a click is invalid for a notification, i.e it has not been shown long enough + * that a user might have consciously clicked on it. + * + * @param key the key of the touched notification + * @return whether the touch is invalid and should be discarded + */ + public boolean shouldSwallowClick(@NonNull String key) { + HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key); + if (entry != null && mClock.currentTimeMillis() < entry.postTime) { + return true; + } + return false; + } + + public void onExpandingFinished() { + if (mReleaseOnExpandFinish) { + releaseAllImmediately(); + mReleaseOnExpandFinish = false; + } else { + for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { + if (isHeadsUp(entry.key)) { + // Maybe the heads-up was removed already + removeHeadsUpEntry(entry); + } + } + } + mEntriesToRemoveAfterExpand.clear(); + } + + /** + * Sets the tracking-heads-up flag. If the flag is true, HeadsUpManager doesn't remove the entry + * from the list even after a Heads Up Notification is gone. + */ + public void setTrackingHeadsUp(boolean trackingHeadsUp) { + mTrackingHeadsUp = trackingHeadsUp; + } + + /** + * Notify that the status bar panel gets expanded or collapsed. + * + * @param isExpanded True to notify expanded, false to notify collapsed. + */ + public void setIsPanelExpanded(boolean isExpanded) { + if (isExpanded != mIsExpanded) { + mIsExpanded = isExpanded; + if (isExpanded) { + // make sure our state is sane + mWaitingOnCollapseWhenGoingAway = false; + mHeadsUpGoingAway = false; + updateTouchableRegionListener(); + } + } + } + + /** + * Set the current state of the statusbar. + */ + public void setStatusBarState(int statusBarState) { + mStatusBarState = statusBarState; + } + + /** + * Set that we are exiting the headsUp pinned mode, but some notifications might still be + * animating out. This is used to keep the touchable regions in a sane state. + */ + public void setHeadsUpGoingAway(boolean headsUpGoingAway) { + if (headsUpGoingAway != mHeadsUpGoingAway) { + mHeadsUpGoingAway = headsUpGoingAway; + if (!headsUpGoingAway) { + waitForStatusBarLayout(); + } + updateTouchableRegionListener(); + } + } + + /** + * Notifies that a remote input textbox in notification gets active or inactive. + * @param entry The entry of the target notification. + * @param remoteInputActive True to notify active, False to notify inactive. + */ + public void setRemoteInputActive( + @NonNull NotificationData.Entry entry, boolean remoteInputActive) { + HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(entry.key); + if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) { + headsUpEntry.remoteInputActive = remoteInputActive; + if (remoteInputActive) { + headsUpEntry.removeAutoRemovalCallbacks(); + } else { + headsUpEntry.updateEntry(false /* updatePostTime */); + } + } + } + + @VisibleForTesting + public void removeMinimumDisplayTimeForTesting() { + mMinimumDisplayTime = 1; + mHeadsUpNotificationDecay = 1; + mTouchAcceptanceDelay = 1; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // HeadsUpManager public methods overrides: + + @Override + public boolean isTrackingHeadsUp() { + return mTrackingHeadsUp; + } + + @Override + public void snooze() { + super.snooze(); + mReleaseOnExpandFinish = true; + } + + /** + * React to the removal of the notification in the heads up. + * + * @return true if the notification was removed and false if it still needs to be kept around + * for a bit since it wasn't shown long enough + */ + @Override + public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) { + if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) { + return super.removeNotification(key, ignoreEarliestRemovalTime); + } else { + HeadsUpEntryPhone entry = getHeadsUpEntryPhone(key); + entry.removeAsSoonAsPossible(); + return false; + } + } + + public void addSwipedOutNotification(@NonNull String key) { + mSwipedOutKeys.add(key); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Dumpable overrides: + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("HeadsUpManagerPhone state:"); + dumpInternal(fd, pw, args); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // ViewTreeObserver.OnComputeInternalInsetsListener overrides: + + /** + * Overridden from TreeObserver. + */ + @Override + public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { + if (mIsExpanded || mBar.isBouncerShowing()) { + // The touchable region is always the full area when expanded + return; + } + if (hasPinnedHeadsUp()) { + ExpandableNotificationRow topEntry = getTopEntry().row; + if (topEntry.isChildInGroup()) { + final ExpandableNotificationRow groupSummary + = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification()); + if (groupSummary != null) { + topEntry = groupSummary; + } + } + topEntry.getLocationOnScreen(mTmpTwoArray); + int minX = mTmpTwoArray[0]; + int maxX = mTmpTwoArray[0] + topEntry.getWidth(); + int maxY = topEntry.getIntrinsicHeight(); + + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(minX, 0, maxX, maxY); + } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { + info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // VisualStabilityManager.Callback overrides: + + @Override + public void onReorderingAllowed() { + mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false); + for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { + if (isHeadsUp(entry.key)) { + // Maybe the heads-up was removed already + removeHeadsUpEntry(entry); + } + } + mEntriesToRemoveWhenReorderingAllowed.clear(); + mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // HeadsUpManager utility (protected) methods overrides: + + @Override + protected HeadsUpEntry createHeadsUpEntry() { + return mEntryPool.acquire(); + } + + @Override + protected void releaseHeadsUpEntry(HeadsUpEntry entry) { + mEntryPool.release((HeadsUpEntryPhone) entry); + } + + @Override + protected boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) { + return mStatusBarState != StatusBarState.KEYGUARD && !mIsExpanded + || super.shouldHeadsUpBecomePinned(entry); + } + + @Override + protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) { + super.dumpInternal(fd, pw, args); + pw.print(" mStatusBarState="); pw.println(mStatusBarState); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Protected utility methods: + + @Nullable + protected HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) { + return (HeadsUpEntryPhone) getHeadsUpEntry(key); + } + + @Nullable + protected HeadsUpEntryPhone getTopHeadsUpEntryPhone() { + return (HeadsUpEntryPhone) getTopHeadsUpEntry(); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Private utility methods: + + private boolean wasShownLongEnough(@NonNull String key) { + if (mSwipedOutKeys.contains(key)) { + // We always instantly dismiss views being manually swiped out. + mSwipedOutKeys.remove(key); + return true; + } + + HeadsUpEntryPhone headsUpEntry = getHeadsUpEntryPhone(key); + HeadsUpEntryPhone topEntry = getTopHeadsUpEntryPhone(); + if (headsUpEntry != topEntry) { + return true; + } + return headsUpEntry.wasShownLongEnough(); + } + + /** + * We need to wait on the whole panel to collapse, before we can remove the touchable region + * listener. + */ + private void waitForStatusBarLayout() { + mWaitingOnCollapseWhenGoingAway = true; + mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, + int oldTop, int oldRight, int oldBottom) { + if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) { + mStatusBarWindowView.removeOnLayoutChangeListener(this); + mWaitingOnCollapseWhenGoingAway = false; + updateTouchableRegionListener(); + } + } + }); + } + + private void updateTouchableRegionListener() { + boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway + || mWaitingOnCollapseWhenGoingAway; + if (shouldObserve == mIsObserving) { + return; + } + if (shouldObserve) { + mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); + mStatusBarWindowView.requestLayout(); + } else { + mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); + } + mIsObserving = shouldObserve; + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // HeadsUpEntryPhone: + + protected class HeadsUpEntryPhone extends HeadsUpManager.HeadsUpEntry { + public void setEntry(@NonNull final NotificationData.Entry entry) { + Runnable removeHeadsUpRunnable = () -> { + if (!mVisualStabilityManager.isReorderingAllowed()) { + mEntriesToRemoveWhenReorderingAllowed.add(entry); + mVisualStabilityManager.addReorderingAllowedCallback( + HeadsUpManagerPhone.this); + } else if (!mTrackingHeadsUp) { + removeHeadsUpEntry(entry); + } else { + mEntriesToRemoveAfterExpand.add(entry); + } + }; + + super.setEntry(entry, removeHeadsUpRunnable); + } + + public boolean wasShownLongEnough() { + return earliestRemovaltime < mClock.currentTimeMillis(); + } + + @Override + public void updateEntry(boolean updatePostTime) { + super.updateEntry(updatePostTime); + + if (mEntriesToRemoveAfterExpand.contains(entry)) { + mEntriesToRemoveAfterExpand.remove(entry); + } + if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) { + mEntriesToRemoveWhenReorderingAllowed.remove(entry); + } + } + + @Override + public void expanded(boolean expanded) { + if (this.expanded == expanded) { + return; + } + + this.expanded = expanded; + if (expanded) { + removeAutoRemovalCallbacks(); + } else { + updateEntry(false /* updatePostTime */); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index c85571c1895..2bfdefe3901 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -23,7 +23,7 @@ import android.view.ViewConfiguration; import com.android.systemui.Gefingerpoken; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.ExpandableView; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; /** @@ -31,7 +31,7 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; */ public class HeadsUpTouchHelper implements Gefingerpoken { - private HeadsUpManager mHeadsUpManager; + private HeadsUpManagerPhone mHeadsUpManager; private NotificationStackScrollLayout mStackScroller; private int mTrackingPointer; private float mTouchSlop; @@ -43,7 +43,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { private NotificationPanelView mPanel; private ExpandableNotificationRow mPickedChild; - public HeadsUpTouchHelper(HeadsUpManager headsUpManager, + public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager, NotificationStackScrollLayout stackScroller, NotificationPanelView notificationPanelView) { mHeadsUpManager = headsUpManager; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 31b8159d3cd..2111d2ef5d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -68,7 +68,7 @@ import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.NotificationUtils; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; @@ -1571,7 +1571,7 @@ public class NotificationPanelView extends PanelView implements private void updatePanelExpanded() { boolean isExpanded = !isFullyCollapsed(); if (mPanelExpanded != isExpanded) { - mHeadsUpManager.setIsExpanded(isExpanded); + mHeadsUpManager.setIsPanelExpanded(isExpanded); mStatusBar.setPanelExpanded(isExpanded); mPanelExpanded = isExpanded; } @@ -2338,7 +2338,7 @@ public class NotificationPanelView extends PanelView implements } @Override - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { super.setHeadsUpManager(headsUpManager); mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller, this); @@ -2630,8 +2630,8 @@ public class NotificationPanelView extends PanelView implements } } - public void setPulsing(Collection pulsing) { - mKeyguardStatusView.setPulsing(pulsing != null); + public void setPulsing(boolean pulsing) { + mKeyguardStatusView.setPulsing(pulsing); positionClockAndNotifications(); mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1] + mKeyguardStatusView.getClockBottom()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 2b7e4747a83..6daabede7f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -50,7 +50,7 @@ import com.android.systemui.classifier.FalsingManager; import com.android.systemui.doze.DozeLog; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -75,7 +75,7 @@ public abstract class PanelView extends FrameLayout { } protected StatusBar mStatusBar; - protected HeadsUpManager mHeadsUpManager; + protected HeadsUpManagerPhone mHeadsUpManager; private float mPeekHeight; private float mHintDistance; @@ -1252,7 +1252,7 @@ public abstract class PanelView extends FrameLayout { */ protected abstract int getClearAllHeight(); - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 426268ba490..116e3f93bf8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -208,6 +208,7 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -219,6 +220,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; @@ -802,15 +804,14 @@ public class StatusBar extends SystemUI implements DemoMode, .commit(); mIconController = Dependency.get(StatusBarIconController.class); - mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow, mGroupManager); - mHeadsUpManager.setBar(this); + mHeadsUpManager = new HeadsUpManagerPhone(context, mStatusBarWindow, mGroupManager, this, + mVisualStabilityManager); mHeadsUpManager.addListener(this); mHeadsUpManager.addListener(mNotificationPanel); mHeadsUpManager.addListener(mGroupManager); mHeadsUpManager.addListener(mVisualStabilityManager); mNotificationPanel.setHeadsUpManager(mHeadsUpManager); mGroupManager.setHeadsUpManager(mHeadsUpManager); - mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager); putComponent(HeadsUpManager.class, mHeadsUpManager); mEntryManager.setUpWithPresenter(this, mStackScroller, this, mHeadsUpManager); @@ -1341,7 +1342,8 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPerformRemoveNotification(StatusBarNotification n) { - if (mStackScroller.hasPulsingNotifications() && mHeadsUpManager.getAllEntries().isEmpty()) { + if (mStackScroller.hasPulsingNotifications() && + !mHeadsUpManager.hasHeadsUpNotifications()) { // We were showing a pulse for a notification, but no notifications are pulsing anymore. // Finish the pulse. mDozeScrimController.pulseOutNow(); @@ -2090,9 +2092,8 @@ public class StatusBar extends SystemUI implements DemoMode, } public void maybeEscalateHeadsUp() { - Collection entries = mHeadsUpManager.getAllEntries(); - for (HeadsUpManager.HeadsUpEntry entry : entries) { - final StatusBarNotification sbn = entry.entry.notification; + mHeadsUpManager.getAllEntries().forEach(entry -> { + final StatusBarNotification sbn = entry.notification; final Notification notification = sbn.getNotification(); if (notification.fullScreenIntent != null) { if (DEBUG) { @@ -2102,11 +2103,11 @@ public class StatusBar extends SystemUI implements DemoMode, EventLog.writeEvent(EventLogTags.SYSUI_HEADS_UP_ESCALATION, sbn.getKey()); notification.fullScreenIntent.send(); - entry.entry.notifyFullScreenIntentLaunched(); + entry.notifyFullScreenIntentLaunched(); } catch (PendingIntent.CanceledException e) { } } - } + }); mHeadsUpManager.releaseAllImmediately(); } @@ -4636,24 +4637,22 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void onPulseStarted() { callback.onPulseStarted(); - Collection pulsingEntries = - mHeadsUpManager.getAllEntries(); - if (!pulsingEntries.isEmpty()) { + if (mHeadsUpManager.hasHeadsUpNotifications()) { // Only pulse the stack scroller if there's actually something to show. // Otherwise just show the always-on screen. - setPulsing(pulsingEntries); + setPulsing(true); } } @Override public void onPulseFinished() { callback.onPulseFinished(); - setPulsing(null); + setPulsing(false); } - private void setPulsing(Collection pulsing) { + private void setPulsing(boolean pulsing) { mNotificationPanel.setPulsing(pulsing); - mVisualStabilityManager.setPulsing(pulsing != null); + mVisualStabilityManager.setPulsing(pulsing); mIgnoreTouchWhilePulsing = false; } }, reason); @@ -4801,7 +4800,7 @@ public class StatusBar extends SystemUI implements DemoMode, // for heads up notifications - protected HeadsUpManager mHeadsUpManager; + protected HeadsUpManagerPhone mHeadsUpManager; private AboveShelfObserver mAboveShelfObserver; @@ -4904,7 +4903,7 @@ public class StatusBar extends SystemUI implements DemoMode, // Release the HUN notification to the shade. if (isPresenterFullyCollapsed()) { - HeadsUpManager.setIsClickedNotification(row, true); + HeadsUpUtil.setIsClickedHeadsUpNotification(row, true); } // // In most cases, when FLAG_AUTO_CANCEL is set, the notification will diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 53dfb244c77..a2b896dc015 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -16,118 +16,68 @@ package com.android.systemui.statusbar.policy; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; +import android.os.SystemClock; import android.os.Handler; import android.os.Looper; -import android.os.SystemClock; -import android.provider.Settings; -import android.support.v4.util.ArraySet; import android.util.ArrayMap; +import android.provider.Settings; import android.util.Log; -import android.util.Pools; -import android.view.View; -import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityEvent; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableNotificationRow; import com.android.systemui.statusbar.NotificationData; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.StatusBar; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; +import java.util.Iterator; +import java.util.stream.Stream; import java.util.HashMap; import java.util.HashSet; -import java.util.Stack; /** * A manager which handles heads up notifications which is a special mode where * they simply peek from the top of the screen. */ -public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsListener, - VisualStabilityManager.Callback { +public class HeadsUpManager { private static final String TAG = "HeadsUpManager"; private static final boolean DEBUG = false; private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms"; - private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag; - - private final int mHeadsUpNotificationDecay; - private final int mMinimumDisplayTime; - private final int mTouchAcceptanceDelay; - private final ArrayMap mSnoozedPackages; - private final HashSet mListeners = new HashSet<>(); - private final int mDefaultSnoozeLengthMs; - private final Handler mHandler = new Handler(Looper.getMainLooper()); - private final Pools.Pool mEntryPool = new Pools.Pool() { + protected final Clock mClock = new Clock(); + protected final Context mContext; + protected final HashSet mListeners = new HashSet<>(); + protected final Handler mHandler = new Handler(Looper.getMainLooper()); - private Stack mPoolObjects = new Stack<>(); + protected int mHeadsUpNotificationDecay; + protected int mMinimumDisplayTime; + protected int mTouchAcceptanceDelay; + protected int mSnoozeLengthMs; + protected boolean mHasPinnedNotification; + protected int mUser; - @Override - public HeadsUpEntry acquire() { - if (!mPoolObjects.isEmpty()) { - return mPoolObjects.pop(); - } - return new HeadsUpEntry(); - } + private final HashMap mHeadsUpEntries = new HashMap<>(); + private final ArrayMap mSnoozedPackages; + private final ContentObserver mSettingsObserver; - @Override - public boolean release(HeadsUpEntry instance) { - instance.reset(); - mPoolObjects.push(instance); - return true; - } - }; - - private final View mStatusBarWindowView; - private final int mStatusBarHeight; - private final Context mContext; - private final NotificationGroupManager mGroupManager; - private StatusBar mBar; - private int mSnoozeLengthMs; - private ContentObserver mSettingsObserver; - private HashMap mHeadsUpEntries = new HashMap<>(); - private HashSet mSwipedOutKeys = new HashSet<>(); - private int mUser; - private Clock mClock; - private boolean mReleaseOnExpandFinish; - private boolean mTrackingHeadsUp; - private HashSet mEntriesToRemoveAfterExpand = new HashSet<>(); - private ArraySet mEntriesToRemoveWhenReorderingAllowed - = new ArraySet<>(); - private boolean mIsExpanded; - private boolean mHasPinnedNotification; - private int[] mTmpTwoArray = new int[2]; - private boolean mHeadsUpGoingAway; - private boolean mWaitingOnCollapseWhenGoingAway; - private boolean mIsObserving; - private boolean mRemoteInputActive; - private float mExpandedHeight; - private VisualStabilityManager mVisualStabilityManager; - private int mStatusBarState; - - public HeadsUpManager(final Context context, View statusBarWindowView, - NotificationGroupManager groupManager) { + public HeadsUpManager(@NonNull final Context context) { mContext = context; - Resources resources = mContext.getResources(); - mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay); - mSnoozedPackages = new ArrayMap<>(); - mDefaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms); - mSnoozeLengthMs = mDefaultSnoozeLengthMs; + Resources resources = context.getResources(); mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); mHeadsUpNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay); - mClock = new Clock(); + mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay); + mSnoozedPackages = new ArrayMap<>(); + int defaultSnoozeLengthMs = + resources.getInteger(R.integer.heads_up_default_snooze_length_ms); mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(), - SETTING_HEADS_UP_SNOOZE_LENGTH_MS, mDefaultSnoozeLengthMs); + SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs); mSettingsObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { @@ -142,47 +92,26 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL context.getContentResolver().registerContentObserver( Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false, mSettingsObserver); - mStatusBarWindowView = statusBarWindowView; - mGroupManager = groupManager; - mStatusBarHeight = resources.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height); - } - - private void updateTouchableRegionListener() { - boolean shouldObserve = mHasPinnedNotification || mHeadsUpGoingAway - || mWaitingOnCollapseWhenGoingAway; - if (shouldObserve == mIsObserving) { - return; - } - if (shouldObserve) { - mStatusBarWindowView.getViewTreeObserver().addOnComputeInternalInsetsListener(this); - mStatusBarWindowView.requestLayout(); - } else { - mStatusBarWindowView.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - } - mIsObserving = shouldObserve; } - public void setBar(StatusBar bar) { - mBar = bar; - } - - public void addListener(OnHeadsUpChangedListener listener) { + /** + * Adds an OnHeadUpChangedListener to observe events. + */ + public void addListener(@NonNull OnHeadsUpChangedListener listener) { mListeners.add(listener); } - public void removeListener(OnHeadsUpChangedListener listener) { + /** + * Removes the OnHeadUpChangedListener from the observer list. + */ + public void removeListener(@NonNull OnHeadsUpChangedListener listener) { mListeners.remove(listener); } - public StatusBar getBar() { - return mBar; - } - /** * Called when posting a new notification to the heads up. */ - public void showNotification(NotificationData.Entry headsUp) { + public void showNotification(@NonNull NotificationData.Entry headsUp) { if (DEBUG) Log.v(TAG, "showNotification"); addHeadsUpEntry(headsUp); updateNotification(headsUp, true); @@ -192,7 +121,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL /** * Called when updating or posting a notification to the heads up. */ - public void updateNotification(NotificationData.Entry headsUp, boolean alert) { + public void updateNotification(@NonNull NotificationData.Entry headsUp, boolean alert) { if (DEBUG) Log.v(TAG, "updateNotification"); headsUp.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); @@ -204,14 +133,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL // with the groupmanager return; } - headsUpEntry.updateEntry(); + headsUpEntry.updateEntry(true /* updatePostTime */); setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUp)); } } - private void addHeadsUpEntry(NotificationData.Entry entry) { - HeadsUpEntry headsUpEntry = mEntryPool.acquire(); - + private void addHeadsUpEntry(@NonNull NotificationData.Entry entry) { + HeadsUpEntry headsUpEntry = createHeadsUpEntry(); // This will also add the entry to the sortedList headsUpEntry.setEntry(entry); mHeadsUpEntries.put(entry.key, headsUpEntry); @@ -223,16 +151,17 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); } - private boolean shouldHeadsUpBecomePinned(NotificationData.Entry entry) { - return mStatusBarState != StatusBarState.KEYGUARD - && !mIsExpanded || hasFullScreenIntent(entry); + protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationData.Entry entry) { + return hasFullScreenIntent(entry); } - private boolean hasFullScreenIntent(NotificationData.Entry entry) { + protected boolean hasFullScreenIntent(@NonNull NotificationData.Entry entry) { return entry.notification.getNotification().fullScreenIntent != null; } - private void setEntryPinned(HeadsUpEntry headsUpEntry, boolean isPinned) { + protected void setEntryPinned( + @NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) { + if (DEBUG) Log.v(TAG, "setEntryPinned: " + isPinned); ExpandableNotificationRow row = headsUpEntry.entry.row; if (row.isPinned() != isPinned) { row.setPinned(isPinned); @@ -247,33 +176,35 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } - private void removeHeadsUpEntry(NotificationData.Entry entry) { + protected void removeHeadsUpEntry(NotificationData.Entry entry) { HeadsUpEntry remove = mHeadsUpEntries.remove(entry.key); + onHeadsUpEntryRemoved(remove); + releaseHeadsUpEntry(remove); + } + + protected void onHeadsUpEntryRemoved(HeadsUpEntry remove) { + NotificationData.Entry entry = remove.entry; entry.row.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); entry.row.setHeadsUp(false); setEntryPinned(remove, false /* isPinned */); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpStateChanged(entry, false); } - mEntryPool.release(remove); - } - - public void removeAllHeadsUpEntries() { - for (String key : mHeadsUpEntries.keySet()) { - removeHeadsUpEntry(mHeadsUpEntries.get(key).entry); - } } - private void updatePinnedMode() { + protected void updatePinnedMode() { boolean hasPinnedNotification = hasPinnedNotificationInternal(); if (hasPinnedNotification == mHasPinnedNotification) { return; } + if (DEBUG) { + Log.v(TAG, "Pinned mode changed: " + mHasPinnedNotification + " -> " + + hasPinnedNotification); + } mHasPinnedNotification = hasPinnedNotification; if (mHasPinnedNotification) { MetricsLogger.count(mContext, "note_peek", 1); } - updateTouchableRegionListener(); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); } @@ -285,47 +216,36 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL * @return true if the notification was removed and false if it still needs to be kept around * for a bit since it wasn't shown long enough */ - public boolean removeNotification(String key, boolean ignoreEarliestRemovalTime) { - if (DEBUG) Log.v(TAG, "remove"); - if (wasShownLongEnough(key) || ignoreEarliestRemovalTime) { - releaseImmediately(key); - return true; - } else { - getHeadsUpEntry(key).removeAsSoonAsPossible(); - return false; - } - } - - private boolean wasShownLongEnough(String key) { - HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); - HeadsUpEntry topEntry = getTopEntry(); - if (mSwipedOutKeys.contains(key)) { - // We always instantly dismiss views being manually swiped out. - mSwipedOutKeys.remove(key); - return true; - } - if (headsUpEntry != topEntry) { - return true; - } - return headsUpEntry.wasShownLongEnough(); + public boolean removeNotification(@NonNull String key, boolean ignoreEarliestRemovalTime) { + if (DEBUG) Log.v(TAG, "removeNotification"); + releaseImmediately(key); + return true; } + /** + * Returns if the given notification is in the Heads Up Notification list or not. + */ public boolean isHeadsUp(String key) { return mHeadsUpEntries.containsKey(key); } /** - * Push any current Heads Up notification down into the shade. + * Pushes any current Heads Up notification down into the shade. */ public void releaseAllImmediately() { if (DEBUG) Log.v(TAG, "releaseAllImmediately"); - ArrayList keys = new ArrayList<>(mHeadsUpEntries.keySet()); - for (String key : keys) { - releaseImmediately(key); + Iterator iterator = mHeadsUpEntries.values().iterator(); + while (iterator.hasNext()) { + HeadsUpEntry entry = iterator.next(); + iterator.remove(); + onHeadsUpEntryRemoved(entry); } } - public void releaseImmediately(String key) { + /** + * Pushes the given Heads Up notification down into the shade. + */ + public void releaseImmediately(@NonNull String key) { HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); if (headsUpEntry == null) { return; @@ -334,11 +254,14 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL removeHeadsUpEntry(shadeEntry); } - public boolean isSnoozed(String packageName) { + /** + * Returns if the given notification is snoozed or not. + */ + public boolean isSnoozed(@NonNull String packageName) { final String key = snoozeKey(packageName, mUser); Long snoozedUntil = mSnoozedPackages.get(key); if (snoozedUntil != null) { - if (snoozedUntil > SystemClock.elapsedRealtime()) { + if (snoozedUntil > mClock.currentTimeMillis()) { if (DEBUG) Log.v(TAG, key + " snoozed"); return true; } @@ -347,33 +270,61 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return false; } + /** + * Snoozes all current Heads Up Notifications. + */ public void snooze() { for (String key : mHeadsUpEntries.keySet()) { HeadsUpEntry entry = mHeadsUpEntries.get(key); String packageName = entry.entry.notification.getPackageName(); mSnoozedPackages.put(snoozeKey(packageName, mUser), - SystemClock.elapsedRealtime() + mSnoozeLengthMs); + mClock.currentTimeMillis() + mSnoozeLengthMs); } - mReleaseOnExpandFinish = true; } - private static String snoozeKey(String packageName, int user) { + private static String snoozeKey(@NonNull String packageName, int user) { return user + "," + packageName; } - private HeadsUpEntry getHeadsUpEntry(String key) { + protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) { return mHeadsUpEntries.get(key); } - public NotificationData.Entry getEntry(String key) { - return mHeadsUpEntries.get(key).entry; + /** + * Returns the entry of given Heads Up Notification. + * + * @param key Key of heads up notification + */ + public NotificationData.Entry getEntry(@NonNull String key) { + HeadsUpEntry entry = mHeadsUpEntries.get(key); + return entry != null ? entry.entry : null; + } + + /** + * Returns the stream of all current Heads Up Notifications. + */ + @NonNull + public Stream getAllEntries() { + return mHeadsUpEntries.values().stream().map(headsUpEntry -> headsUpEntry.entry); + } + + /** + * Returns the top Heads Up Notification, which appeares to show at first. + */ + @Nullable + public NotificationData.Entry getTopEntry() { + HeadsUpEntry topEntry = getTopHeadsUpEntry(); + return (topEntry != null) ? topEntry.entry : null; } - public Collection getAllEntries() { - return mHeadsUpEntries.values(); + /** + * Returns if any heads up notification is available or not. + */ + public boolean hasHeadsUpNotifications() { + return !mHeadsUpEntries.isEmpty(); } - public HeadsUpEntry getTopEntry() { + protected HeadsUpEntry getTopHeadsUpEntry() { if (mHeadsUpEntries.isEmpty()) { return null; } @@ -387,56 +338,21 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * Decides whether a click is invalid for a notification, i.e it has not been shown long enough - * that a user might have consciously clicked on it. - * - * @param key the key of the touched notification - * @return whether the touch is invalid and should be discarded + * Sets the current user. */ - public boolean shouldSwallowClick(String key) { - HeadsUpEntry entry = mHeadsUpEntries.get(key); - if (entry != null && mClock.currentTimeMillis() < entry.postTime) { - return true; - } - return false; - } - - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - if (mIsExpanded || mBar.isBouncerShowing()) { - // The touchable region is always the full area when expanded - return; - } - if (mHasPinnedNotification) { - ExpandableNotificationRow topEntry = getTopEntry().entry.row; - if (topEntry.isChildInGroup()) { - final ExpandableNotificationRow groupSummary - = mGroupManager.getGroupSummary(topEntry.getStatusBarNotification()); - if (groupSummary != null) { - topEntry = groupSummary; - } - } - topEntry.getLocationOnScreen(mTmpTwoArray); - int minX = mTmpTwoArray[0]; - int maxX = mTmpTwoArray[0] + topEntry.getWidth(); - int maxY = topEntry.getIntrinsicHeight(); - - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(minX, 0, maxX, maxY); - } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) { - info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight); - } - } - public void setUser(int user) { mUser = user; } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("HeadsUpManager state:"); + dumpInternal(fd, pw, args); + } + + protected void dumpInternal(FileDescriptor fd, PrintWriter pw, String[] args) { pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay); pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs); - pw.print(" now="); pw.println(SystemClock.elapsedRealtime()); + pw.print(" now="); pw.println(mClock.currentTimeMillis()); pw.print(" mUser="); pw.println(mUser); for (HeadsUpEntry entry: mHeadsUpEntries.values()) { pw.print(" HeadsUpEntry="); pw.println(entry.entry); @@ -449,6 +365,9 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } + /** + * Returns if there are any pinned Heads Up Notifications or not. + */ public boolean hasPinnedHeadsUp() { return mHasPinnedNotification; } @@ -464,14 +383,8 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * Notifies that a notification was swiped out and will be removed. - * - * @param key the notification key + * Unpins all pinned Heads Up Notifications. */ - public void addSwipedOutNotification(String key) { - mSwipedOutKeys.add(key); - } - public void unpinAll() { for (String key : mHeadsUpEntries.keySet()) { HeadsUpEntry entry = mHeadsUpEntries.get(key); @@ -481,60 +394,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } } - public void onExpandingFinished() { - if (mReleaseOnExpandFinish) { - releaseAllImmediately(); - mReleaseOnExpandFinish = false; - } else { - for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) { - if (isHeadsUp(entry.key)) { - // Maybe the heads-up was removed already - removeHeadsUpEntry(entry); - } - } - } - mEntriesToRemoveAfterExpand.clear(); - } - - public void setTrackingHeadsUp(boolean trackingHeadsUp) { - mTrackingHeadsUp = trackingHeadsUp; - } - - public boolean isTrackingHeadsUp() { - return mTrackingHeadsUp; - } - - public void setIsExpanded(boolean isExpanded) { - if (isExpanded != mIsExpanded) { - mIsExpanded = isExpanded; - if (isExpanded) { - // make sure our state is sane - mWaitingOnCollapseWhenGoingAway = false; - mHeadsUpGoingAway = false; - updateTouchableRegionListener(); - } - } - } - /** - * @return the height of the top heads up notification when pinned. This is different from the - * intrinsic height, which also includes whether the notification is system expanded and - * is mainly used when dragging down from a heads up notification. + * Returns the value of the tracking-heads-up flag. See the doc of {@code setTrackingHeadsUp} as + * well. */ - public int getTopHeadsUpPinnedHeight() { - HeadsUpEntry topEntry = getTopEntry(); - if (topEntry == null || topEntry.entry == null) { - return 0; - } - ExpandableNotificationRow row = topEntry.entry.row; - if (row.isChildInGroup()) { - final ExpandableNotificationRow groupSummary - = mGroupManager.getGroupSummary(row.getStatusBarNotification()); - if (groupSummary != null) { - row = groupSummary; - } - } - return row.getPinnedHeadsUpHeight(); + public boolean isTrackingHeadsUp() { + // Might be implemented in subclass. + return false; } /** @@ -553,147 +419,67 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } /** - * Set that we are exiting the headsUp pinned mode, but some notifications might still be - * animating out. This is used to keep the touchable regions in a sane state. + * Sets an entry to be expanded and therefore stick in the heads up area if it's pinned + * until it's collapsed again. */ - public void setHeadsUpGoingAway(boolean headsUpGoingAway) { - if (headsUpGoingAway != mHeadsUpGoingAway) { - mHeadsUpGoingAway = headsUpGoingAway; - if (!headsUpGoingAway) { - waitForStatusBarLayout(); - } - updateTouchableRegionListener(); - } - } - - /** - * We need to wait on the whole panel to collapse, before we can remove the touchable region - * listener. - */ - private void waitForStatusBarLayout() { - mWaitingOnCollapseWhenGoingAway = true; - mStatusBarWindowView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, - int oldTop, int oldRight, int oldBottom) { - if (mStatusBarWindowView.getHeight() <= mStatusBarHeight) { - mStatusBarWindowView.removeOnLayoutChangeListener(this); - mWaitingOnCollapseWhenGoingAway = false; - updateTouchableRegionListener(); - } - } - }); - } - - public static void setIsClickedNotification(View child, boolean clicked) { - child.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null); - } - - public static boolean isClickedHeadsUpNotification(View child) { - Boolean clicked = (Boolean) child.getTag(TAG_CLICKED_NOTIFICATION); - return clicked != null && clicked; - } - - public void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive) { - HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); - if (headsUpEntry != null && headsUpEntry.remoteInputActive != remoteInputActive) { - headsUpEntry.remoteInputActive = remoteInputActive; - if (remoteInputActive) { - headsUpEntry.removeAutoRemovalCallbacks(); - } else { - headsUpEntry.updateEntry(false /* updatePostTime */); - } - } - } /** * Set an entry to be expanded and therefore stick in the heads up area if it's pinned * until it's collapsed again. */ - public void setExpanded(NotificationData.Entry entry, boolean expanded) { - HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); - if (headsUpEntry != null && headsUpEntry.expanded != expanded && entry.row.isPinned()) { - headsUpEntry.expanded = expanded; - if (expanded) { - headsUpEntry.removeAutoRemovalCallbacks(); - } else { - headsUpEntry.updateEntry(false /* updatePostTime */); - } - } - } - - @Override - public void onReorderingAllowed() { - mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(false); - for (NotificationData.Entry entry : mEntriesToRemoveWhenReorderingAllowed) { - if (isHeadsUp(entry.key)) { - // Maybe the heads-up was removed already - removeHeadsUpEntry(entry); - } + public void setExpanded(@NonNull NotificationData.Entry entry, boolean expanded) { + HeadsUpManager.HeadsUpEntry headsUpEntry = mHeadsUpEntries.get(entry.key); + if (headsUpEntry != null && entry.row.isPinned()) { + headsUpEntry.expanded(expanded); } - mEntriesToRemoveWhenReorderingAllowed.clear(); - mBar.getNotificationScrollLayout().setHeadsUpGoingAwayAnimationsAllowed(true); } - public void setVisualStabilityManager(VisualStabilityManager visualStabilityManager) { - mVisualStabilityManager = visualStabilityManager; + @NonNull + protected HeadsUpEntry createHeadsUpEntry() { + return new HeadsUpEntry(); } - public void setStatusBarState(int statusBarState) { - mStatusBarState = statusBarState; + protected void releaseHeadsUpEntry(@NonNull HeadsUpEntry entry) { + // Do nothing for HeadsUpEntry. } /** * This represents a notification and how long it is in a heads up mode. It also manages its * lifecycle automatically when created. */ - public class HeadsUpEntry implements Comparable { - public NotificationData.Entry entry; + protected class HeadsUpEntry implements Comparable { + @Nullable public NotificationData.Entry entry; public long postTime; - public long earliestRemovaltime; - private Runnable mRemoveHeadsUpRunnable; public boolean remoteInputActive; + public long earliestRemovaltime; public boolean expanded; - public void setEntry(final NotificationData.Entry entry) { + private Runnable mRemoveHeadsUpRunnable; + + public void setEntry(@Nullable final NotificationData.Entry entry) { + setEntry(entry, null); + } + + public void setEntry(@Nullable final NotificationData.Entry entry, + @Nullable Runnable removeHeadsUpRunnable) { this.entry = entry; + this.mRemoveHeadsUpRunnable = removeHeadsUpRunnable; // The actual post time will be just after the heads-up really slided in postTime = mClock.currentTimeMillis() + mTouchAcceptanceDelay; - mRemoveHeadsUpRunnable = new Runnable() { - @Override - public void run() { - if (!mVisualStabilityManager.isReorderingAllowed()) { - mEntriesToRemoveWhenReorderingAllowed.add(entry); - mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManager.this); - } else if (!mTrackingHeadsUp) { - removeHeadsUpEntry(entry); - } else { - mEntriesToRemoveAfterExpand.add(entry); - } - } - }; - updateEntry(); - } - - public void updateEntry() { - updateEntry(true); + updateEntry(true /* updatePostTime */); } public void updateEntry(boolean updatePostTime) { + if (DEBUG) Log.v(TAG, "updateEntry"); + long currentTime = mClock.currentTimeMillis(); earliestRemovaltime = currentTime + mMinimumDisplayTime; if (updatePostTime) { postTime = Math.max(postTime, currentTime); } removeAutoRemovalCallbacks(); - if (mEntriesToRemoveAfterExpand.contains(entry)) { - mEntriesToRemoveAfterExpand.remove(entry); - } - if (mEntriesToRemoveWhenReorderingAllowed.contains(entry)) { - mEntriesToRemoveWhenReorderingAllowed.remove(entry); - } + if (!isSticky()) { long finishTime = postTime + mHeadsUpNotificationDecay; long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime); @@ -707,7 +493,7 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL } @Override - public int compareTo(HeadsUpEntry o) { + public int compareTo(@NonNull HeadsUpEntry o) { boolean isPinned = entry.row.isPinned(); boolean otherPinned = o.entry.row.isPinned(); if (isPinned && !otherPinned) { @@ -734,26 +520,29 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL : -1; } - public void removeAutoRemovalCallbacks() { - mHandler.removeCallbacks(mRemoveHeadsUpRunnable); - } - - public boolean wasShownLongEnough() { - return earliestRemovaltime < mClock.currentTimeMillis(); - } - - public void removeAsSoonAsPossible() { - removeAutoRemovalCallbacks(); - mHandler.postDelayed(mRemoveHeadsUpRunnable, - earliestRemovaltime - mClock.currentTimeMillis()); + public void expanded(boolean expanded) { + this.expanded = expanded; } public void reset() { - removeAutoRemovalCallbacks(); entry = null; - mRemoveHeadsUpRunnable = null; expanded = false; remoteInputActive = false; + removeAutoRemovalCallbacks(); + mRemoveHeadsUpRunnable = null; + } + + public void removeAutoRemovalCallbacks() { + if (mRemoveHeadsUpRunnable != null) + mHandler.removeCallbacks(mRemoveHeadsUpRunnable); + } + + public void removeAsSoonAsPossible() { + if (mRemoveHeadsUpRunnable != null) { + removeAutoRemovalCallbacks(); + mHandler.postDelayed(mRemoveHeadsUpRunnable, + earliestRemovaltime - mClock.currentTimeMillis()); + } } } @@ -762,5 +551,4 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return SystemClock.elapsedRealtime(); } } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java new file mode 100644 index 00000000000..1e3c123cfbc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpUtil.java @@ -0,0 +1,47 @@ +/* + * 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.systemui.statusbar.policy; + +import android.view.View; + +import com.android.systemui.R; + +/** + * A class of utility static methods for heads up notifications. + */ +public final class HeadsUpUtil { + private static final int TAG_CLICKED_NOTIFICATION = R.id.is_clicked_heads_up_tag; + + /** + * Set the given view as clicked or not-clicked. + * @param view The view to be set the flag to. + * @param clicked True to set as clicked. False to not-clicked. + */ + public static void setIsClickedHeadsUpNotification(View view, boolean clicked) { + view.setTag(TAG_CLICKED_NOTIFICATION, clicked ? true : null); + } + + /** + * Check if the given view has the flag of "clicked notification" + * @param view The view to be checked. + * @return True if the view has clicked. False othrewise. + */ + public static boolean isClickedHeadsUpNotification(View view) { + Boolean clicked = (Boolean) view.getTag(TAG_CLICKED_NOTIFICATION); + return clicked != null && clicked; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java index 424858a86e5..d7a810eca02 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java @@ -64,7 +64,7 @@ public class AmbientState { private boolean mPanelTracking; private boolean mExpansionChanging; private boolean mPanelFullWidth; - private Collection mPulsing; + private boolean mPulsing; private boolean mUnlockHintRunning; private boolean mQsCustomizerShowing; private int mIntrinsicPadding; @@ -315,23 +315,18 @@ public class AmbientState { } public boolean hasPulsingNotifications() { - return mPulsing != null; + return mPulsing; } - public void setPulsing(Collection hasPulsing) { + public void setPulsing(boolean hasPulsing) { mPulsing = hasPulsing; } public boolean isPulsing(NotificationData.Entry entry) { - if (mPulsing == null) { + if (!mPulsing || mHeadsUpManager == null) { return false; } - for (HeadsUpManager.HeadsUpEntry e : mPulsing) { - if (e.entry == entry) { - return true; - } - } - return false; + return mHeadsUpManager.getAllEntries().anyMatch(e -> (e == entry)); } public boolean isPanelTracking() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index ad8a0eb98ea..6d20684ec3f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -92,10 +92,11 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.FakeShadowView; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.HeadsUpUtil; import com.android.systemui.statusbar.policy.ScrollAdapter; import android.support.v4.graphics.ColorUtils; @@ -288,7 +289,7 @@ public class NotificationStackScrollLayout extends ViewGroup private HashSet mClearOverlayViewsWhenFinished = new HashSet<>(); private HashSet> mHeadsUpChangeAnimations = new HashSet<>(); - private HeadsUpManager mHeadsUpManager; + private HeadsUpManagerPhone mHeadsUpManager; private boolean mTrackingHeadsUp; private ScrimController mScrimController; private boolean mForceNoOverlappingRendering; @@ -358,7 +359,7 @@ public class NotificationStackScrollLayout extends ViewGroup } }; private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); - private Collection mPulsing; + private boolean mPulsing; private boolean mDrawBackgroundAsSrc; private boolean mFadingOut; private boolean mParentNotFullyVisible; @@ -689,7 +690,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void updateAlgorithmHeightAndPadding() { - if (mPulsing != null) { + if (mPulsing) { mTopPadding = mClockBottom; } else { mTopPadding = mAmbientState.isDark() ? mDarkTopPadding : mRegularTopPadding; @@ -918,6 +919,27 @@ public class NotificationStackScrollLayout extends ViewGroup return getMinExpansionHeight(); } + /** + * @return the height of the top heads up notification when pinned. This is different from the + * intrinsic height, which also includes whether the notification is system expanded and + * is mainly used when dragging down from a heads up notification. + */ + private int getTopHeadsUpPinnedHeight() { + NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry(); + if (topEntry == null) { + return 0; + } + ExpandableNotificationRow row = topEntry.row; + if (row.isChildInGroup()) { + final ExpandableNotificationRow groupSummary + = mGroupManager.getGroupSummary(row.getStatusBarNotification()); + if (groupSummary != null) { + row = groupSummary; + } + } + return row.getPinnedHeadsUpHeight(); + } + /** * @return the position from where the appear transition ends when expanding. * Measured in absolute height. @@ -929,7 +951,7 @@ public class NotificationStackScrollLayout extends ViewGroup int minNotificationsForShelf = 1; if (mTrackingHeadsUp || (mHeadsUpManager.hasPinnedHeadsUp() && !mAmbientState.isDark())) { - appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight(); + appearPosition = getTopHeadsUpPinnedHeight(); minNotificationsForShelf = 2; } else { appearPosition = 0; @@ -1197,9 +1219,9 @@ public class NotificationStackScrollLayout extends ViewGroup if (slidingChild instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild; if (!mIsExpanded && row.isHeadsUp() && row.isPinned() - && mHeadsUpManager.getTopEntry().entry.row != row + && mHeadsUpManager.getTopEntry().row != row && mGroupManager.getGroupSummary( - mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification()) + mHeadsUpManager.getTopEntry().row.getStatusBarNotification()) != row) { continue; } @@ -2119,7 +2141,7 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public boolean hasPulsingNotifications() { - return mPulsing != null; + return mPulsing; } private void updateScrollability() { @@ -2751,7 +2773,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private boolean isClickedHeadsUp(View child) { - return HeadsUpManager.isClickedHeadsUpNotification(child); + return HeadsUpUtil.isClickedHeadsUpNotification(child); } /** @@ -4256,7 +4278,7 @@ public class NotificationStackScrollLayout extends ViewGroup mAnimationFinishedRunnables.add(runnable); } - public void setHeadsUpManager(HeadsUpManager headsUpManager) { + public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; mAmbientState.setHeadsUpManager(headsUpManager); } @@ -4324,8 +4346,8 @@ public class NotificationStackScrollLayout extends ViewGroup return mIsExpanded; } - public void setPulsing(Collection pulsing, int clockBottom) { - if (mPulsing == null && pulsing == null) { + public void setPulsing(boolean pulsing, int clockBottom) { + if (!mPulsing && !pulsing) { return; } mPulsing = pulsing; @@ -4463,7 +4485,7 @@ public class NotificationStackScrollLayout extends ViewGroup pw.println(String.format("[%s: pulsing=%s qsCustomizerShowing=%s visibility=%s" + " alpha:%f scrollY:%d]", this.getClass().getSimpleName(), - mPulsing != null ?"T":"f", + mPulsing ? "T":"f", mAmbientState.isQsCustomizerShowing() ? "T":"f", getVisibility() == View.VISIBLE ? "visible" : getVisibility() == View.GONE ? "gone" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java index 682b8493e91..04a7bd79c6c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java @@ -30,7 +30,7 @@ import com.android.systemui.R; import com.android.systemui.statusbar.ExpandableView; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; -import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.statusbar.policy.HeadsUpUtil; /** * A state of a view. This can be used to apply a set of view properties to a view with @@ -582,7 +582,7 @@ public class ViewState { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - HeadsUpManager.setIsClickedNotification(child, false); + HeadsUpUtil.setIsClickedHeadsUpNotification(child, false); child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null); child.setTag(TAG_START_TRANSLATION_Y, null); child.setTag(TAG_END_TRANSLATION_Y, null); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java index 6e7477fbac3..f3c1171f650 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.AboveShelfObserver; import com.android.systemui.statusbar.notification.InflationException; import com.android.systemui.statusbar.notification.NotificationInflaterTest; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; @@ -51,7 +52,7 @@ public class NotificationTestHelper { public NotificationTestHelper(Context context) { mContext = context; mInstrumentation = InstrumentationRegistry.getInstrumentation(); - mHeadsUpManager = new HeadsUpManager(mContext, null, mGroupManager); + mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null); } public ExpandableNotificationRow createRow() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java new file mode 100644 index 00000000000..28f94177904 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone; + +import android.app.ActivityManager; +import android.app.Notification; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.view.View; +import android.service.notification.StatusBarNotification; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.ExpandableNotificationRow; +import com.android.systemui.statusbar.NotificationData; +import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.notification.VisualStabilityManager; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.assertFalse; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class HeadsUpManagerPhoneTest extends SysuiTestCase { + @Rule public MockitoRule rule = MockitoJUnit.rule(); + + private static final String TEST_PACKAGE_NAME = "test"; + private static final int TEST_UID = 0; + + private HeadsUpManagerPhone mHeadsUpManager; + + private NotificationData.Entry mEntry; + private StatusBarNotification mSbn; + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + + @Mock private NotificationGroupManager mGroupManager; + @Mock private View mStatusBarWindowView; + @Mock private StatusBar mBar; + @Mock private ExpandableNotificationRow mRow; + @Mock private VisualStabilityManager mVSManager; + + @Before + public void setUp() { + when(mVSManager.isReorderingAllowed()).thenReturn(true); + + mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarWindowView, mGroupManager, mBar, mVSManager); + + Notification.Builder n = new Notification.Builder(mContext, "") + .setSmallIcon(R.drawable.ic_person) + .setContentTitle("Title") + .setContentText("Text"); + mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, + 0, n.build(), new UserHandle(ActivityManager.getCurrentUser()), null, 0); + + mEntry = new NotificationData.Entry(mSbn); + mEntry.row = mRow; + mEntry.expandedIcon = mock(StatusBarIconView.class); + } + + @Test + public void testBasicOperations() { + // Check the initial state. + assertNull(mHeadsUpManager.getEntry(mEntry.key)); + assertNull(mHeadsUpManager.getTopEntry()); + assertEquals(0, mHeadsUpManager.getAllEntries().count()); + assertFalse(mHeadsUpManager.hasHeadsUpNotifications()); + + // Add a notification. + mHeadsUpManager.showNotification(mEntry); + + assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key)); + assertEquals(mEntry, mHeadsUpManager.getTopEntry()); + assertEquals(1, mHeadsUpManager.getAllEntries().count()); + assertTrue(mHeadsUpManager.hasHeadsUpNotifications()); + + // Update the notification. + mHeadsUpManager.updateNotification(mEntry, false); + + assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key)); + assertEquals(mEntry, mHeadsUpManager.getTopEntry()); + assertEquals(1, mHeadsUpManager.getAllEntries().count()); + assertTrue(mHeadsUpManager.hasHeadsUpNotifications()); + + // Remove but defer, since the notification is visible on display. + mHeadsUpManager.removeNotification(mEntry.key, false); + + assertEquals(mEntry, mHeadsUpManager.getEntry(mEntry.key)); + assertEquals(mEntry, mHeadsUpManager.getTopEntry()); + assertEquals(1, mHeadsUpManager.getAllEntries().count()); + assertTrue(mHeadsUpManager.hasHeadsUpNotifications()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index bdf9b1f6da9..31442af5a04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -86,8 +86,8 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.VisualStabilityManager; +import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.DeviceProvisionedController; -import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; @@ -110,7 +110,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private UnlockMethodCache mUnlockMethodCache; @Mock private KeyguardIndicationController mKeyguardIndicationController; @Mock private NotificationStackScrollLayout mStackScroller; - @Mock private HeadsUpManager mHeadsUpManager; + @Mock private HeadsUpManagerPhone mHeadsUpManager; @Mock private SystemServicesProxy mSystemServicesProxy; @Mock private NotificationPanelView mNotificationPanelView; @Mock private IStatusBarService mBarService; @@ -588,7 +588,7 @@ public class StatusBarTest extends SysuiTestCase { static class TestableStatusBar extends StatusBar { public TestableStatusBar(StatusBarKeyguardViewManager man, UnlockMethodCache unlock, KeyguardIndicationController key, - NotificationStackScrollLayout stack, HeadsUpManager hum, + NotificationStackScrollLayout stack, HeadsUpManagerPhone hum, PowerManager pm, NotificationPanelView panelView, IStatusBarService barService, NotificationListener notificationListener, NotificationLogger notificationLogger, @@ -650,7 +650,7 @@ public class StatusBarTest extends SysuiTestCase { public void setUpForTest(NotificationPresenter presenter, NotificationListContainer listContainer, Callback callback, - HeadsUpManager headsUpManager, + HeadsUpManagerPhone headsUpManager, NotificationData notificationData) { super.setUpWithPresenter(presenter, listContainer, callback, headsUpManager); mNotificationData = notificationData; -- GitLab From a79de7da8b9c5862b3ce04491f5bff86d6858e84 Mon Sep 17 00:00:00 2001 From: Hyundo Moon Date: Tue, 30 Jan 2018 16:31:18 +0900 Subject: [PATCH 077/416] MediaController2: Rename onAudioInfoChanged This CL renames the controller callback method onAudioInfoChanged into onPlaybackInfoChanged. Bug: 72616099 Test: Builds successfully Change-Id: I6a0059790d65b55fdb6edcab18743fd94c4bdf6c --- media/java/android/media/MediaController2.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java index 3d27a4894d0..47dbde98c61 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/java/android/media/MediaController2.java @@ -104,7 +104,7 @@ public class MediaController2 implements AutoCloseable { * * @param info new playback info */ - public void onAudioInfoChanged(PlaybackInfo info) { } + public void onPlaybackInfoChanged(PlaybackInfo info) { } /** * Called when the allowed commands are changed by session. -- GitLab From 233a0b14ca1158b9844cd127d0beabfc3d601c1c Mon Sep 17 00:00:00 2001 From: Todd Kennedy Date: Mon, 29 Jan 2018 20:30:24 +0000 Subject: [PATCH 078/416] Revert "Minor LoadedApk refactoring." This reverts commit 7541ca4d1aa48e3110187a83a8dccbfa72084148. Change-Id: I2a6aa5038870c32c4145436f0463092c9b82dd23 Bug: 71501570 Test: manual --- core/java/android/app/ActivityThread.java | 180 +++++++-------- core/java/android/app/Application.java | 2 +- .../app/ApplicationPackageManager.java | 2 +- .../android/app/ClientTransactionHandler.java | 2 +- core/java/android/app/ContextImpl.java | 210 ++++++++++-------- core/java/android/app/Instrumentation.java | 6 +- core/java/android/app/LoadedApk.java | 75 +------ .../PendingTransactionActions.java | 2 +- 8 files changed, 222 insertions(+), 257 deletions(-) diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 934b0f3cd4e..a873c621dce 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -389,7 +389,7 @@ public final class ActivityThread extends ClientTransactionHandler { ActivityInfo activityInfo; CompatibilityInfo compatInfo; - public LoadedApk loadedApk; + public LoadedApk packageInfo; List pendingResults; List pendingIntents; @@ -432,7 +432,7 @@ public final class ActivityThread extends ClientTransactionHandler { this.isForward = isForward; this.profilerInfo = profilerInfo; this.overrideConfig = overrideConfig; - this.loadedApk = client.getLoadedApkNoCheck(activityInfo.applicationInfo, + this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo, compatInfo); init(); } @@ -614,7 +614,7 @@ public final class ActivityThread extends ClientTransactionHandler { } static final class AppBindData { - LoadedApk loadedApk; + LoadedApk info; String processName; ApplicationInfo appInfo; List providers; @@ -1913,13 +1913,13 @@ public final class ActivityThread extends ClientTransactionHandler { return mH; } - public final LoadedApk getLoadedApkForPackageName(String packageName, - CompatibilityInfo compatInfo, int flags) { - return getLoadedApkForPackageName(packageName, compatInfo, flags, UserHandle.myUserId()); + public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, + int flags) { + return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId()); } - public final LoadedApk getLoadedApkForPackageName(String packageName, - CompatibilityInfo compatInfo, int flags, int userId) { + public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo, + int flags, int userId) { final boolean differentUser = (UserHandle.myUserId() != userId); synchronized (mResourcesManager) { WeakReference ref; @@ -1932,13 +1932,13 @@ public final class ActivityThread extends ClientTransactionHandler { ref = mResourcePackages.get(packageName); } - LoadedApk loadedApk = ref != null ? ref.get() : null; - //Slog.i(TAG, "getLoadedApkForPackageName " + packageName + ": " + loadedApk); - //if (loadedApk != null) Slog.i(TAG, "isUptoDate " + loadedApk.mResDir - // + ": " + loadedApk.mResources.getAssets().isUpToDate()); - if (loadedApk != null && (loadedApk.mResources == null - || loadedApk.mResources.getAssets().isUpToDate())) { - if (loadedApk.isSecurityViolation() + LoadedApk packageInfo = ref != null ? ref.get() : null; + //Slog.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo); + //if (packageInfo != null) Slog.i(TAG, "isUptoDate " + packageInfo.mResDir + // + ": " + packageInfo.mResources.getAssets().isUpToDate()); + if (packageInfo != null && (packageInfo.mResources == null + || packageInfo.mResources.getAssets().isUpToDate())) { + if (packageInfo.isSecurityViolation() && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) { throw new SecurityException( "Requesting code from " + packageName @@ -1946,7 +1946,7 @@ public final class ActivityThread extends ClientTransactionHandler { + mBoundApplication.processName + "/" + mBoundApplication.appInfo.uid); } - return loadedApk; + return packageInfo; } } @@ -1961,13 +1961,13 @@ public final class ActivityThread extends ClientTransactionHandler { } if (ai != null) { - return getLoadedApk(ai, compatInfo, flags); + return getPackageInfo(ai, compatInfo, flags); } return null; } - public final LoadedApk getLoadedApk(ApplicationInfo ai, CompatibilityInfo compatInfo, + public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo, int flags) { boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0; boolean securityViolation = includeCode && ai.uid != 0 @@ -1989,17 +1989,17 @@ public final class ActivityThread extends ClientTransactionHandler { throw new SecurityException(msg); } } - return getLoadedApk(ai, compatInfo, null, securityViolation, includeCode, + return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode, registerPackage); } @Override - public final LoadedApk getLoadedApkNoCheck(ApplicationInfo ai, + public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { - return getLoadedApk(ai, compatInfo, null, false, true, false); + return getPackageInfo(ai, compatInfo, null, false, true, false); } - public final LoadedApk peekLoadedApk(String packageName, boolean includeCode) { + public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) { synchronized (mResourcesManager) { WeakReference ref; if (includeCode) { @@ -2011,7 +2011,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } - private LoadedApk getLoadedApk(ApplicationInfo aInfo, CompatibilityInfo compatInfo, + private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode, boolean registerPackage) { final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid)); @@ -2026,35 +2026,35 @@ public final class ActivityThread extends ClientTransactionHandler { ref = mResourcePackages.get(aInfo.packageName); } - LoadedApk loadedApk = ref != null ? ref.get() : null; - if (loadedApk == null || (loadedApk.mResources != null - && !loadedApk.mResources.getAssets().isUpToDate())) { + LoadedApk packageInfo = ref != null ? ref.get() : null; + if (packageInfo == null || (packageInfo.mResources != null + && !packageInfo.mResources.getAssets().isUpToDate())) { if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " : "Loading resource-only package ") + aInfo.packageName + " (in " + (mBoundApplication != null ? mBoundApplication.processName : null) + ")"); - loadedApk = + packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage); if (mSystemThread && "android".equals(aInfo.packageName)) { - loadedApk.installSystemApplicationInfo(aInfo, - getSystemContext().mLoadedApk.getClassLoader()); + packageInfo.installSystemApplicationInfo(aInfo, + getSystemContext().mPackageInfo.getClassLoader()); } if (differentUser) { // Caching not supported across users } else if (includeCode) { mPackages.put(aInfo.packageName, - new WeakReference(loadedApk)); + new WeakReference(packageInfo)); } else { mResourcePackages.put(aInfo.packageName, - new WeakReference(loadedApk)); + new WeakReference(packageInfo)); } } - return loadedApk; + return packageInfo; } } @@ -2778,8 +2778,8 @@ public final class ActivityThread extends ClientTransactionHandler { /** Core implementation of activity launch. */ private Activity performLaunchActivity(ActivityClientRecord r) { ActivityInfo aInfo = r.activityInfo; - if (r.loadedApk == null) { - r.loadedApk = getLoadedApk(aInfo.applicationInfo, r.compatInfo, + if (r.packageInfo == null) { + r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo, Context.CONTEXT_INCLUDE_CODE); } @@ -2816,15 +2816,15 @@ public final class ActivityThread extends ClientTransactionHandler { } try { - Application app = r.loadedApk.makeApplication(false, mInstrumentation); + Application app = r.packageInfo.makeApplication(false, mInstrumentation); if (localLOGV) Slog.v(TAG, "Performing launch of " + r); if (localLOGV) Slog.v( TAG, r + ": app=" + app + ", appName=" + app.getPackageName() - + ", pkg=" + r.loadedApk.getPackageName() + + ", pkg=" + r.packageInfo.getPackageName() + ", comp=" + r.intent.getComponent().toShortString() - + ", dir=" + r.loadedApk.getAppDir()); + + ", dir=" + r.packageInfo.getAppDir()); if (activity != null) { CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); @@ -2969,7 +2969,7 @@ public final class ActivityThread extends ClientTransactionHandler { } ContextImpl appContext = ContextImpl.createActivityContext( - this, r.loadedApk, r.activityInfo, r.token, displayId, r.overrideConfig); + this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig); final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance(); // For debugging purposes, if the activity's package name contains the value of @@ -2977,7 +2977,7 @@ public final class ActivityThread extends ClientTransactionHandler { // its content on a secondary display if there is one. String pkgName = SystemProperties.get("debug.second-display.pkg"); if (pkgName != null && !pkgName.isEmpty() - && r.loadedApk.mPackageName.contains(pkgName)) { + && r.packageInfo.mPackageName.contains(pkgName)) { for (int id : dm.getDisplayIds()) { if (id != Display.DEFAULT_DISPLAY) { Display display = @@ -3309,7 +3309,7 @@ public final class ActivityThread extends ClientTransactionHandler { String component = data.intent.getComponent().getClassName(); - LoadedApk loadedApk = getLoadedApkNoCheck( + LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); IActivityManager mgr = ActivityManager.getService(); @@ -3318,7 +3318,7 @@ public final class ActivityThread extends ClientTransactionHandler { BroadcastReceiver receiver; ContextImpl context; try { - app = loadedApk.makeApplication(false, mInstrumentation); + app = packageInfo.makeApplication(false, mInstrumentation); context = (ContextImpl) app.getBaseContext(); if (data.info.splitName != null) { context = (ContextImpl) context.createContextForSplit(data.info.splitName); @@ -3327,7 +3327,7 @@ public final class ActivityThread extends ClientTransactionHandler { data.intent.setExtrasClassLoader(cl); data.intent.prepareToEnterProcess(); data.setExtrasClassLoader(cl); - receiver = loadedApk.getAppFactory() + receiver = packageInfo.getAppFactory() .instantiateReceiver(cl, data.info.name, data.intent); } catch (Exception e) { if (DEBUG_BROADCAST) Slog.i(TAG, @@ -3343,9 +3343,9 @@ public final class ActivityThread extends ClientTransactionHandler { TAG, "Performing receive of " + data.intent + ": app=" + app + ", appName=" + app.getPackageName() - + ", pkg=" + loadedApk.getPackageName() + + ", pkg=" + packageInfo.getPackageName() + ", comp=" + data.intent.getComponent().toShortString() - + ", dir=" + loadedApk.getAppDir()); + + ", dir=" + packageInfo.getAppDir()); sCurrentBroadcastIntent.set(data.intent); receiver.setPendingResult(data); @@ -3390,8 +3390,8 @@ public final class ActivityThread extends ClientTransactionHandler { unscheduleGcIdler(); // instantiate the BackupAgent class named in the manifest - LoadedApk loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); - String packageName = loadedApk.mPackageName; + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + String packageName = packageInfo.mPackageName; if (packageName == null) { Slog.d(TAG, "Asked to create backup agent for nonexistent package"); return; @@ -3417,11 +3417,11 @@ public final class ActivityThread extends ClientTransactionHandler { try { if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname); - java.lang.ClassLoader cl = loadedApk.getClassLoader(); + java.lang.ClassLoader cl = packageInfo.getClassLoader(); agent = (BackupAgent) cl.loadClass(classname).newInstance(); // set up the agent's context - ContextImpl context = ContextImpl.createAppContext(this, loadedApk); + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(agent); agent.attach(context); @@ -3457,8 +3457,8 @@ public final class ActivityThread extends ClientTransactionHandler { private void handleDestroyBackupAgent(CreateBackupAgentData data) { if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data); - LoadedApk loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); - String packageName = loadedApk.mPackageName; + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + String packageName = packageInfo.mPackageName; BackupAgent agent = mBackupAgents.get(packageName); if (agent != null) { try { @@ -3478,12 +3478,12 @@ public final class ActivityThread extends ClientTransactionHandler { // we are back active so skip it. unscheduleGcIdler(); - LoadedApk loadedApk = getLoadedApkNoCheck( + LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null; try { - java.lang.ClassLoader cl = loadedApk.getClassLoader(); - service = loadedApk.getAppFactory() + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + service = packageInfo.getAppFactory() .instantiateService(cl, data.info.name, data.intent); } catch (Exception e) { if (!mInstrumentation.onException(service, e)) { @@ -3496,10 +3496,10 @@ public final class ActivityThread extends ClientTransactionHandler { try { if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name); - ContextImpl context = ContextImpl.createAppContext(this, loadedApk); + ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); - Application app = loadedApk.makeApplication(false, mInstrumentation); + Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); service.onCreate(); @@ -4363,11 +4363,11 @@ public final class ActivityThread extends ClientTransactionHandler { } private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) { - LoadedApk apk = peekLoadedApk(data.pkg, false); + LoadedApk apk = peekPackageInfo(data.pkg, false); if (apk != null) { apk.setCompatibilityInfo(data.info); } - apk = peekLoadedApk(data.pkg, true); + apk = peekPackageInfo(data.pkg, true); if (apk != null) { apk.setCompatibilityInfo(data.info); } @@ -4861,7 +4861,7 @@ public final class ActivityThread extends ClientTransactionHandler { if (a != null) { Configuration thisConfig = applyConfigCompatMainThread( mCurDefaultDisplayDpi, newConfig, - ar.loadedApk.getCompatibilityInfo()); + ar.packageInfo.getCompatibilityInfo()); if (!ar.activity.mFinished && (allActivities || !ar.paused)) { // If the activity is currently resumed, its configuration // needs to change right now. @@ -5347,7 +5347,7 @@ public final class ActivityThread extends ClientTransactionHandler { } final void handleDispatchPackageBroadcast(int cmd, String[] packages) { - boolean hasLoadedApk = false; + boolean hasPkgInfo = false; switch (cmd) { case ApplicationThreadConstants.PACKAGE_REMOVED: case ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL: @@ -5358,14 +5358,14 @@ public final class ActivityThread extends ClientTransactionHandler { } synchronized (mResourcesManager) { for (int i = packages.length - 1; i >= 0; i--) { - if (!hasLoadedApk) { + if (!hasPkgInfo) { WeakReference ref = mPackages.get(packages[i]); if (ref != null && ref.get() != null) { - hasLoadedApk = true; + hasPkgInfo = true; } else { ref = mResourcePackages.get(packages[i]); if (ref != null && ref.get() != null) { - hasLoadedApk = true; + hasPkgInfo = true; } } } @@ -5385,21 +5385,21 @@ public final class ActivityThread extends ClientTransactionHandler { synchronized (mResourcesManager) { for (int i = packages.length - 1; i >= 0; i--) { WeakReference ref = mPackages.get(packages[i]); - LoadedApk loadedApk = ref != null ? ref.get() : null; - if (loadedApk != null) { - hasLoadedApk = true; + LoadedApk pkgInfo = ref != null ? ref.get() : null; + if (pkgInfo != null) { + hasPkgInfo = true; } else { ref = mResourcePackages.get(packages[i]); - loadedApk = ref != null ? ref.get() : null; - if (loadedApk != null) { - hasLoadedApk = true; + pkgInfo = ref != null ? ref.get() : null; + if (pkgInfo != null) { + hasPkgInfo = true; } } // If the package is being replaced, yet it still has a valid // LoadedApk object, the package was updated with _DONT_KILL. // Adjust it's internal references to the application info and // resources. - if (loadedApk != null) { + if (pkgInfo != null) { try { final String packageName = packages[i]; final ApplicationInfo aInfo = @@ -5413,13 +5413,13 @@ public final class ActivityThread extends ClientTransactionHandler { if (ar.activityInfo.applicationInfo.packageName .equals(packageName)) { ar.activityInfo.applicationInfo = aInfo; - ar.loadedApk = loadedApk; + ar.packageInfo = pkgInfo; } } } final List oldPaths = sPackageManager.getPreviousCodePaths(packageName); - loadedApk.updateApplicationInfo(aInfo, oldPaths); + pkgInfo.updateApplicationInfo(aInfo, oldPaths); } catch (RemoteException e) { } } @@ -5428,7 +5428,7 @@ public final class ActivityThread extends ClientTransactionHandler { break; } } - ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasLoadedApk); + ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo); } final void handleLowMemory() { @@ -5636,10 +5636,10 @@ public final class ActivityThread extends ClientTransactionHandler { applyCompatConfiguration(mCurDefaultDisplayDpi); } - data.loadedApk = getLoadedApkNoCheck(data.appInfo, data.compatInfo); + data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); if (agent != null) { - handleAttachAgent(agent, data.loadedApk); + handleAttachAgent(agent, data.info); } /** @@ -5684,7 +5684,7 @@ public final class ActivityThread extends ClientTransactionHandler { // XXX should have option to change the port. Debug.changeDebugPort(8100); if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) { - Slog.w(TAG, "Application " + data.loadedApk.getPackageName() + Slog.w(TAG, "Application " + data.info.getPackageName() + " is waiting for the debugger on port 8100..."); IActivityManager mgr = ActivityManager.getService(); @@ -5703,7 +5703,7 @@ public final class ActivityThread extends ClientTransactionHandler { } } else { - Slog.w(TAG, "Application " + data.loadedApk.getPackageName() + Slog.w(TAG, "Application " + data.info.getPackageName() + " can be debugged on port 8100..."); } } @@ -5751,14 +5751,14 @@ public final class ActivityThread extends ClientTransactionHandler { mInstrumentationAppDir = ii.sourceDir; mInstrumentationSplitAppDirs = ii.splitSourceDirs; mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii); - mInstrumentedAppDir = data.loadedApk.getAppDir(); - mInstrumentedSplitAppDirs = data.loadedApk.getSplitAppDirs(); - mInstrumentedLibDir = data.loadedApk.getLibDir(); + mInstrumentedAppDir = data.info.getAppDir(); + mInstrumentedSplitAppDirs = data.info.getSplitAppDirs(); + mInstrumentedLibDir = data.info.getLibDir(); } else { ii = null; } - final ContextImpl appContext = ContextImpl.createAppContext(this, data.loadedApk); + final ContextImpl appContext = ContextImpl.createAppContext(this, data.info); updateLocaleListFromAppContext(appContext, mResourcesManager.getConfiguration().getLocales()); @@ -5802,9 +5802,9 @@ public final class ActivityThread extends ClientTransactionHandler { } ii.copyTo(instrApp); instrApp.initForUser(UserHandle.myUserId()); - final LoadedApk loadedApk = getLoadedApk(instrApp, data.compatInfo, + final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo, appContext.getClassLoader(), false, true, false); - final ContextImpl instrContext = ContextImpl.createAppContext(this, loadedApk); + final ContextImpl instrContext = ContextImpl.createAppContext(this, pi); try { final ClassLoader cl = instrContext.getClassLoader(); @@ -5849,7 +5849,7 @@ public final class ActivityThread extends ClientTransactionHandler { try { // If the app is being launched for full backup or restore, bring it up in // a restricted environment with the base application class. - app = data.loadedApk.makeApplication(data.restrictedBackupMode, null); + app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; // don't bring up providers in restricted mode; they may depend on the @@ -5903,7 +5903,7 @@ public final class ActivityThread extends ClientTransactionHandler { final int preloadedFontsResource = info.metaData.getInt( ApplicationInfo.METADATA_PRELOADED_FONTS, 0); if (preloadedFontsResource != 0) { - data.loadedApk.getResources().preloadFonts(preloadedFontsResource); + data.info.getResources().preloadFonts(preloadedFontsResource); } } } catch (RemoteException e) { @@ -6361,12 +6361,12 @@ public final class ActivityThread extends ClientTransactionHandler { try { final java.lang.ClassLoader cl = c.getClassLoader(); - LoadedApk loadedApk = peekLoadedApk(ai.packageName, true); - if (loadedApk == null) { + LoadedApk packageInfo = peekPackageInfo(ai.packageName, true); + if (packageInfo == null) { // System startup case. - loadedApk = getSystemContext().mLoadedApk; + packageInfo = getSystemContext().mPackageInfo; } - localProvider = loadedApk.getAppFactory() + localProvider = packageInfo.getAppFactory() .instantiateProvider(cl, info.name); provider = localProvider.getIContentProvider(); if (provider == null) { @@ -6515,8 +6515,8 @@ public final class ActivityThread extends ClientTransactionHandler { mInstrumentation = new Instrumentation(); mInstrumentation.basicInit(this); ContextImpl context = ContextImpl.createAppContext( - this, getSystemContext().mLoadedApk); - mInitialApplication = context.mLoadedApk.makeApplication(true, null); + this, getSystemContext().mPackageInfo); + mInitialApplication = context.mPackageInfo.makeApplication(true, null); mInitialApplication.onCreate(); } catch (Exception e) { throw new RuntimeException( diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java index 5822f5c8f6c..156df36a600 100644 --- a/core/java/android/app/Application.java +++ b/core/java/android/app/Application.java @@ -187,7 +187,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { */ /* package */ final void attach(Context context) { attachBaseContext(context); - mLoadedApk = ContextImpl.getImpl(context).mLoadedApk; + mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; } /* package */ void dispatchActivityCreated(Activity activity, Bundle savedInstanceState) { diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index cc68c0518b4..6b0a2f90c38 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1409,7 +1409,7 @@ public class ApplicationPackageManager extends PackageManager { sameUid ? app.sourceDir : app.publicSourceDir, sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY, - mContext.mLoadedApk); + mContext.mPackageInfo); if (r != null) { return r; } diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 0f66652af76..5b61fdf8677 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -111,7 +111,7 @@ public abstract class ClientTransactionHandler { PendingTransactionActions pendingActions); /** Get package info. */ - public abstract LoadedApk getLoadedApkNoCheck(ApplicationInfo ai, + public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo); /** Deliver app configuration change notification. */ diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index ea940426f5a..6496110ac9c 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -159,7 +159,7 @@ class ContextImpl extends Context { private ArrayMap mSharedPrefsPaths; final @NonNull ActivityThread mMainThread; - final @NonNull LoadedApk mLoadedApk; + final @NonNull LoadedApk mPackageInfo; private @Nullable ClassLoader mClassLoader; private final @Nullable IBinder mActivityToken; @@ -257,8 +257,8 @@ class ContextImpl extends Context { @Override public Context getApplicationContext() { - return (mLoadedApk != null) ? - mLoadedApk.getApplication() : mMainThread.getApplication(); + return (mPackageInfo != null) ? + mPackageInfo.getApplication() : mMainThread.getApplication(); } @Override @@ -302,15 +302,15 @@ class ContextImpl extends Context { @Override public ClassLoader getClassLoader() { - return mClassLoader != null ? mClassLoader : (mLoadedApk != null ? mLoadedApk.getClassLoader() : ClassLoader.getSystemClassLoader()); + return mClassLoader != null ? mClassLoader : (mPackageInfo != null ? mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader()); } @Override public String getPackageName() { - if (mLoadedApk != null) { - return mLoadedApk.getPackageName(); + if (mPackageInfo != null) { + return mPackageInfo.getPackageName(); } - // No mLoadedApk means this is a Context for the system itself, + // No mPackageInfo means this is a Context for the system itself, // and this here is its name. return "android"; } @@ -329,24 +329,24 @@ class ContextImpl extends Context { @Override public ApplicationInfo getApplicationInfo() { - if (mLoadedApk != null) { - return mLoadedApk.getApplicationInfo(); + if (mPackageInfo != null) { + return mPackageInfo.getApplicationInfo(); } throw new RuntimeException("Not supported in system context"); } @Override public String getPackageResourcePath() { - if (mLoadedApk != null) { - return mLoadedApk.getResDir(); + if (mPackageInfo != null) { + return mPackageInfo.getResDir(); } throw new RuntimeException("Not supported in system context"); } @Override public String getPackageCodePath() { - if (mLoadedApk != null) { - return mLoadedApk.getAppDir(); + if (mPackageInfo != null) { + return mPackageInfo.getAppDir(); } throw new RuntimeException("Not supported in system context"); } @@ -356,7 +356,7 @@ class ContextImpl extends Context { // At least one application in the world actually passes in a null // name. This happened to work because when we generated the file name // we would stringify it to "null.xml". Nice. - if (mLoadedApk.getApplicationInfo().targetSdkVersion < + if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) { if (name == null) { name = "null"; @@ -1104,11 +1104,11 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1208,11 +1208,11 @@ class ContextImpl extends Context { Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1262,11 +1262,11 @@ class ContextImpl extends Context { warnIfCallingFromSystemProcess(); IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1344,11 +1344,11 @@ class ContextImpl extends Context { Bundle initialExtras) { IIntentReceiver rd = null; if (resultReceiver != null) { - if (mLoadedApk != null) { + if (mPackageInfo != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, mMainThread.getInstrumentation(), false); } else { @@ -1425,11 +1425,11 @@ class ContextImpl extends Context { Handler scheduler, Context context, int flags) { IIntentReceiver rd = null; if (receiver != null) { - if (mLoadedApk != null && context != null) { + if (mPackageInfo != null && context != null) { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = mLoadedApk.getReceiverDispatcher( + rd = mPackageInfo.getReceiverDispatcher( receiver, context, scheduler, mMainThread.getInstrumentation(), true); } else { @@ -1456,8 +1456,8 @@ class ContextImpl extends Context { @Override public void unregisterReceiver(BroadcastReceiver receiver) { - if (mLoadedApk != null) { - IIntentReceiver rd = mLoadedApk.forgetReceiverDispatcher( + if (mPackageInfo != null) { + IIntentReceiver rd = mPackageInfo.forgetReceiverDispatcher( getOuterContext(), receiver); try { ActivityManager.getService().unregisterReceiver(rd); @@ -1590,7 +1590,7 @@ class ContextImpl extends Context { @Override public IServiceConnection getServiceDispatcher(ServiceConnection conn, Handler handler, int flags) { - return mLoadedApk.getServiceDispatcher(conn, getOuterContext(), handler, flags); + return mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); } /** @hide */ @@ -1612,16 +1612,16 @@ class ContextImpl extends Context { if (conn == null) { throw new IllegalArgumentException("connection is null"); } - if (mLoadedApk != null) { - sd = mLoadedApk.getServiceDispatcher(conn, getOuterContext(), handler, flags); + if (mPackageInfo != null) { + sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); } else { throw new RuntimeException("Not supported in system context"); } validateServiceIntent(service); try { IBinder token = getActivityToken(); - if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mLoadedApk != null - && mLoadedApk.getApplicationInfo().targetSdkVersion + if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null + && mPackageInfo.getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { flags |= BIND_WAIVE_PRIORITY; } @@ -1645,8 +1645,8 @@ class ContextImpl extends Context { if (conn == null) { throw new IllegalArgumentException("connection is null"); } - if (mLoadedApk != null) { - IServiceConnection sd = mLoadedApk.forgetServiceDispatcher( + if (mPackageInfo != null) { + IServiceConnection sd = mPackageInfo.forgetServiceDispatcher( getOuterContext(), conn); try { ActivityManager.getService().unbindService(sd); @@ -1991,20 +1991,40 @@ class ContextImpl extends Context { } } + private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName, + int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { + final String[] splitResDirs; + final ClassLoader classLoader; + try { + splitResDirs = pi.getSplitPaths(splitName); + classLoader = pi.getSplitClassLoader(splitName); + } catch (NameNotFoundException e) { + throw new RuntimeException(e); + } + return ResourcesManager.getInstance().getResources(activityToken, + pi.getResDir(), + splitResDirs, + pi.getOverlayDirs(), + pi.getApplicationInfo().sharedLibraryFiles, + displayId, + overrideConfig, + compatInfo, + classLoader); + } + @Override public Context createApplicationContext(ApplicationInfo application, int flags) throws NameNotFoundException { - LoadedApk loadedApk = mMainThread.getLoadedApk(application, - mResources.getCompatibilityInfo(), + LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE); - if (loadedApk != null) { - ContextImpl c = new ContextImpl(this, mMainThread, loadedApk, null, mActivityToken, + if (pi != null) { + ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, new UserHandle(UserHandle.getUserId(application.uid)), flags, null); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - c.setResources(loadedApk.createResources(mActivityToken, null, displayId, null, + c.setResources(createResources(mActivityToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2028,21 +2048,20 @@ class ContextImpl extends Context { if (packageName.equals("system") || packageName.equals("android")) { // The system resources are loaded in every application, so we can safely copy // the context without reloading Resources. - return new ContextImpl(this, mMainThread, mLoadedApk, null, mActivityToken, user, + return new ContextImpl(this, mMainThread, mPackageInfo, null, mActivityToken, user, flags, null); } - LoadedApk loadedApk = mMainThread.getLoadedApkForPackageName(packageName, - mResources.getCompatibilityInfo(), + LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(), flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier()); - if (loadedApk != null) { - ContextImpl c = new ContextImpl(this, mMainThread, loadedApk, null, mActivityToken, user, + if (pi != null) { + ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user, flags, null); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - c.setResources(loadedApk.createResources(mActivityToken, null, displayId, null, + c.setResources(createResources(mActivityToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); if (c.mResources != null) { return c; @@ -2056,21 +2075,30 @@ class ContextImpl extends Context { @Override public Context createContextForSplit(String splitName) throws NameNotFoundException { - if (!mLoadedApk.getApplicationInfo().requestsIsolatedSplitLoading()) { + if (!mPackageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) { // All Splits are always loaded. return this; } - final ClassLoader classLoader = mLoadedApk.getSplitClassLoader(splitName); + final ClassLoader classLoader = mPackageInfo.getSplitClassLoader(splitName); + final String[] paths = mPackageInfo.getSplitPaths(splitName); - final ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, splitName, + final ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, splitName, mActivityToken, mUser, mFlags, classLoader); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - context.setResources(mLoadedApk.getOrCreateResourcesForSplit(splitName, - mActivityToken, displayId)); + context.setResources(ResourcesManager.getInstance().getResources( + mActivityToken, + mPackageInfo.getResDir(), + paths, + mPackageInfo.getOverlayDirs(), + mPackageInfo.getApplicationInfo().sharedLibraryFiles, + displayId, + null, + mPackageInfo.getCompatibilityInfo(), + classLoader)); return context; } @@ -2080,11 +2108,11 @@ class ContextImpl extends Context { throw new IllegalArgumentException("overrideConfiguration must not be null"); } - ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, mFlags, mClassLoader); final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY; - context.setResources(mLoadedApk.createResources(mActivityToken, mSplitName, displayId, + context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo())); return context; } @@ -2095,11 +2123,11 @@ class ContextImpl extends Context { throw new IllegalArgumentException("display must not be null"); } - ContextImpl context = new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, + ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, mFlags, mClassLoader); final int displayId = display.getDisplayId(); - context.setResources(mLoadedApk.createResources(mActivityToken, mSplitName, displayId, + context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); context.mDisplay = display; return context; @@ -2109,7 +2137,7 @@ class ContextImpl extends Context { public Context createDeviceProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) | Context.CONTEXT_DEVICE_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, mActivityToken, mUser, + return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, flags, mClassLoader); } @@ -2117,7 +2145,7 @@ class ContextImpl extends Context { public Context createCredentialProtectedStorageContext() { final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE) | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; - return new ContextImpl(this, mMainThread, mLoadedApk, mSplitName, mActivityToken, mUser, + return new ContextImpl(this, mMainThread, mPackageInfo, mSplitName, mActivityToken, mUser, flags, mClassLoader); } @@ -2166,14 +2194,14 @@ class ContextImpl extends Context { @Override public File getDataDir() { - if (mLoadedApk != null) { + if (mPackageInfo != null) { File res = null; if (isCredentialProtectedStorage()) { - res = mLoadedApk.getCredentialProtectedDataDirFile(); + res = mPackageInfo.getCredentialProtectedDataDirFile(); } else if (isDeviceProtectedStorage()) { - res = mLoadedApk.getDeviceProtectedDataDirFile(); + res = mPackageInfo.getDeviceProtectedDataDirFile(); } else { - res = mLoadedApk.getDataDirFile(); + res = mPackageInfo.getDataDirFile(); } if (res != null) { @@ -2224,10 +2252,10 @@ class ContextImpl extends Context { } static ContextImpl createSystemContext(ActivityThread mainThread) { - LoadedApk loadedApk = new LoadedApk(mainThread); - ContextImpl context = new ContextImpl(null, mainThread, loadedApk, null, null, null, 0, + LoadedApk packageInfo = new LoadedApk(mainThread); + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null); - context.setResources(loadedApk.getResources()); + context.setResources(packageInfo.getResources()); context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(), context.mResourcesManager.getDisplayMetrics()); return context; @@ -2238,35 +2266,35 @@ class ContextImpl extends Context { * Make sure that the created system UI context shares the same LoadedApk as the system context. */ static ContextImpl createSystemUiContext(ContextImpl systemContext) { - final LoadedApk loadedApk = systemContext.mLoadedApk; - ContextImpl context = new ContextImpl(null, systemContext.mMainThread, loadedApk, null, + final LoadedApk packageInfo = systemContext.mPackageInfo; + ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null, null, null, 0, null); - context.setResources(loadedApk.createResources(null, null, Display.DEFAULT_DISPLAY, null, - loadedApk.getCompatibilityInfo())); + context.setResources(createResources(null, packageInfo, null, Display.DEFAULT_DISPLAY, null, + packageInfo.getCompatibilityInfo())); return context; } - static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk loadedApk) { - if (loadedApk == null) throw new IllegalArgumentException("loadedApk"); - ContextImpl context = new ContextImpl(null, mainThread, loadedApk, null, null, null, 0, + static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { + if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null); - context.setResources(loadedApk.getResources()); + context.setResources(packageInfo.getResources()); return context; } static ContextImpl createActivityContext(ActivityThread mainThread, - LoadedApk loadedApk, ActivityInfo activityInfo, IBinder activityToken, int displayId, + LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId, Configuration overrideConfiguration) { - if (loadedApk == null) throw new IllegalArgumentException("loadedApk"); + if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); - String[] splitDirs = loadedApk.getSplitResDirs(); - ClassLoader classLoader = loadedApk.getClassLoader(); + String[] splitDirs = packageInfo.getSplitResDirs(); + ClassLoader classLoader = packageInfo.getClassLoader(); - if (loadedApk.getApplicationInfo().requestsIsolatedSplitLoading()) { + if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies"); try { - classLoader = loadedApk.getSplitClassLoader(activityInfo.splitName); - splitDirs = loadedApk.getSplitPaths(activityInfo.splitName); + classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName); + splitDirs = packageInfo.getSplitPaths(activityInfo.splitName); } catch (NameNotFoundException e) { // Nothing above us can handle a NameNotFoundException, better crash. throw new RuntimeException(e); @@ -2275,14 +2303,14 @@ class ContextImpl extends Context { } } - ContextImpl context = new ContextImpl(null, mainThread, loadedApk, activityInfo.splitName, + ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName, activityToken, null, 0, classLoader); // Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY. displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY; final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY) - ? loadedApk.getCompatibilityInfo() + ? packageInfo.getCompatibilityInfo() : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; final ResourcesManager resourcesManager = ResourcesManager.getInstance(); @@ -2290,10 +2318,10 @@ class ContextImpl extends Context { // Create the base resources for which all configuration contexts for this Activity // will be rebased upon. context.setResources(resourcesManager.createBaseActivityResources(activityToken, - loadedApk.getResDir(), + packageInfo.getResDir(), splitDirs, - loadedApk.getOverlayDirs(), - loadedApk.getApplicationInfo().sharedLibraryFiles, + packageInfo.getOverlayDirs(), + packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, compatInfo, @@ -2304,7 +2332,7 @@ class ContextImpl extends Context { } private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread, - @NonNull LoadedApk loadedApk, @Nullable String splitName, + @NonNull LoadedApk packageInfo, @Nullable String splitName, @Nullable IBinder activityToken, @Nullable UserHandle user, int flags, @Nullable ClassLoader classLoader) { mOuterContext = this; @@ -2313,10 +2341,10 @@ class ContextImpl extends Context { // location for application. if ((flags & (Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE | Context.CONTEXT_DEVICE_PROTECTED_STORAGE)) == 0) { - final File dataDir = loadedApk.getDataDirFile(); - if (Objects.equals(dataDir, loadedApk.getCredentialProtectedDataDirFile())) { + final File dataDir = packageInfo.getDataDirFile(); + if (Objects.equals(dataDir, packageInfo.getCredentialProtectedDataDirFile())) { flags |= Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE; - } else if (Objects.equals(dataDir, loadedApk.getDeviceProtectedDataDirFile())) { + } else if (Objects.equals(dataDir, packageInfo.getDeviceProtectedDataDirFile())) { flags |= Context.CONTEXT_DEVICE_PROTECTED_STORAGE; } } @@ -2330,7 +2358,7 @@ class ContextImpl extends Context { } mUser = user; - mLoadedApk = loadedApk; + mPackageInfo = packageInfo; mSplitName = splitName; mClassLoader = classLoader; mResourcesManager = ResourcesManager.getInstance(); @@ -2341,8 +2369,8 @@ class ContextImpl extends Context { setResources(container.mResources); mDisplay = container.mDisplay; } else { - mBasePackageName = loadedApk.mPackageName; - ApplicationInfo ainfo = loadedApk.getApplicationInfo(); + mBasePackageName = packageInfo.mPackageName; + ApplicationInfo ainfo = packageInfo.getApplicationInfo(); if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) { // Special case: system components allow themselves to be loaded in to other // processes. For purposes of app ops, we must then consider the context as @@ -2365,7 +2393,7 @@ class ContextImpl extends Context { } void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) { - mLoadedApk.installSystemApplicationInfo(info, classLoader); + mPackageInfo.installSystemApplicationInfo(info, classLoader); } final void scheduleFinalCleanup(String who, String what) { @@ -2374,7 +2402,7 @@ class ContextImpl extends Context { final void performFinalCleanup(String who, String what) { //Log.i(TAG, "Cleanup up context: " + this); - mLoadedApk.removeContextRegistrations(getOuterContext(), who, what); + mPackageInfo.removeContextRegistrations(getOuterContext(), who, what); } final Context getReceiverRestrictedContext() { diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index f90b276ac17..9f97139a73c 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1216,10 +1216,10 @@ public class Instrumentation { + " disabling AppComponentFactory", new Throwable()); return AppComponentFactory.DEFAULT; } - LoadedApk loadedApk = mThread.peekLoadedApk(pkg, true); + LoadedApk apk = mThread.peekPackageInfo(pkg, true); // This is in the case of starting up "android". - if (loadedApk == null) loadedApk = mThread.getSystemContext().mLoadedApk; - return loadedApk.getAppFactory(); + if (apk == null) apk = mThread.getSystemContext().mPackageInfo; + return apk.getAppFactory(); } private void prePerformCreate(Activity activity) { diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 26f49808778..ab00a7ddde5 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -31,7 +31,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.split.SplitDependencyLoader; import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; -import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; @@ -49,13 +48,15 @@ import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.ArrayMap; import android.util.Log; -import android.util.LogPrinter; import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayAdjustments; + import com.android.internal.util.ArrayUtils; + import dalvik.system.VMRuntime; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -967,78 +968,14 @@ public final class LoadedApk { throw new AssertionError("null split not found"); } - mResources = ResourcesManager.getInstance().getResources( - null, - mResDir, - splitPaths, - mOverlayDirs, - mApplicationInfo.sharedLibraryFiles, - Display.DEFAULT_DISPLAY, - null, - getCompatibilityInfo(), + mResources = ResourcesManager.getInstance().getResources(null, mResDir, + splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, + Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(), getClassLoader()); } return mResources; } - public Resources getOrCreateResourcesForSplit(@NonNull String splitName, - @Nullable IBinder activityToken, int displayId) throws NameNotFoundException { - return ResourcesManager.getInstance().getResources( - activityToken, - mResDir, - getSplitPaths(splitName), - mOverlayDirs, - mApplicationInfo.sharedLibraryFiles, - displayId, - null, - getCompatibilityInfo(), - getSplitClassLoader(splitName)); - } - - /** - * Creates the top level resources for the given package. Will return an existing - * Resources if one has already been created. - */ - public Resources getOrCreateTopLevelResources(@NonNull ApplicationInfo appInfo) { - // Request for this app, short circuit - if (appInfo.uid == Process.myUid()) { - return getResources(); - } - - // Get resources for a different package - return ResourcesManager.getInstance().getResources( - null, - appInfo.publicSourceDir, - appInfo.splitPublicSourceDirs, - appInfo.resourceDirs, - appInfo.sharedLibraryFiles, - Display.DEFAULT_DISPLAY, - null, - getCompatibilityInfo(), - getClassLoader()); - } - - public Resources createResources(IBinder activityToken, String splitName, - int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) { - final String[] splitResDirs; - final ClassLoader classLoader; - try { - splitResDirs = getSplitPaths(splitName); - classLoader = getSplitClassLoader(splitName); - } catch (NameNotFoundException e) { - throw new RuntimeException(e); - } - return ResourcesManager.getInstance().getResources(activityToken, - mResDir, - splitResDirs, - mOverlayDirs, - mApplicationInfo.sharedLibraryFiles, - displayId, - overrideConfig, - compatInfo, - classLoader); - } - public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) { if (mApplication != null) { diff --git a/core/java/android/app/servertransaction/PendingTransactionActions.java b/core/java/android/app/servertransaction/PendingTransactionActions.java index 8304c1c5a2d..073d28cfa27 100644 --- a/core/java/android/app/servertransaction/PendingTransactionActions.java +++ b/core/java/android/app/servertransaction/PendingTransactionActions.java @@ -134,7 +134,7 @@ public class PendingTransactionActions { Bundle.dumpStats(pw, mPersistentState); if (ex instanceof TransactionTooLargeException - && mActivity.loadedApk.getTargetSdkVersion() < Build.VERSION_CODES.N) { + && mActivity.packageInfo.getTargetSdkVersion() < Build.VERSION_CODES.N) { Log.e(TAG, "App sent too much data in instance state, so it was ignored", ex); return; } -- GitLab From 09336b56187ca494c6215f3f12b7991119e44d73 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Wed, 6 Dec 2017 12:43:28 +0000 Subject: [PATCH 079/416] Use prebuilt android.test.base.jar for app builds (cherry picked from commit 1f090a8d66126a936e40f0e872c5fe5b655fa335) Bug: 30188076 Test: make checkbuild Change-Id: I09a70f3e79d0935394332870613b96c653af5e85 Merged-In: I1d7e705baf5728e7a034f3bd32746de3a1d3cd78 --- test-base/Android.mk | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test-base/Android.mk b/test-base/Android.mk index 25c3d7646df..861385467a0 100644 --- a/test-base/Android.mk +++ b/test-base/Android.mk @@ -16,8 +16,11 @@ LOCAL_PATH:= $(call my-dir) -# Generate the stub source files for legacy.test.stubs -# ==================================================== +# For unbundled build we'll use the prebuilt jar from prebuilts/sdk. +ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK))) + +# Generate the stub source files for android.test.base.stubs +# ========================================================== include $(CLEAR_VARS) LOCAL_SRC_FILES := \ @@ -107,6 +110,8 @@ update-android-test-base-api: $(ANDROID_TEST_BASE_OUTPUT_API_FILE) | $(ACP) @echo Copying removed.txt $(hide) $(ACP) $(ANDROID_TEST_BASE_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_BASE_REMOVED_API_FILE) +endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true + ifeq ($(HOST_OS),linux) # Build the legacy-performance-test-hostdex library # ================================================= -- GitLab From 6b8b7e04efce0943c4d7d72f1bf91b28aaa3c903 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Fri, 30 Jun 2017 16:02:09 +0100 Subject: [PATCH 080/416] Build stubs against SDK and clear local variables The stubs need to be built against the current SDK where possible and not the internal modules. (cherry picked from commit d41d847c83c598082a6572f38d0d40f5dee427ef) Bug: 30188076 Test: delete stub files and remake targets Change-Id: I9b46a4e2be341fed7e5b33bbf1a80d88c5486ae6 Merged-In: Id724c16e56d1e8fe7f61cfafe7f11ea27e01e659 --- test-runner/Android.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test-runner/Android.mk b/test-runner/Android.mk index cdc7756d575..37cd0848cac 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -69,9 +69,11 @@ LOCAL_JAVA_LIBRARIES := \ android.test.mock.stubs \ LOCAL_SOURCE_FILES_ALL_GENERATED := true +LOCAL_SDK_VERSION := current # Make sure to run droiddoc first to generate the stub source files. LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_runner_api_gen_stamp) +android_test_runner_api_gen_stamp := include $(BUILD_STATIC_JAVA_LIBRARY) -- GitLab From bee7cce828e0e876b20d4d89706f87f56e3a2dc2 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Fri, 5 Jan 2018 15:11:18 +0000 Subject: [PATCH 081/416] Add android.test.legacy target Adds a library that builds against the public API and so can be safely statically included into applications to avoid them having to depend on the android.test.base and android.test.runner runtime libraries. (cherry picked from commit a70f66cb5e40105d4b0ec535f011eea83a38c86a) Bug: 30188076 Test: make checkbuild Change-Id: Ibd8cb61d00a65dbcf630672706323e42d82e6ba2 Merged-In: Iae7e3c64392e11035322092ed8e194740ba2d321 --- test-base/Android.bp | 20 ++++++++++++++++++++ test-runner/Android.mk | 15 +++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/test-base/Android.bp b/test-base/Android.bp index a42dc5a10ec..ccf57b00a37 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -64,6 +64,26 @@ java_library_static { jarjar_rules: "jarjar-rules.txt", } +// Build the android.test.base-minus-junit library +// =============================================== +// This contains the android.test classes from android.test.base plus +// the com.android.internal.util.Predicate[s] classes. This is only +// intended for inclusion in the android.test.legacy static library and +// must not be used elsewhere. +java_library_static { + name: "android.test.base-minus-junit", + + srcs: [ + "src/android/**/*.java", + "src/com/**/*.java", + ], + + sdk_version: "current", + libs: [ + "junit", + ], +} + // Build the legacy-android-test library // ===================================== // This contains the android.test classes that were in Android API level 25, diff --git a/test-runner/Android.mk b/test-runner/Android.mk index 706f6364ef8..f5c2bc69049 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -117,5 +117,20 @@ update-android-test-runner-api: $(ANDROID_TEST_RUNNER_OUTPUT_API_FILE) | $(ACP) endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true +# Build the android.test.legacy library +# ===================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := android.test.legacy + +LOCAL_SRC_FILES := $(call all-java-files-under, src/android) + +LOCAL_SDK_VERSION := current + +LOCAL_JAVA_LIBRARIES := android.test.mock.stubs junit +LOCAL_STATIC_JAVA_LIBRARIES := android.test.base-minus-junit + +include $(BUILD_STATIC_JAVA_LIBRARY) + # additionally, build unit tests in a separate .apk include $(call all-makefiles-under,$(LOCAL_PATH)) -- GitLab From d00221cfc681539bc0b0d5a1876bf58069e458f4 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Tue, 7 Nov 2017 07:33:11 +0000 Subject: [PATCH 082/416] Use prebuilt android.test. stubs jars for app builds (cherry picked from commit e144602d9c94d46b2da11d70f526d747b5e05750) Bug: 30188076 Test: tapas Launcher3 Change-Id: I48864d48098cfa02e15a6eaf8e4d6b53afc5b56c Merged-In: If632c39c9d98d89d597d410ebc7973903c9fa91e --- test-runner/Android.mk | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test-runner/Android.mk b/test-runner/Android.mk index 37cd0848cac..67f1354d52b 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -16,6 +16,9 @@ LOCAL_PATH:= $(call my-dir) +# For unbundled build we'll use the prebuilt jar from prebuilts/sdk. +ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK))) + # Generate the stub source files for android.test.runner.stubs # ============================================================ include $(CLEAR_VARS) @@ -111,3 +114,5 @@ update-android-test-runner-api: $(ANDROID_TEST_RUNNER_OUTPUT_API_FILE) | $(ACP) $(hide) $(ACP) $(ANDROID_TEST_RUNNER_OUTPUT_API_FILE) $(ANDROID_TEST_RUNNER_API_FILE) @echo Copying removed.txt $(hide) $(ACP) $(ANDROID_TEST_RUNNER_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_RUNNER_REMOVED_API_FILE) + +endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true -- GitLab From 1116291cecc0de5b426fa6307c1b13f7d90259e1 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Fri, 26 Jan 2018 15:10:36 +0000 Subject: [PATCH 083/416] Add android.test.legacy.jar to SDK build This is needed in order to create a prebuilts version of this JAR that can be used for unbundled builds. Bug: 30188076 Test: wait for pi-release build and check (cherry picked from commit af06ed42b5316a12b0c37d4296cc86cb112d0021) Change-Id: I0ea83bc88b6107f4154a9ca02f13b7c8cb367fea --- test-runner/Android.mk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test-runner/Android.mk b/test-runner/Android.mk index f5c2bc69049..229a6ac05bb 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -132,5 +132,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := android.test.base-minus-junit include $(BUILD_STATIC_JAVA_LIBRARY) +# Archive a copy of the classes.jar in SDK build. +$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.legacy.jar) + # additionally, build unit tests in a separate .apk include $(call all-makefiles-under,$(LOCAL_PATH)) -- GitLab From cc600d6d47572a338ec9836b9e8232172f065ae1 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Mon, 11 Dec 2017 15:13:08 +0000 Subject: [PATCH 084/416] Build test-runner/tests Previous change e254526f0fe5d22681555bd4a00b7ee96fee1dc1 inadvertently removed the line to include the tests/Android.mk file. (cherry picked from commit 006b7a2b760b89211b9530804118a8333cee314b) Bug: 30188076 Test: make checkbuild Change-Id: I59bd6ec5d317eb6306642974902e4c061e594aa8 Merged-In: Ia0ba14a70d2232d464420265a7a5f9c4dde3661b --- test-runner/Android.mk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test-runner/Android.mk b/test-runner/Android.mk index 67f1354d52b..706f6364ef8 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -116,3 +116,6 @@ update-android-test-runner-api: $(ANDROID_TEST_RUNNER_OUTPUT_API_FILE) | $(ACP) $(hide) $(ACP) $(ANDROID_TEST_RUNNER_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_RUNNER_REMOVED_API_FILE) endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true + +# additionally, build unit tests in a separate .apk +include $(call all-makefiles-under,$(LOCAL_PATH)) -- GitLab From 4fa8064378770a0e05b7462c173bfb74cb540f37 Mon Sep 17 00:00:00 2001 From: Insun Kang Date: Tue, 30 Jan 2018 16:44:19 +0900 Subject: [PATCH 085/416] VideoView: Change showSubtitle() to get boolean parameter - Adds VideoView2 attributes - enableControlView - showSubtitle (boolean) - viewType (enum) - surfaceView - textureView - showSubtitle() --> showSubtitle(boolean) - hideSubtitle() removed. Test: build Change-Id: Ib21722af1c9c1caf036e047a18d27d46097e8f03 --- core/java/android/widget/VideoView2.java | 20 +++++++------------ core/res/res/values/attrs.xml | 10 ++++++++++ .../media/update/VideoView2Provider.java | 3 +-- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java index ac83313bd83..8404223d91c 100644 --- a/core/java/android/widget/VideoView2.java +++ b/core/java/android/widget/VideoView2.java @@ -112,14 +112,14 @@ public class VideoView2 extends FrameLayout { public @interface ViewType {} /** - * Indicates video is rendering on SurfaceView + * Indicates video is rendering on SurfaceView. * * @see #setViewType */ public static final int VIEW_TYPE_SURFACEVIEW = 1; /** - * Indicates video is rendering on TextureView + * Indicates video is rendering on TextureView. * * @see #setViewType */ @@ -188,18 +188,12 @@ public class VideoView2 extends FrameLayout { } /** - * Starts rendering closed caption or subtitles if there is any. The first subtitle track will - * be chosen by default if there multiple subtitle tracks exist. + * Shows or hides closed caption or subtitles if there is any. + * The first subtitle track will be chosen by default if there multiple subtitle tracks exist. + * @param show shows closed caption or subtitles if this value is true, or hides. */ - public void showSubtitle() { - mProvider.showSubtitle_impl(); - } - - /** - * Stops showing closed captions or subtitles. - */ - public void hideSubtitle() { - mProvider.hideSubtitle_impl(); + public void showSubtitle(boolean show) { + mProvider.showSubtitle_impl(show); } /** diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 5184dda7025..0543305f988 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -8773,6 +8773,16 @@ + + + + + + + + + + diff --git a/media/java/android/media/update/VideoView2Provider.java b/media/java/android/media/update/VideoView2Provider.java index c5b58f7bb61..10f03d223d9 100644 --- a/media/java/android/media/update/VideoView2Provider.java +++ b/media/java/android/media/update/VideoView2Provider.java @@ -47,8 +47,7 @@ public interface VideoView2Provider extends ViewProvider { void setMediaControlView2_impl(MediaControlView2 mediaControlView); MediaController getMediaController_impl(); MediaControlView2 getMediaControlView2_impl(); - void showSubtitle_impl(); - void hideSubtitle_impl(); + void showSubtitle_impl(boolean show); // TODO: remove setSpeed_impl once MediaController2 is ready. void setSpeed_impl(float speed); void setAudioFocusRequest_impl(int focusGain); -- GitLab From 898e7de6c71e00e11f299b67bd62d4af5fd12ca2 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Tue, 30 Jan 2018 13:01:30 +0000 Subject: [PATCH 086/416] Create test-legacy/ for android.test.legacy target The android.test.legacy (and legacy-android-test) target depends on code from both test-base/ and test-runner/ and do not really belong in either folder. Having a separate folder will also provide a convenient place for the artifacts needed to publish android.test.legacy to maven.google.com. Bug: 30188076 Test: make checkbuild Change-Id: Ied54c24694b3167fcf9075a3157e92ec53b8f636 --- test-base/Android.bp | 25 ------------------------- test-legacy/Android.bp | 36 ++++++++++++++++++++++++++++++++++++ test-legacy/Android.mk | 40 ++++++++++++++++++++++++++++++++++++++++ test-runner/Android.mk | 18 ------------------ 4 files changed, 76 insertions(+), 43 deletions(-) create mode 100644 test-legacy/Android.bp create mode 100644 test-legacy/Android.mk diff --git a/test-base/Android.bp b/test-base/Android.bp index ccf57b00a37..62fed61da27 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -83,28 +83,3 @@ java_library_static { "junit", ], } - -// Build the legacy-android-test library -// ===================================== -// This contains the android.test classes that were in Android API level 25, -// including those from android.test.runner. -// Also contains the com.android.internal.util.Predicate[s] classes. -java_library_static { - name: "legacy-android-test", - - srcs: [ - "src/android/**/*.java", - "src/com/**/*.java", - ], - - static_libs: [ - "android.test.runner-minus-junit", - "android.test.mock", - ], - - no_framework_libs: true, - libs: [ - "framework", - "junit", - ], -} diff --git a/test-legacy/Android.bp b/test-legacy/Android.bp new file mode 100644 index 00000000000..d2af8a9f1c8 --- /dev/null +++ b/test-legacy/Android.bp @@ -0,0 +1,36 @@ +// +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Build the legacy-android-test library +// ===================================== +// This contains the android.test classes that were in Android API level 25, +// including those from android.test.runner. +// Also contains the com.android.internal.util.Predicate[s] classes. +java_library_static { + name: "legacy-android-test", + + static_libs: [ + "android.test.base-minus-junit", + "android.test.runner-minus-junit", + "android.test.mock", + ], + + no_framework_libs: true, + libs: [ + "framework", + "junit", + ], +} diff --git a/test-legacy/Android.mk b/test-legacy/Android.mk new file mode 100644 index 00000000000..b8c53266b9f --- /dev/null +++ b/test-legacy/Android.mk @@ -0,0 +1,40 @@ +# +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) + +# For unbundled build we'll use the prebuilt jar from prebuilts/sdk. +ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK))) + +# Build the android.test.legacy library +# ===================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := android.test.legacy + +LOCAL_SDK_VERSION := current + +LOCAL_JAVA_LIBRARIES := junit +LOCAL_STATIC_JAVA_LIBRARIES := \ + android.test.base-minus-junit \ + android.test.runner-minus-junit \ + +include $(BUILD_STATIC_JAVA_LIBRARY) + +# Archive a copy of the classes.jar in SDK build. +$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.legacy.jar) + +endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true diff --git a/test-runner/Android.mk b/test-runner/Android.mk index 229a6ac05bb..706f6364ef8 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -117,23 +117,5 @@ update-android-test-runner-api: $(ANDROID_TEST_RUNNER_OUTPUT_API_FILE) | $(ACP) endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true -# Build the android.test.legacy library -# ===================================== -include $(CLEAR_VARS) - -LOCAL_MODULE := android.test.legacy - -LOCAL_SRC_FILES := $(call all-java-files-under, src/android) - -LOCAL_SDK_VERSION := current - -LOCAL_JAVA_LIBRARIES := android.test.mock.stubs junit -LOCAL_STATIC_JAVA_LIBRARIES := android.test.base-minus-junit - -include $(BUILD_STATIC_JAVA_LIBRARY) - -# Archive a copy of the classes.jar in SDK build. -$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.legacy.jar) - # additionally, build unit tests in a separate .apk include $(call all-makefiles-under,$(LOCAL_PATH)) -- GitLab From f641c0977225d5b2477278ead25d03f85fe9fcce Mon Sep 17 00:00:00 2001 From: Jaewan Kim Date: Tue, 30 Jan 2018 15:31:51 +0900 Subject: [PATCH 087/416] MediaSession2: Move MediaMetadata2 to updatable Bug: 72670468 Test: Run all MediaComponents test once Change-Id: I2351f63e00a3ed6adfe5a61b995fd8143d67bb35 --- media/java/android/media/MediaItem2.java | 3 +- media/java/android/media/MediaMetadata2.java | 277 +++++------------- media/java/android/media/MediaSession2.java | 12 +- .../media/update/MediaMetadata2Provider.java | 37 +++ .../android/media/update/StaticProvider.java | 11 +- 5 files changed, 126 insertions(+), 214 deletions(-) create mode 100644 media/java/android/media/update/MediaMetadata2Provider.java diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java index 2e9894b1da6..eae4436cae7 100644 --- a/media/java/android/media/MediaItem2.java +++ b/media/java/android/media/MediaItem2.java @@ -24,7 +24,6 @@ import android.content.Context; import android.media.update.ApiLoader; import android.media.update.MediaItem2Provider; import android.os.Bundle; -import android.text.TextUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -69,7 +68,7 @@ public class MediaItem2 { public MediaItem2(@NonNull Context context, @NonNull String mediaId, @NonNull DataSourceDesc dsd, @Nullable MediaMetadata2 metadata, @Flags int flags) { - mProvider = ApiLoader.getProvider(context).createMediaItem2Provider( + mProvider = ApiLoader.getProvider(context).createMediaItem2( context, this, mediaId, dsd, metadata, flags); } diff --git a/media/java/android/media/MediaMetadata2.java b/media/java/android/media/MediaMetadata2.java index fcdb4f75edc..54a9057eeb2 100644 --- a/media/java/android/media/MediaMetadata2.java +++ b/media/java/android/media/MediaMetadata2.java @@ -16,17 +16,16 @@ package android.media; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; +import android.annotation.SystemApi; +import android.content.Context; import android.graphics.Bitmap; +import android.media.update.ApiLoader; +import android.media.update.MediaMetadata2Provider; import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; -import android.util.Log; -import android.util.ArrayMap; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -37,9 +36,11 @@ import java.util.Set; * * @hide */ -// TODO(jaewan): Move this to updatable public final class MediaMetadata2 { - private static final String TAG = "MediaMetadata2"; + // New version of MediaMetadata that no longer implements Parcelable but added from/toBundle() + // for updatable. + // MediaDescription is deprecated because it was insufficient for controller to display media + // contents. Added getExtra() here to support all the features from the MediaDescription. /** * The title of the media. @@ -365,76 +366,14 @@ public final class MediaMetadata2 { @Retention(RetentionPolicy.SOURCE) public @interface RatingKey {} - static final int METADATA_TYPE_LONG = 0; - static final int METADATA_TYPE_TEXT = 1; - static final int METADATA_TYPE_BITMAP = 2; - static final int METADATA_TYPE_RATING = 3; - static final ArrayMap METADATA_KEYS_TYPE; - - static { - METADATA_KEYS_TYPE = new ArrayMap(); - METADATA_KEYS_TYPE.put(METADATA_KEY_TITLE, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_ARTIST, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_DURATION, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_AUTHOR, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_WRITER, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_COMPOSER, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_COMPILATION, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_DATE, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_YEAR, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_GENRE, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_TRACK_NUMBER, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_NUM_TRACKS, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_DISC_NUMBER, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ARTIST, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_ART, METADATA_TYPE_BITMAP); - METADATA_KEYS_TYPE.put(METADATA_KEY_ART_URI, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART, METADATA_TYPE_BITMAP); - METADATA_KEYS_TYPE.put(METADATA_KEY_ALBUM_ART_URI, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_USER_RATING, METADATA_TYPE_RATING); - METADATA_KEYS_TYPE.put(METADATA_KEY_RATING, METADATA_TYPE_RATING); - METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_TITLE, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_SUBTITLE, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_DESCRIPTION, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON, METADATA_TYPE_BITMAP); - METADATA_KEYS_TYPE.put(METADATA_KEY_DISPLAY_ICON_URI, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_ID, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_BT_FOLDER_TYPE, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_MEDIA_URI, METADATA_TYPE_TEXT); - METADATA_KEYS_TYPE.put(METADATA_KEY_ADVERTISEMENT, METADATA_TYPE_LONG); - METADATA_KEYS_TYPE.put(METADATA_KEY_DOWNLOAD_STATUS, METADATA_TYPE_LONG); - } - - private static final @TextKey String[] PREFERRED_DESCRIPTION_ORDER = { - METADATA_KEY_TITLE, - METADATA_KEY_ARTIST, - METADATA_KEY_ALBUM, - METADATA_KEY_ALBUM_ARTIST, - METADATA_KEY_WRITER, - METADATA_KEY_AUTHOR, - METADATA_KEY_COMPOSER - }; - - private static final @BitmapKey String[] PREFERRED_BITMAP_ORDER = { - METADATA_KEY_DISPLAY_ICON, - METADATA_KEY_ART, - METADATA_KEY_ALBUM_ART - }; - - private static final @TextKey String[] PREFERRED_URI_ORDER = { - METADATA_KEY_DISPLAY_ICON_URI, - METADATA_KEY_ART_URI, - METADATA_KEY_ALBUM_ART_URI - }; - - final Bundle mBundle; + private final MediaMetadata2Provider mProvider; /** * @hide */ - public MediaMetadata2(Bundle bundle) { - mBundle = new Bundle(bundle); + @SystemApi + public MediaMetadata2(MediaMetadata2Provider provider) { + mProvider = provider; } /** @@ -443,8 +382,8 @@ public final class MediaMetadata2 { * @param key a String key * @return true if the key exists in this metadata, false otherwise */ - public boolean containsKey(String key) { - return mBundle.containsKey(key); + public boolean containsKey(@NonNull String key) { + return mProvider.containsKey_impl(key); } /** @@ -455,8 +394,8 @@ public final class MediaMetadata2 { * @param key The key the value is stored under * @return a CharSequence value, or null */ - public CharSequence getText(@TextKey String key) { - return mBundle.getCharSequence(key); + public @Nullable CharSequence getText(@TextKey String key) { + return mProvider.getText_impl(key); } /** @@ -464,11 +403,11 @@ public final class MediaMetadata2 { * the desired type exists for the given key or a null value is explicitly * associated with the key. * - * @ * @return media id. Can be {@code null} + * @see #METADATA_KEY_MEDIA_ID */ public @Nullable String getMediaId() { - return getString(METADATA_KEY_MEDIA_ID); + return mProvider.getMediaId_impl(); } /** @@ -479,12 +418,8 @@ public final class MediaMetadata2 { * @param key The key the value is stored under * @return a String value, or null */ - public String getString(@TextKey String key) { - CharSequence text = mBundle.getCharSequence(key); - if (text != null) { - return text.toString(); - } - return null; + public @Nullable String getString(@NonNull @TextKey String key) { + return mProvider.getString_impl(key); } /** @@ -494,8 +429,8 @@ public final class MediaMetadata2 { * @param key The key the value is stored under * @return a long value */ - public long getLong(@LongKey String key) { - return mBundle.getLong(key, 0); + public long getLong(@NonNull @LongKey String key) { + return mProvider.getLong_impl(key); } /** @@ -503,18 +438,10 @@ public final class MediaMetadata2 { * the given key. * * @param key The key the value is stored under - * @return A {@link Rating2} or null - */ - public Rating2 getRating(@RatingKey String key) { - // TODO(jaewan): Add backward compatibility - Rating2 rating = null; - try { - rating = Rating2.fromBundle(mBundle.getBundle(key)); - } catch (Exception e) { - // ignore, value was not a rating - Log.w(TAG, "Failed to retrieve a key as Rating.", e); - } - return rating; + * @return A {@link Rating2} or {@code null} + */ + public @Nullable Rating2 getRating(@RatingKey String key) { + return mProvider.getRating_impl(key); } /** @@ -525,14 +452,7 @@ public final class MediaMetadata2 { * @return A {@link Bitmap} or null */ public Bitmap getBitmap(@BitmapKey String key) { - Bitmap bmp = null; - try { - bmp = mBundle.getParcelable(key); - } catch (Exception e) { - // ignore, value was not a bitmap - Log.w(TAG, "Failed to retrieve a key as Bitmap.", e); - } - return bmp; + return mProvider.getBitmap_impl(key); } /** @@ -540,14 +460,8 @@ public final class MediaMetadata2 { * * @return A {@link Bundle} or {@code null} */ - public Bundle getExtra() { - try { - return mBundle.getBundle(METADATA_KEY_EXTRA); - } catch (Exception e) { - // ignore, value was not an bundle - Log.w(TAG, "Failed to retrieve an extra"); - } - return null; + public @Nullable Bundle getExtra() { + return mProvider.getExtra_impl(); } /** @@ -556,7 +470,7 @@ public final class MediaMetadata2 { * @return The number of fields in the metadata. */ public int size() { - return mBundle.size(); + return mProvider.size_impl(); } /** @@ -564,8 +478,8 @@ public final class MediaMetadata2 { * * @return a Set of String keys */ - public Set keySet() { - return mBundle.keySet(); + public @NonNull Set keySet() { + return mProvider.keySet_impl(); } /** @@ -574,8 +488,21 @@ public final class MediaMetadata2 { * * @return The Bundle backing this metadata. */ - public Bundle getBundle() { - return mBundle; + public @NonNull Bundle toBundle() { + return mProvider.toBundle_impl(); + } + + /** + * Creates the {@link MediaMetadata2} from the bundle that previously returned by + * {@link #toBundle()}. + * + * @param context context + * @param bundle bundle for the metadata + * @return a new MediaMetadata2 + */ + public static @NonNull MediaMetadata2 fromBundle(@NonNull Context context, + @Nullable Bundle bundle) { + return ApiLoader.getProvider(context).fromBundle_MediaMetadata2(context, bundle); } /** @@ -583,14 +510,15 @@ public final class MediaMetadata2 { * use the appropriate data type. */ public static final class Builder { - private final Bundle mBundle; + private final MediaMetadata2Provider.BuilderProvider mProvider; /** * Create an empty Builder. Any field that should be included in the * {@link MediaMetadata2} must be added. */ - public Builder() { - mBundle = new Bundle(); + public Builder(@NonNull Context context) { + mProvider = ApiLoader.getProvider(context).createMediaMetadata2Builder( + context, this); } /** @@ -600,31 +528,17 @@ public final class MediaMetadata2 { * * @param source */ - public Builder(MediaMetadata2 source) { - mBundle = new Bundle(source.mBundle); + public Builder(@NonNull Context context, @NonNull MediaMetadata2 source) { + mProvider = ApiLoader.getProvider(context).createMediaMetadata2Builder( + context, this, source); } /** - * Create a Builder using a {@link MediaMetadata2} instance to set - * initial values, but replace bitmaps with a scaled down copy if they - * are larger than maxBitmapSize. - * - * @param source The original metadata to copy. - * @param maxBitmapSize The maximum height/width for bitmaps contained - * in the metadata. * @hide */ - public Builder(MediaMetadata2 source, int maxBitmapSize) { - this(source); - for (String key : mBundle.keySet()) { - Object value = mBundle.get(key); - if (value instanceof Bitmap) { - Bitmap bmp = (Bitmap) value; - if (bmp.getHeight() > maxBitmapSize || bmp.getWidth() > maxBitmapSize) { - putBitmap(key, scaleBitmap(bmp, maxBitmapSize)); - } - } - } + @SystemApi + public Builder(@NonNull MediaMetadata2Provider.BuilderProvider provider) { + mProvider = provider; } /** @@ -653,15 +567,8 @@ public final class MediaMetadata2 { * @param value The CharSequence value to store * @return The Builder to allow chaining */ - public Builder putText(@TextKey String key, CharSequence value) { - if (METADATA_KEYS_TYPE.containsKey(key)) { - if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) { - throw new IllegalArgumentException("The " + key - + " key cannot be used to put a CharSequence"); - } - } - mBundle.putCharSequence(key, value); - return this; + public @NonNull Builder putText(@TextKey String key, @Nullable CharSequence value) { + return mProvider.putText_impl(key, value); } /** @@ -690,15 +597,8 @@ public final class MediaMetadata2 { * @param value The String value to store * @return The Builder to allow chaining */ - public Builder putString(@TextKey String key, String value) { - if (METADATA_KEYS_TYPE.containsKey(key)) { - if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_TEXT) { - throw new IllegalArgumentException("The " + key - + " key cannot be used to put a String"); - } - } - mBundle.putCharSequence(key, value); - return this; + public @NonNull Builder putString(@TextKey String key, @Nullable String value) { + return mProvider.putString_impl(key, value); } /** @@ -720,15 +620,8 @@ public final class MediaMetadata2 { * @param value The String value to store * @return The Builder to allow chaining */ - public Builder putLong(@LongKey String key, long value) { - if (METADATA_KEYS_TYPE.containsKey(key)) { - if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_LONG) { - throw new IllegalArgumentException("The " + key - + " key cannot be used to put a long"); - } - } - mBundle.putLong(key, value); - return this; + public @NonNull Builder putLong(@NonNull @LongKey String key, long value) { + return mProvider.putLong_impl(key, value); } /** @@ -744,16 +637,8 @@ public final class MediaMetadata2 { * @param value The String value to store * @return The Builder to allow chaining */ - public Builder putRating(@RatingKey String key, Rating2 value) { - if (METADATA_KEYS_TYPE.containsKey(key)) { - if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_RATING) { - throw new IllegalArgumentException("The " + key - + " key cannot be used to put a Rating"); - } - } - mBundle.putBundle(key, value.toBundle()); - - return this; + public @NonNull Builder putRating(@NonNull @RatingKey String key, @Nullable Rating2 value) { + return mProvider.putRating_impl(key, value); } /** @@ -774,23 +659,15 @@ public final class MediaMetadata2 { * @param value The Bitmap to store * @return The Builder to allow chaining */ - public Builder putBitmap(@BitmapKey String key, Bitmap value) { - if (METADATA_KEYS_TYPE.containsKey(key)) { - if (METADATA_KEYS_TYPE.get(key) != METADATA_TYPE_BITMAP) { - throw new IllegalArgumentException("The " + key - + " key cannot be used to put a Bitmap"); - } - } - mBundle.putParcelable(key, value); - return this; + public @NonNull Builder putBitmap(@NonNull @BitmapKey String key, @Nullable Bitmap value) { + return mProvider.putBitmap_impl(key, value); } /** * Set an extra {@link Bundle} into the metadata. */ - public Builder setExtra(Bundle bundle) { - mBundle.putBundle(METADATA_KEY_EXTRA, bundle); - return this; + public @NonNull Builder setExtra(@Nullable Bundle bundle) { + return mProvider.setExtra_impl(bundle); } /** @@ -798,18 +675,8 @@ public final class MediaMetadata2 { * * @return The new MediaMetadata2 instance */ - public MediaMetadata2 build() { - return new MediaMetadata2(mBundle); - } - - private Bitmap scaleBitmap(Bitmap bmp, int maxSize) { - float maxSizeF = maxSize; - float widthScale = maxSizeF / bmp.getWidth(); - float heightScale = maxSizeF / bmp.getHeight(); - float scale = Math.min(widthScale, heightScale); - int height = (int) (bmp.getHeight() * scale); - int width = (int) (bmp.getWidth() * scale); - return Bitmap.createScaledBitmap(bmp, width, height, true); + public @NonNull MediaMetadata2 build() { + return mProvider.build_impl(); } } } diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 0ea1e860416..0d23147d8d9 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -568,7 +568,7 @@ public class MediaSession2 implements AutoCloseable { public ControllerInfo(Context context, int uid, int pid, String packageName, IInterface callback) { mProvider = ApiLoader.getProvider(context) - .createMediaSession2ControllerInfoProvider( + .createMediaSession2ControllerInfo( context, this, uid, pid, packageName, callback); } @@ -894,7 +894,7 @@ public class MediaSession2 implements AutoCloseable { bundle.putInt(KEY_REPEAT_MODE, mRepeatMode); bundle.putInt(KEY_SHUFFLE_MODE, mShuffleMode); if (mPlaylistMetadata != null) { - bundle.putBundle(KEY_MEDIA_METADATA2_BUNDLE, mPlaylistMetadata.getBundle()); + bundle.putBundle(KEY_MEDIA_METADATA2_BUNDLE, mPlaylistMetadata.toBundle()); } return bundle; } @@ -916,8 +916,12 @@ public class MediaSession2 implements AutoCloseable { } Bundle metadataBundle = bundle.getBundle(KEY_MEDIA_METADATA2_BUNDLE); - MediaMetadata2 metadata = - metadataBundle == null ? null : new MediaMetadata2(metadataBundle); + MediaMetadata2 metadata = null; + // TODO(jaewan): Uncomment here when the PlaylistParam becomes updatable. + /* + MediaMetadata2 metadata = metadataBundle == null + ? null : MediaMetadata2.fromBundle(mContext, metadataBundle); + */ return new PlaylistParams( bundle.getInt(KEY_REPEAT_MODE), diff --git a/media/java/android/media/update/MediaMetadata2Provider.java b/media/java/android/media/update/MediaMetadata2Provider.java new file mode 100644 index 00000000000..55ac43d797d --- /dev/null +++ b/media/java/android/media/update/MediaMetadata2Provider.java @@ -0,0 +1,37 @@ +package android.media.update; + +import android.graphics.Bitmap; +import android.media.MediaMetadata2; +import android.media.MediaMetadata2.Builder; +import android.media.Rating2; +import android.os.Bundle; + +import java.util.Set; + +/** + * @hide + */ +// TODO(jaewan): SystemApi +public interface MediaMetadata2Provider { + boolean containsKey_impl(String key); + CharSequence getText_impl(String key); + String getMediaId_impl(); + String getString_impl(String key); + long getLong_impl(String key); + Rating2 getRating_impl(String key); + Bundle toBundle_impl(); + Set keySet_impl(); + int size_impl(); + Bitmap getBitmap_impl(String key); + Bundle getExtra_impl(); + + interface BuilderProvider { + Builder putText_impl(String key, CharSequence value); + Builder putString_impl(String key, String value); + Builder putLong_impl(String key, long value); + Builder putRating_impl(String key, Rating2 value); + Builder putBitmap_impl(String key, Bitmap value); + Builder setExtra_impl(Bundle bundle); + MediaMetadata2 build_impl(); + } +} diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 9648b272afa..169c70b301e 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -71,11 +71,10 @@ public interface StaticProvider { CommandGroupProvider createMediaSession2CommandGroup(Context context, MediaSession2.CommandGroup instance, MediaSession2.CommandGroup others); MediaSession2.CommandGroup fromBundle_MediaSession2CommandGroup(Context context, Bundle bundle); - ControllerInfoProvider createMediaSession2ControllerInfoProvider(Context context, + ControllerInfoProvider createMediaSession2ControllerInfo(Context context, MediaSession2.ControllerInfo instance, int uid, int pid, String packageName, IInterface callback); - MediaController2Provider createMediaController2(Context context, MediaController2 instance, SessionToken2 token, Executor executor, ControllerCallback callback); @@ -96,7 +95,13 @@ public interface StaticProvider { SessionPlayer2Provider createSessionPlayer2(Context context, SessionPlayer2 instance); - MediaItem2Provider createMediaItem2Provider(Context context, MediaItem2 mediaItem2, + MediaItem2Provider createMediaItem2(Context context, MediaItem2 mediaItem2, String mediaId, DataSourceDesc dsd, MediaMetadata2 metadata, int flags); MediaItem2 fromBundle_MediaItem2(Context context, Bundle bundle); + + MediaMetadata2 fromBundle_MediaMetadata2(Context context, Bundle bundle); + MediaMetadata2Provider.BuilderProvider createMediaMetadata2Builder( + Context context, MediaMetadata2.Builder builder); + MediaMetadata2Provider.BuilderProvider createMediaMetadata2Builder( + Context context, MediaMetadata2.Builder builder, MediaMetadata2 source); } -- GitLab From 3117c0c2634bc7a065770aafce0325f135c576d7 Mon Sep 17 00:00:00 2001 From: Jaewan Kim Date: Tue, 30 Jan 2018 16:34:54 +0900 Subject: [PATCH 088/416] MediaSession2: Move MediaSession2.PlaylistParams to updatable Bug: 72670266 Test: Run all MediaComponents tests once Change-Id: I5bf0ae9b97f2f60baabfe05e9df893308ee8d41a --- media/java/android/media/MediaSession2.java | 108 ++++++++---------- .../media/update/MediaSession2Provider.java | 8 ++ .../android/media/update/StaticProvider.java | 7 +- 3 files changed, 62 insertions(+), 61 deletions(-) diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 0d23147d8d9..2d55cc6fc7a 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -795,7 +795,7 @@ public class MediaSession2 implements AutoCloseable { /** * Parameter for the playlist. */ - public static class PlaylistParams { + public final static class PlaylistParams { /** * @hide */ @@ -850,83 +850,71 @@ public class MediaSession2 implements AutoCloseable { */ public static final int SHUFFLE_MODE_GROUP = 2; + + private final MediaSession2Provider.PlaylistParamsProvider mProvider; + /** - * Keys used for converting a PlaylistParams object to a bundle object and vice versa. + * Instantiate {@link PlaylistParams} + * + * @param context context + * @param repeatMode repeat mode + * @param shuffleMode shuffle mode + * @param playlistMetadata metadata for the list */ - private static final String KEY_REPEAT_MODE = - "android.media.session2.playlistparams2.repeat_mode"; - private static final String KEY_SHUFFLE_MODE = - "android.media.session2.playlistparams2.shuffle_mode"; - private static final String KEY_MEDIA_METADATA2_BUNDLE = - "android.media.session2.playlistparams2.metadata2_bundle"; - - private @RepeatMode int mRepeatMode; - private @ShuffleMode int mShuffleMode; - - private MediaMetadata2 mPlaylistMetadata; - - public PlaylistParams(@RepeatMode int repeatMode, @ShuffleMode int shuffleMode, - @Nullable MediaMetadata2 playlistMetadata) { - mRepeatMode = repeatMode; - mShuffleMode = shuffleMode; - mPlaylistMetadata = playlistMetadata; + public PlaylistParams(@NonNull Context context, @RepeatMode int repeatMode, + @ShuffleMode int shuffleMode, @Nullable MediaMetadata2 playlistMetadata) { + mProvider = ApiLoader.getProvider(context).createMediaSession2PlaylistParams( + context, this, repeatMode, shuffleMode, playlistMetadata); } - public @RepeatMode int getRepeatMode() { - return mRepeatMode; + /** + * Create a new bundle for this object. + * + * @return + */ + public @NonNull Bundle toBundle() { + return mProvider.toBundle_impl(); } - public @ShuffleMode int getShuffleMode() { - return mShuffleMode; + /** + * Create a new playlist params from the bundle that was previously returned by + * {@link #toBundle}. + * + * @param context context + * @return a new playlist params. Can be {@code null} for error. + */ + public static @Nullable PlaylistParams fromBundle( + @NonNull Context context, @Nullable Bundle bundle) { + return ApiLoader.getProvider(context).fromBundle_PlaylistParams(context, bundle); } - public MediaMetadata2 getPlaylistMetadata() { - return mPlaylistMetadata; + /** + * Get repeat mode + * + * @return repeat mode + * @see #REPEAT_MODE_NONE, #REPEAT_MODE_ONE, #REPEAT_MODE_ALL, #REPEAT_MODE_GROUP + */ + public @RepeatMode int getRepeatMode() { + return mProvider.getRepeatMode_impl(); } /** - * Returns this object as a bundle to share between processes. + * Get shuffle mode * - * @hide + * @return shuffle mode + * @see #SHUFFLE_MODE_NONE, #SHUFFLE_MODE_ALL, #SHUFFLE_MODE_GROUP */ - public Bundle toBundle() { - Bundle bundle = new Bundle(); - bundle.putInt(KEY_REPEAT_MODE, mRepeatMode); - bundle.putInt(KEY_SHUFFLE_MODE, mShuffleMode); - if (mPlaylistMetadata != null) { - bundle.putBundle(KEY_MEDIA_METADATA2_BUNDLE, mPlaylistMetadata.toBundle()); - } - return bundle; + public @ShuffleMode int getShuffleMode() { + return mProvider.getShuffleMode_impl(); } /** - * Creates an instance from a bundle which is previously created by {@link #toBundle()}. + * Get metadata for the playlist * - * @param bundle A bundle created by {@link #toBundle()}. - * @return A new {@link PlaylistParams} instance. Returns {@code null} if the given - * {@param bundle} is null, or if the {@param bundle} has no playlist parameters. - * @hide + * @return metadata. Can be {@code null} */ - public static PlaylistParams fromBundle(Bundle bundle) { - if (bundle == null) { - return null; - } - if (!bundle.containsKey(KEY_REPEAT_MODE) || !bundle.containsKey(KEY_SHUFFLE_MODE)) { - return null; - } - - Bundle metadataBundle = bundle.getBundle(KEY_MEDIA_METADATA2_BUNDLE); - MediaMetadata2 metadata = null; - // TODO(jaewan): Uncomment here when the PlaylistParam becomes updatable. - /* - MediaMetadata2 metadata = metadataBundle == null - ? null : MediaMetadata2.fromBundle(mContext, metadataBundle); - */ - - return new PlaylistParams( - bundle.getInt(KEY_REPEAT_MODE), - bundle.getInt(KEY_SHUFFLE_MODE), - metadata); + public @Nullable MediaMetadata2 getPlaylistMetadata() { + return mProvider.getPlaylistMetadata_impl(); } } diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java index f9e29d96f74..0c8c2e37104 100644 --- a/media/java/android/media/update/MediaSession2Provider.java +++ b/media/java/android/media/update/MediaSession2Provider.java @@ -17,6 +17,7 @@ package android.media.update; import android.media.MediaItem2; +import android.media.MediaMetadata2; import android.media.MediaPlayerInterface; import android.media.MediaPlayerInterface.PlaybackListener; import android.media.MediaSession2.Command; @@ -87,4 +88,11 @@ public interface MediaSession2Provider extends TransportControlProvider { int hashCode_impl(); boolean equals_impl(ControllerInfoProvider obj); } + + interface PlaylistParamsProvider { + int getRepeatMode_impl(); + int getShuffleMode_impl(); + MediaMetadata2 getPlaylistMetadata_impl(); + Bundle toBundle_impl(); + } } diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 169c70b301e..39e27f4faa4 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -31,6 +31,7 @@ import android.media.MediaLibraryService2.MediaLibrarySessionCallback; import android.media.MediaMetadata2; import android.media.MediaPlayerInterface; import android.media.MediaSession2; +import android.media.MediaSession2.PlaylistParams; import android.media.MediaSession2.SessionCallback; import android.media.MediaSessionService2; import android.media.SessionPlayer2; @@ -40,6 +41,7 @@ import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProv import android.media.update.MediaSession2Provider.CommandGroupProvider; import android.media.update.MediaSession2Provider.CommandProvider; import android.media.update.MediaSession2Provider.ControllerInfoProvider; +import android.media.update.MediaSession2Provider.PlaylistParamsProvider; import android.os.Bundle; import android.os.IInterface; import android.util.AttributeSet; @@ -74,7 +76,10 @@ public interface StaticProvider { ControllerInfoProvider createMediaSession2ControllerInfo(Context context, MediaSession2.ControllerInfo instance, int uid, int pid, String packageName, IInterface callback); - + PlaylistParamsProvider createMediaSession2PlaylistParams(Context context, + PlaylistParams playlistParams, int repeatMode, int shuffleMode, + MediaMetadata2 playlistMetadata); + PlaylistParams fromBundle_PlaylistParams(Context context, Bundle bundle); MediaController2Provider createMediaController2(Context context, MediaController2 instance, SessionToken2 token, Executor executor, ControllerCallback callback); -- GitLab From bb6ca486d1224e5d8c9427079a44223f55ce51c1 Mon Sep 17 00:00:00 2001 From: Jaewan Kim Date: Tue, 30 Jan 2018 18:31:10 +0900 Subject: [PATCH 089/416] MediaSession2: Move Rating2 to updatable Test: Run all MediaComponents tests once Bug: 72670051 Change-Id: I5c405e34569c1ccc469dad09bbc8aad1cd7fdcf4 --- media/java/android/media/Rating2.java | 154 +++++++----------- .../android/media/update/Rating2Provider.java | 37 +++++ .../android/media/update/StaticProvider.java | 8 + 3 files changed, 107 insertions(+), 92 deletions(-) create mode 100644 media/java/android/media/update/Rating2Provider.java diff --git a/media/java/android/media/Rating2.java b/media/java/android/media/Rating2.java index 93aea6f9c1d..4f77ecd5814 100644 --- a/media/java/android/media/Rating2.java +++ b/media/java/android/media/Rating2.java @@ -16,7 +16,13 @@ package android.media; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.IntDef; +import android.annotation.SystemApi; +import android.content.Context; +import android.media.update.ApiLoader; +import android.media.update.Rating2Provider; import android.os.Bundle; import android.util.Log; @@ -33,10 +39,8 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public final class Rating2 { - private static final String TAG = "Rating2"; - - private static final String KEY_STYLE = "android.media.rating2.style"; - private static final String KEY_VALUE = "android.media.rating2.value"; + // Mostly same as the android.media.Rating, but it's no longer implements Parcelable for + // updatable support. /** * @hide @@ -91,31 +95,48 @@ public final class Rating2 { */ public final static int RATING_PERCENTAGE = 6; - private final static float RATING_NOT_RATED = -1.0f; + private final Rating2Provider mProvider; - private final int mRatingStyle; + /** + * @hide + */ + @SystemApi + public Rating2(@NonNull Rating2Provider provider) { + mProvider = provider; + } - private final float mRatingValue; + @Override + public String toString() { + return mProvider.toString_impl(); + } - private Rating2(@Style int ratingStyle, float rating) { - mRatingStyle = ratingStyle; - mRatingValue = rating; + /** + * @hide + */ + @SystemApi + public Rating2Provider getProvider() { + return mProvider; } @Override - public String toString() { - return "Rating2:style=" + mRatingStyle + " rating=" - + (mRatingValue < 0.0f ? "unrated" : String.valueOf(mRatingValue)); + public boolean equals(Object obj) { + return mProvider.equals_impl(obj); + } + + @Override + public int hashCode() { + return mProvider.hashCode_impl(); } /** * Create an instance from bundle object, previoulsy created by {@link #toBundle()} * + * @param context context * @param bundle bundle - * @return new Rating2 instance + * @return new Rating2 instance or {@code null} for error */ - public static Rating2 fromBundle(Bundle bundle) { - return new Rating2(bundle.getInt(KEY_STYLE), bundle.getFloat(KEY_VALUE)); + public static Rating2 fromBundle(@NonNull Context context, @Nullable Bundle bundle) { + return ApiLoader.getProvider(context).fromBundle_Rating2(context, bundle); } /** @@ -123,55 +144,45 @@ public final class Rating2 { * @return bundle of this object */ public Bundle toBundle() { - Bundle bundle = new Bundle(); - bundle.putInt(KEY_STYLE, mRatingStyle); - bundle.putFloat(KEY_VALUE, mRatingValue); - return bundle; + return mProvider.toBundle_impl(); } /** * Return a Rating2 instance with no rating. * Create and return a new Rating2 instance with no rating known for the given * rating style. + * @param context context * @param ratingStyle one of {@link #RATING_HEART}, {@link #RATING_THUMB_UP_DOWN}, * {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, {@link #RATING_5_STARS}, * or {@link #RATING_PERCENTAGE}. * @return null if an invalid rating style is passed, a new Rating2 instance otherwise. */ - public static Rating2 newUnratedRating(@Style int ratingStyle) { - switch(ratingStyle) { - case RATING_HEART: - case RATING_THUMB_UP_DOWN: - case RATING_3_STARS: - case RATING_4_STARS: - case RATING_5_STARS: - case RATING_PERCENTAGE: - return new Rating2(ratingStyle, RATING_NOT_RATED); - default: - return null; - } + public static @Nullable Rating2 newUnratedRating(@NonNull Context context, @Style int ratingStyle) { + return ApiLoader.getProvider(context).newUnratedRating_Rating2(context, ratingStyle); } /** * Return a Rating2 instance with a heart-based rating. * Create and return a new Rating2 instance with a rating style of {@link #RATING_HEART}, * and a heart-based rating. + * @param context context * @param hasHeart true for a "heart selected" rating, false for "heart unselected". * @return a new Rating2 instance. */ - public static Rating2 newHeartRating(boolean hasHeart) { - return new Rating2(RATING_HEART, hasHeart ? 1.0f : 0.0f); + public static @Nullable Rating2 newHeartRating(@NonNull Context context, boolean hasHeart) { + return ApiLoader.getProvider(context).newHeartRating_Rating2(context, hasHeart); } /** * Return a Rating2 instance with a thumb-based rating. * Create and return a new Rating2 instance with a {@link #RATING_THUMB_UP_DOWN} * rating style, and a "thumb up" or "thumb down" rating. + * @param context context * @param thumbIsUp true for a "thumb up" rating, false for "thumb down". * @return a new Rating2 instance. */ - public static Rating2 newThumbRating(boolean thumbIsUp) { - return new Rating2(RATING_THUMB_UP_DOWN, thumbIsUp ? 1.0f : 0.0f); + public static @Nullable Rating2 newThumbRating(@NonNull Context context, boolean thumbIsUp) { + return ApiLoader.getProvider(context).newThumbRating_Rating2(context, thumbIsUp); } /** @@ -179,6 +190,7 @@ public final class Rating2 { * Create and return a new Rating2 instance with one of the star-base rating styles * and the given integer or fractional number of stars. Non integer values can for instance * be used to represent an average rating value, which might not be an integer number of stars. + * @param context context * @param starRatingStyle one of {@link #RATING_3_STARS}, {@link #RATING_4_STARS}, * {@link #RATING_5_STARS}. * @param starRating a number ranging from 0.0f to 3.0f, 4.0f or 5.0f according to @@ -186,51 +198,30 @@ public final class Rating2 { * @return null if the rating style is invalid, or the rating is out of range, * a new Rating2 instance otherwise. */ - public static Rating2 newStarRating(@StarStyle int starRatingStyle, float starRating) { - float maxRating = -1.0f; - switch(starRatingStyle) { - case RATING_3_STARS: - maxRating = 3.0f; - break; - case RATING_4_STARS: - maxRating = 4.0f; - break; - case RATING_5_STARS: - maxRating = 5.0f; - break; - default: - Log.e(TAG, "Invalid rating style (" + starRatingStyle + ") for a star rating"); - return null; - } - if ((starRating < 0.0f) || (starRating > maxRating)) { - Log.e(TAG, "Trying to set out of range star-based rating"); - return null; - } - return new Rating2(starRatingStyle, starRating); + public static @Nullable Rating2 newStarRating(@NonNull Context context, + @StarStyle int starRatingStyle, float starRating) { + return ApiLoader.getProvider(context).newStarRating_Rating2( + context, starRatingStyle, starRating); } /** * Return a Rating2 instance with a percentage-based rating. * Create and return a new Rating2 instance with a {@link #RATING_PERCENTAGE} * rating style, and a rating of the given percentage. + * @param context context * @param percent the value of the rating * @return null if the rating is out of range, a new Rating2 instance otherwise. */ - public static Rating2 newPercentageRating(float percent) { - if ((percent < 0.0f) || (percent > 100.0f)) { - Log.e(TAG, "Invalid percentage-based rating value"); - return null; - } else { - return new Rating2(RATING_PERCENTAGE, percent); - } + public static @Nullable Rating2 newPercentageRating(@NonNull Context context, float percent) { + return ApiLoader.getProvider(context).newPercentageRating_Rating2(context, percent); } /** * Return whether there is a rating value available. - * @return true if the instance was not created with {@link #newUnratedRating(int)}. + * @return true if the instance was not created with {@link #newUnratedRating(Context, int)}. */ public boolean isRated() { - return mRatingValue >= 0.0f; + return mProvider.isRated_impl(); } /** @@ -241,7 +232,7 @@ public final class Rating2 { */ @Style public int getRatingStyle() { - return mRatingStyle; + return mProvider.getRatingStyle_impl(); } /** @@ -250,11 +241,7 @@ public final class Rating2 { * if the rating style is not {@link #RATING_HEART} or if it is unrated. */ public boolean hasHeart() { - if (mRatingStyle != RATING_HEART) { - return false; - } else { - return (mRatingValue == 1.0f); - } + return mProvider.hasHeart_impl(); } /** @@ -263,11 +250,7 @@ public final class Rating2 { * if the rating style is not {@link #RATING_THUMB_UP_DOWN} or if it is unrated. */ public boolean isThumbUp() { - if (mRatingStyle != RATING_THUMB_UP_DOWN) { - return false; - } else { - return (mRatingValue == 1.0f); - } + return mProvider.isThumbUp_impl(); } /** @@ -276,16 +259,7 @@ public final class Rating2 { * not star-based, or if it is unrated. */ public float getStarRating() { - switch (mRatingStyle) { - case RATING_3_STARS: - case RATING_4_STARS: - case RATING_5_STARS: - if (isRated()) { - return mRatingValue; - } - default: - return -1.0f; - } + return mProvider.getStarRating_impl(); } /** @@ -294,10 +268,6 @@ public final class Rating2 { * not percentage-based, or if it is unrated. */ public float getPercentRating() { - if ((mRatingStyle != RATING_PERCENTAGE) || !isRated()) { - return -1.0f; - } else { - return mRatingValue; - } + return mProvider.getPercentRating_impl(); } } diff --git a/media/java/android/media/update/Rating2Provider.java b/media/java/android/media/update/Rating2Provider.java new file mode 100644 index 00000000000..8966196890c --- /dev/null +++ b/media/java/android/media/update/Rating2Provider.java @@ -0,0 +1,37 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +import android.annotation.SystemApi; +import android.os.Bundle; + +/** + * @hide + */ +// TODO(jaewan): @SystemApi +public interface Rating2Provider { + String toString_impl(); + boolean equals_impl(Object obj); + int hashCode_impl(); + Bundle toBundle_impl(); + boolean isRated_impl(); + int getRatingStyle_impl(); + boolean hasHeart_impl(); + boolean isThumbUp_impl(); + float getStarRating_impl(); + float getPercentRating_impl(); +} \ No newline at end of file diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 39e27f4faa4..9282494b32d 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -34,6 +34,7 @@ import android.media.MediaSession2; import android.media.MediaSession2.PlaylistParams; import android.media.MediaSession2.SessionCallback; import android.media.MediaSessionService2; +import android.media.Rating2; import android.media.SessionPlayer2; import android.media.SessionToken2; import android.media.VolumeProvider; @@ -109,4 +110,11 @@ public interface StaticProvider { Context context, MediaMetadata2.Builder builder); MediaMetadata2Provider.BuilderProvider createMediaMetadata2Builder( Context context, MediaMetadata2.Builder builder, MediaMetadata2 source); + + Rating2 newUnratedRating_Rating2(Context context, int ratingStyle); + Rating2 fromBundle_Rating2(Context context, Bundle bundle); + Rating2 newHeartRating_Rating2(Context context, boolean hasHeart); + Rating2 newThumbRating_Rating2(Context context, boolean thumbIsUp); + Rating2 newStarRating_Rating2(Context context, int starRatingStyle, float starRating); + Rating2 newPercentageRating_Rating2(Context context, float percent); } -- GitLab From 7f50956f8af785568f492259e5f670592b3305be Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Tue, 30 Jan 2018 13:42:42 +0000 Subject: [PATCH 090/416] Revert "Delay coming out of doze until keyguard dismissed" This reverts commit 77ee6507962576e8ddc41ee0209158a799e79519. Reason for revert: Occasional deadlock b/72684251 Change-Id: I1eb02bf1fefcce66830e1e0ab34a813262ba40d6 --- .../android/server/DeviceIdleController.java | 35 +------------------ .../server/am/ActivityManagerService.java | 1 - .../NotificationManagerService.java | 16 --------- .../tests/uiservicestests/AndroidManifest.xml | 1 - 4 files changed, 1 insertion(+), 52 deletions(-) diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index fd3f708c6bc..a12c85aef85 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -141,8 +141,6 @@ public class DeviceIdleController extends SystemService private boolean mHasNetworkLocation; private Location mLastGenericLocation; private Location mLastGpsLocation; - // Current locked state of the screen - private boolean mScreenLocked; /** Device is currently active. */ private static final int STATE_ACTIVE = 0; @@ -158,7 +156,6 @@ public class DeviceIdleController extends SystemService private static final int STATE_IDLE = 5; /** Device is in the idle state, but temporarily out of idle to do regular maintenance. */ private static final int STATE_IDLE_MAINTENANCE = 6; - private static String stateToString(int state) { switch (state) { case STATE_ACTIVE: return "ACTIVE"; @@ -550,11 +547,6 @@ public class DeviceIdleController extends SystemService "sms_temp_app_whitelist_duration"; private static final String KEY_NOTIFICATION_WHITELIST_DURATION = "notification_whitelist_duration"; - /** - * Whether to wait for the user to unlock the device before causing screen-on to - * exit doze. Default = true - */ - private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock"; /** * This is the time, after becoming inactive, that we go in to the first @@ -773,8 +765,6 @@ public class DeviceIdleController extends SystemService */ public long NOTIFICATION_WHITELIST_DURATION; - public boolean WAIT_FOR_UNLOCK; - private final ContentResolver mResolver; private final boolean mSmallBatteryDevice; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -865,7 +855,6 @@ public class DeviceIdleController extends SystemService KEY_SMS_TEMP_APP_WHITELIST_DURATION, 20 * 1000L); NOTIFICATION_WHITELIST_DURATION = mParser.getDurationMillis( KEY_NOTIFICATION_WHITELIST_DURATION, 30 * 1000L); - WAIT_FOR_UNLOCK = mParser.getBoolean(KEY_WAIT_FOR_UNLOCK, false); } } @@ -973,9 +962,6 @@ public class DeviceIdleController extends SystemService pw.print(" "); pw.print(KEY_NOTIFICATION_WHITELIST_DURATION); pw.print("="); TimeUtils.formatDuration(NOTIFICATION_WHITELIST_DURATION, pw); pw.println(); - - pw.print(" "); pw.print(KEY_WAIT_FOR_UNLOCK); pw.print("="); - pw.println(WAIT_FOR_UNLOCK); } } @@ -1352,12 +1338,6 @@ public class DeviceIdleController extends SystemService public int[] getPowerSaveTempWhitelistAppIds() { return DeviceIdleController.this.getAppIdTempWhitelistInternal(); } - - public void keyguardShowing(boolean showing) { - synchronized (DeviceIdleController.this) { - DeviceIdleController.this.keyguardShowingLocked(showing); - } - } } public DeviceIdleController(Context context) { @@ -1426,7 +1406,6 @@ public class DeviceIdleController extends SystemService mNetworkConnected = true; mScreenOn = true; - mScreenLocked = false; // Start out assuming we are charging. If we aren't, we will at least get // a battery update the next time the level drops. mCharging = true; @@ -1997,7 +1976,7 @@ public class DeviceIdleController extends SystemService } } else if (screenOn) { mScreenOn = true; - if (!mForceIdle && (!mScreenLocked || !mConstants.WAIT_FOR_UNLOCK)) { + if (!mForceIdle) { becomeActiveLocked("screen", Process.myUid()); } } @@ -2018,17 +1997,6 @@ public class DeviceIdleController extends SystemService } } - void keyguardShowingLocked(boolean showing) { - if (DEBUG) Slog.i(TAG, "keyguardShowing=" + showing); - if (mScreenLocked != showing) { - mScreenLocked = showing; - if (mScreenOn && !mForceIdle && !mScreenLocked) { - becomeActiveLocked("unlocked", Process.myUid()); - } - } - } - - void scheduleReportActiveLocked(String activeReason, int activeUid) { Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason); mHandler.sendMessage(msg); @@ -3340,7 +3308,6 @@ public class DeviceIdleController extends SystemService pw.print(" mForceIdle="); pw.println(mForceIdle); pw.print(" mMotionSensor="); pw.println(mMotionSensor); pw.print(" mScreenOn="); pw.println(mScreenOn); - pw.print(" mScreenLocked="); pw.println(mScreenLocked); pw.print(" mNetworkConnected="); pw.println(mNetworkConnected); pw.print(" mCharging="); pw.println(mCharging); pw.print(" mMotionActive="); pw.println(mMotionListener.active); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index d933b2362ae..1eec982a0a9 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -12788,7 +12788,6 @@ public class ActivityManagerService extends IActivityManager.Stub long ident = Binder.clearCallingIdentity(); try { mKeyguardController.setKeyguardShown(showing, secondaryDisplayShowing); - mLocalDeviceIdleController.keyguardShowing(showing); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7c09ead9d42..39b7c7c310f 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -110,7 +110,6 @@ import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; -import android.os.IDeviceIdleController; import android.os.IInterface; import android.os.Looper; import android.os.Message; @@ -286,7 +285,6 @@ public class NotificationManagerService extends SystemService { private AlarmManager mAlarmManager; private ICompanionDeviceManager mCompanionManager; private AccessibilityManager mAccessibilityManager; - private IDeviceIdleController mDeviceIdleController; final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; @@ -660,7 +658,6 @@ public class NotificationManagerService extends SystemService { @Override public void onNotificationClick(int callingUid, int callingPid, String key) { - exitIdle(); synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r == null) { @@ -685,7 +682,6 @@ public class NotificationManagerService extends SystemService { @Override public void onNotificationActionClick(int callingUid, int callingPid, String key, int actionIndex) { - exitIdle(); synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r == null) { @@ -815,7 +811,6 @@ public class NotificationManagerService extends SystemService { @Override public void onNotificationDirectReplied(String key) { - exitIdle(); synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { @@ -1284,8 +1279,6 @@ public class NotificationManagerService extends SystemService { mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); mCompanionManager = companionManager; mActivityManager = activityManager; - mDeviceIdleController = IDeviceIdleController.Stub.asInterface( - ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); mHandler = new WorkerHandler(looper); mRankingThread.start(); @@ -1539,15 +1532,6 @@ public class NotificationManagerService extends SystemService { sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); } - private void exitIdle() { - try { - if (mDeviceIdleController != null) { - mDeviceIdleController.exitIdle("notification interaction"); - } - } catch (RemoteException e) { - } - } - private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel, boolean fromListener) { if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) { diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml index aabf9eab3c1..34755729142 100644 --- a/services/tests/uiservicestests/AndroidManifest.xml +++ b/services/tests/uiservicestests/AndroidManifest.xml @@ -26,7 +26,6 @@ - -- GitLab From 5b51885427ae4d0d828d132ca3f06d75e49ff210 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Tue, 23 Jan 2018 17:23:38 +0100 Subject: [PATCH 091/416] Merge emulated cutout into either rounded corner overlay This so that we can save a layer, which avoids dropping us into GL composition during animations. This assumes that the cutout is always at the top or bottom edge in the device's natural orientation. Note that the two overlays for the top and bottom rounded corners are still separate. Bug: 72492508 Test: enable emulated cutout, verify it still shows up Change-Id: I895084828e0502005bfa31e37d23dd3a6f01a2ca --- .../SystemUI/res/layout/rounded_corners.xml | 4 +- packages/SystemUI/res/values/config.xml | 3 +- packages/SystemUI/res/values/ids.xml | 2 + .../systemui/EmulatedDisplayCutout.java | 129 ------ .../com/android/systemui/RoundedCorners.java | 221 ---------- .../android/systemui/ScreenDecorations.java | 415 ++++++++++++++++++ ...rsTest.java => ScreenDecorationsTest.java} | 67 ++- 7 files changed, 477 insertions(+), 364 deletions(-) delete mode 100644 packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java delete mode 100644 packages/SystemUI/src/com/android/systemui/RoundedCorners.java create mode 100644 packages/SystemUI/src/com/android/systemui/ScreenDecorations.java rename packages/SystemUI/tests/src/com/android/systemui/{RoundedCornersTest.java => ScreenDecorationsTest.java} (66%) diff --git a/packages/SystemUI/res/layout/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml index d1ef5d638e7..734c877d782 100644 --- a/packages/SystemUI/res/layout/rounded_corners.xml +++ b/packages/SystemUI/res/layout/rounded_corners.xml @@ -22,7 +22,7 @@ android:id="@+id/left" android:layout_width="12dp" android:layout_height="12dp" - android:layout_gravity="left" + android:layout_gravity="left|top" android:tint="#ff000000" android:src="@drawable/rounded" /> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 676847088f3..1cc1cc88326 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -348,8 +348,7 @@ com.android.systemui.util.leak.GarbageMonitor$Service com.android.systemui.LatencyTester com.android.systemui.globalactions.GlobalActionsComponent - com.android.systemui.RoundedCorners - com.android.systemui.EmulatedDisplayCutout + com.android.systemui.ScreenDecorations com.android.systemui.fingerprint.FingerprintDialogImpl diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index edda613f2fb..f9aa8216ec1 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -96,5 +96,7 @@ + + diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java deleted file mode 100644 index 5d2e4d09ff4..00000000000 --- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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.systemui; - -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.PixelFormat; -import android.view.DisplayCutout; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.view.WindowInsets; -import android.view.WindowManager; - -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; - -/** - * Emulates a display cutout by drawing its shape in an overlay as supplied by - * {@link DisplayCutout}. - */ -public class EmulatedDisplayCutout extends SystemUI implements ConfigurationListener { - private View mOverlay; - private boolean mAttached; - private WindowManager mWindowManager; - - @Override - public void start() { - Dependency.get(ConfigurationController.class).addCallback(this); - - mWindowManager = mContext.getSystemService(WindowManager.class); - updateAttached(); - } - - @Override - public void onOverlayChanged() { - updateAttached(); - } - - private void updateAttached() { - boolean shouldAttach = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout); - setAttached(shouldAttach); - } - - private void setAttached(boolean attached) { - if (attached && !mAttached) { - if (mOverlay == null) { - mOverlay = new CutoutView(mContext); - mOverlay.setLayoutParams(getLayoutParams()); - } - mWindowManager.addView(mOverlay, mOverlay.getLayoutParams()); - mAttached = true; - } else if (!attached && mAttached) { - mWindowManager.removeView(mOverlay); - mAttached = false; - } - } - - private WindowManager.LayoutParams getLayoutParams() { - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH - | WindowManager.LayoutParams.FLAG_SLIPPERY - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR, - PixelFormat.TRANSLUCENT); - lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS - | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; - lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - lp.setTitle("EmulatedDisplayCutout"); - lp.gravity = Gravity.TOP; - return lp; - } - - private static class CutoutView extends View { - private final Paint mPaint = new Paint(); - private final Path mBounds = new Path(); - - CutoutView(Context context) { - super(context); - } - - @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mBounds.reset(); - if (insets.getDisplayCutout() != null) { - insets.getDisplayCutout().getBounds().getBoundaryPath(mBounds); - } - invalidate(); - return insets.consumeDisplayCutout(); - } - - @Override - protected void onDraw(Canvas canvas) { - if (!mBounds.isEmpty()) { - mPaint.setColor(Color.BLACK); - mPaint.setStyle(Paint.Style.FILL); - - canvas.drawPath(mBounds, mPaint); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java deleted file mode 100644 index c960fa122d5..00000000000 --- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * 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.systemui; - -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - -import static com.android.systemui.tuner.TunablePadding.FLAG_START; -import static com.android.systemui.tuner.TunablePadding.FLAG_END; - -import android.app.Fragment; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.PixelFormat; -import android.provider.Settings.Secure; -import android.support.annotation.VisibleForTesting; -import android.util.DisplayMetrics; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnLayoutChangeListener; -import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; -import android.view.WindowManager; -import android.widget.ImageView; - -import com.android.systemui.R.id; -import com.android.systemui.fragments.FragmentHostManager; -import com.android.systemui.fragments.FragmentHostManager.FragmentListener; -import com.android.systemui.plugins.qs.QS; -import com.android.systemui.qs.SecureSetting; -import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; -import com.android.systemui.statusbar.phone.NavigationBarFragment; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.tuner.TunablePadding; -import com.android.systemui.tuner.TunerService; -import com.android.systemui.tuner.TunerService.Tunable; - -public class RoundedCorners extends SystemUI implements Tunable { - public static final String SIZE = "sysui_rounded_size"; - public static final String PADDING = "sysui_rounded_content_padding"; - - private int mRoundedDefault; - private View mOverlay; - private View mBottomOverlay; - private float mDensity; - private TunablePadding mQsPadding; - private TunablePadding mStatusBarPadding; - private TunablePadding mNavBarPadding; - - @Override - public void start() { - mRoundedDefault = mContext.getResources().getDimensionPixelSize( - R.dimen.rounded_corner_radius); - if (mRoundedDefault != 0) { - setupRounding(); - } - int padding = mContext.getResources().getDimensionPixelSize( - R.dimen.rounded_corner_content_padding); - if (padding != 0) { - setupPadding(padding); - } - } - - private void setupRounding() { - mOverlay = LayoutInflater.from(mContext) - .inflate(R.layout.rounded_corners, null); - mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - mOverlay.setAlpha(0); - mOverlay.findViewById(R.id.right).setRotation(90); - - mContext.getSystemService(WindowManager.class) - .addView(mOverlay, getWindowLayoutParams()); - mBottomOverlay = LayoutInflater.from(mContext) - .inflate(R.layout.rounded_corners, null); - mBottomOverlay.setAlpha(0); - mBottomOverlay.findViewById(R.id.right).setRotation(180); - mBottomOverlay.findViewById(R.id.left).setRotation(270); - WindowManager.LayoutParams layoutParams = getWindowLayoutParams(); - layoutParams.gravity = Gravity.BOTTOM; - mContext.getSystemService(WindowManager.class) - .addView(mBottomOverlay, layoutParams); - - DisplayMetrics metrics = new DisplayMetrics(); - mContext.getSystemService(WindowManager.class) - .getDefaultDisplay().getMetrics(metrics); - mDensity = metrics.density; - - Dependency.get(TunerService.class).addTunable(this, SIZE); - - // Watch color inversion and invert the overlay as needed. - SecureSetting setting = new SecureSetting(mContext, Dependency.get(Dependency.MAIN_HANDLER), - Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) { - @Override - protected void handleValueChanged(int value, boolean observedChange) { - int tint = value != 0 ? Color.WHITE : Color.BLACK; - ColorStateList tintList = ColorStateList.valueOf(tint); - ((ImageView) mOverlay.findViewById(id.left)).setImageTintList(tintList); - ((ImageView) mOverlay.findViewById(id.right)).setImageTintList(tintList); - ((ImageView) mBottomOverlay.findViewById(id.left)).setImageTintList(tintList); - ((ImageView) mBottomOverlay.findViewById(id.right)).setImageTintList(tintList); - } - }; - setting.setListening(true); - setting.onChange(false); - - mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, - int oldTop, int oldRight, int oldBottom) { - mOverlay.removeOnLayoutChangeListener(this); - mOverlay.animate() - .alpha(1) - .setDuration(1000) - .start(); - mBottomOverlay.animate() - .alpha(1) - .setDuration(1000) - .start(); - } - }); - } - - private void setupPadding(int padding) { - // Add some padding to all the content near the edge of the screen. - StatusBar sb = getComponent(StatusBar.class); - View statusBar = (sb != null ? sb.getStatusBarWindow() : null); - if (statusBar != null) { - TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING, - padding, FLAG_END); - - FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar); - fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG, - new TunablePaddingTagListener(padding, R.id.status_bar)); - fragmentHostManager.addTagListener(QS.TAG, - new TunablePaddingTagListener(padding, R.id.header)); - } - } - - private WindowManager.LayoutParams getWindowLayoutParams() { - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - LayoutParams.WRAP_CONTENT, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH - | WindowManager.LayoutParams.FLAG_SLIPPERY - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, - PixelFormat.TRANSLUCENT); - lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS - | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; - lp.setTitle("RoundedOverlay"); - lp.gravity = Gravity.TOP; - lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - return lp; - } - - - @Override - public void onTuningChanged(String key, String newValue) { - if (mOverlay == null) return; - if (SIZE.equals(key)) { - int size = mRoundedDefault; - try { - size = (int) (Integer.parseInt(newValue) * mDensity); - } catch (Exception e) { - } - setSize(mOverlay.findViewById(R.id.left), size); - setSize(mOverlay.findViewById(R.id.right), size); - setSize(mBottomOverlay.findViewById(R.id.left), size); - setSize(mBottomOverlay.findViewById(R.id.right), size); - } - } - - private void setSize(View view, int pixelSize) { - LayoutParams params = view.getLayoutParams(); - params.width = pixelSize; - params.height = pixelSize; - view.setLayoutParams(params); - } - - @VisibleForTesting - static class TunablePaddingTagListener implements FragmentListener { - - private final int mPadding; - private final int mId; - private TunablePadding mTunablePadding; - - public TunablePaddingTagListener(int padding, int id) { - mPadding = padding; - mId = id; - } - - @Override - public void onFragmentViewCreated(String tag, Fragment fragment) { - if (mTunablePadding != null) { - mTunablePadding.destroy(); - } - View view = fragment.getView(); - if (mId != 0) { - view = view.findViewById(mId); - } - mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding, - FLAG_START | FLAG_END); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java new file mode 100644 index 00000000000..0b3e9e5072a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -0,0 +1,415 @@ +/* + * 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.systemui; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + +import static com.android.systemui.tuner.TunablePadding.FLAG_START; +import static com.android.systemui.tuner.TunablePadding.FLAG_END; + +import android.app.Fragment; +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.hardware.display.DisplayManager; +import android.provider.Settings.Secure; +import android.support.annotation.VisibleForTesting; +import android.util.DisplayMetrics; +import android.view.DisplayCutout; +import android.view.DisplayInfo; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnLayoutChangeListener; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.fragments.FragmentHostManager.FragmentListener; +import com.android.systemui.plugins.qs.QS; +import com.android.systemui.qs.SecureSetting; +import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.tuner.TunablePadding; +import com.android.systemui.tuner.TunerService; +import com.android.systemui.tuner.TunerService.Tunable; + +/** + * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout) + * for antialiasing and emulation purposes. + */ +public class ScreenDecorations extends SystemUI implements Tunable { + public static final String SIZE = "sysui_rounded_size"; + public static final String PADDING = "sysui_rounded_content_padding"; + + private int mRoundedDefault; + private View mOverlay; + private View mBottomOverlay; + private float mDensity; + private WindowManager mWindowManager; + private boolean mLandscape; + + @Override + public void start() { + mWindowManager = mContext.getSystemService(WindowManager.class); + mRoundedDefault = mContext.getResources().getDimensionPixelSize( + R.dimen.rounded_corner_radius); + if (mRoundedDefault != 0 || shouldDrawCutout()) { + setupDecorations(); + } + int padding = mContext.getResources().getDimensionPixelSize( + R.dimen.rounded_corner_content_padding); + if (padding != 0) { + setupPadding(padding); + } + } + + private void setupDecorations() { + mOverlay = LayoutInflater.from(mContext) + .inflate(R.layout.rounded_corners, null); + ((ViewGroup)mOverlay).addView(new DisplayCutoutView(mContext, true, + this::updateWindowVisibilities)); + mBottomOverlay = LayoutInflater.from(mContext) + .inflate(R.layout.rounded_corners, null); + ((ViewGroup)mBottomOverlay).addView(new DisplayCutoutView(mContext, false, + this::updateWindowVisibilities)); + + mOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + mOverlay.setAlpha(0); + + mBottomOverlay.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + mBottomOverlay.setAlpha(0); + + updateViews(); + + mWindowManager.addView(mOverlay, getWindowLayoutParams()); + mWindowManager.addView(mBottomOverlay, getBottomLayoutParams()); + + DisplayMetrics metrics = new DisplayMetrics(); + mWindowManager.getDefaultDisplay().getMetrics(metrics); + mDensity = metrics.density; + + Dependency.get(TunerService.class).addTunable(this, SIZE); + + // Watch color inversion and invert the overlay as needed. + SecureSetting setting = new SecureSetting(mContext, Dependency.get(Dependency.MAIN_HANDLER), + Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) { + @Override + protected void handleValueChanged(int value, boolean observedChange) { + int tint = value != 0 ? Color.WHITE : Color.BLACK; + ColorStateList tintList = ColorStateList.valueOf(tint); + ((ImageView) mOverlay.findViewById(R.id.left)).setImageTintList(tintList); + ((ImageView) mOverlay.findViewById(R.id.right)).setImageTintList(tintList); + ((ImageView) mBottomOverlay.findViewById(R.id.left)).setImageTintList(tintList); + ((ImageView) mBottomOverlay.findViewById(R.id.right)).setImageTintList(tintList); + } + }; + setting.setListening(true); + setting.onChange(false); + + mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, + int oldLeft, + int oldTop, int oldRight, int oldBottom) { + mOverlay.removeOnLayoutChangeListener(this); + mOverlay.animate() + .alpha(1) + .setDuration(1000) + .start(); + mBottomOverlay.animate() + .alpha(1) + .setDuration(1000) + .start(); + } + }); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + boolean newLanscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; + if (newLanscape != mLandscape) { + mLandscape = newLanscape; + + if (mOverlay != null) { + updateLayoutParams(); + updateViews(); + } + } + if (shouldDrawCutout() && mOverlay == null) { + setupDecorations(); + } + } + + private void updateViews() { + View topLeft = mOverlay.findViewById(R.id.left); + View topRight = mOverlay.findViewById(R.id.right); + View bottomLeft = mBottomOverlay.findViewById(R.id.left); + View bottomRight = mBottomOverlay.findViewById(R.id.right); + if (mLandscape) { + // Flip corners + View tmp = topRight; + topRight = bottomLeft; + bottomLeft = tmp; + } + updateView(topLeft, Gravity.TOP | Gravity.LEFT, 0); + updateView(topRight, Gravity.TOP | Gravity.RIGHT, 90); + updateView(bottomLeft, Gravity.BOTTOM | Gravity.LEFT, 270); + updateView(bottomRight, Gravity.BOTTOM | Gravity.RIGHT, 180); + + updateWindowVisibilities(); + } + + private void updateView(View v, int gravity, int rotation) { + ((FrameLayout.LayoutParams)v.getLayoutParams()).gravity = gravity; + v.setRotation(rotation); + } + + private void updateWindowVisibilities() { + updateWindowVisibility(mOverlay); + updateWindowVisibility(mBottomOverlay); + } + + private void updateWindowVisibility(View overlay) { + boolean visibleForCutout = shouldDrawCutout() + && overlay.findViewById(R.id.display_cutout).getVisibility() == View.VISIBLE; + boolean visibleForRoundedCorners = mRoundedDefault > 0; + overlay.setVisibility(visibleForCutout || visibleForRoundedCorners + ? View.VISIBLE : View.GONE); + } + + private boolean shouldDrawCutout() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout); + } + + private void setupPadding(int padding) { + // Add some padding to all the content near the edge of the screen. + StatusBar sb = getComponent(StatusBar.class); + View statusBar = (sb != null ? sb.getStatusBarWindow() : null); + if (statusBar != null) { + TunablePadding.addTunablePadding(statusBar.findViewById(R.id.keyguard_header), PADDING, + padding, FLAG_END); + + FragmentHostManager fragmentHostManager = FragmentHostManager.get(statusBar); + fragmentHostManager.addTagListener(CollapsedStatusBarFragment.TAG, + new TunablePaddingTagListener(padding, R.id.status_bar)); + fragmentHostManager.addTagListener(QS.TAG, + new TunablePaddingTagListener(padding, R.id.header)); + } + } + + @VisibleForTesting + WindowManager.LayoutParams getWindowLayoutParams() { + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_SLIPPERY + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + PixelFormat.TRANSLUCENT); + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS + | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; + lp.setTitle("ScreenDecorOverlay"); + lp.gravity = Gravity.TOP | Gravity.LEFT; + lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + if (mLandscape) { + lp.width = WRAP_CONTENT; + lp.height = MATCH_PARENT; + } + return lp; + } + + private WindowManager.LayoutParams getBottomLayoutParams() { + WindowManager.LayoutParams lp = getWindowLayoutParams(); + lp.setTitle("ScreenDecorOverlayBottom"); + lp.gravity = Gravity.BOTTOM | Gravity.RIGHT; + return lp; + } + + private void updateLayoutParams() { + mWindowManager.updateViewLayout(mOverlay, getWindowLayoutParams()); + mWindowManager.updateViewLayout(mBottomOverlay, getBottomLayoutParams()); + } + + @Override + public void onTuningChanged(String key, String newValue) { + if (mOverlay == null) return; + if (SIZE.equals(key)) { + int size = mRoundedDefault; + try { + size = (int) (Integer.parseInt(newValue) * mDensity); + } catch (Exception e) { + } + setSize(mOverlay.findViewById(R.id.left), size); + setSize(mOverlay.findViewById(R.id.right), size); + setSize(mBottomOverlay.findViewById(R.id.left), size); + setSize(mBottomOverlay.findViewById(R.id.right), size); + } + } + + private void setSize(View view, int pixelSize) { + LayoutParams params = view.getLayoutParams(); + params.width = pixelSize; + params.height = pixelSize; + view.setLayoutParams(params); + } + + @VisibleForTesting + static class TunablePaddingTagListener implements FragmentListener { + + private final int mPadding; + private final int mId; + private TunablePadding mTunablePadding; + + public TunablePaddingTagListener(int padding, int id) { + mPadding = padding; + mId = id; + } + + @Override + public void onFragmentViewCreated(String tag, Fragment fragment) { + if (mTunablePadding != null) { + mTunablePadding.destroy(); + } + View view = fragment.getView(); + if (mId != 0) { + view = view.findViewById(mId); + } + mTunablePadding = TunablePadding.addTunablePadding(view, PADDING, mPadding, + FLAG_START | FLAG_END); + } + } + + public static class DisplayCutoutView extends View implements DisplayManager.DisplayListener { + + private final DisplayInfo mInfo = new DisplayInfo(); + private final Paint mPaint = new Paint(); + private final Rect mBoundingRect = new Rect(); + private final Path mBoundingPath = new Path(); + private final int[] mLocation = new int[2]; + private final boolean mStart; + private final Runnable mVisibilityChangedListener; + + public DisplayCutoutView(Context context, boolean start, + Runnable visibilityChangedListener) { + super(context); + mStart = start; + mVisibilityChangedListener = visibilityChangedListener; + setId(R.id.display_cutout); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, + getHandler()); + update(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + getLocationOnScreen(mLocation); + canvas.translate(-mLocation[0], -mLocation[1]); + if (!mBoundingPath.isEmpty()) { + mPaint.setColor(Color.BLACK); + mPaint.setStyle(Paint.Style.FILL); + canvas.drawPath(mBoundingPath, mPaint); + } + } + + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayRemoved(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == getDisplay().getDisplayId()) { + update(); + } + } + + private void update() { + requestLayout(); + getDisplay().getDisplayInfo(mInfo); + mBoundingRect.setEmpty(); + mBoundingPath.reset(); + int newVisible; + if (hasCutout()) { + mBoundingRect.set(mInfo.displayCutout.getBoundingRect()); + mInfo.displayCutout.getBounds().getBoundaryPath(mBoundingPath); + newVisible = VISIBLE; + } else { + newVisible = GONE; + } + if (newVisible != getVisibility()) { + setVisibility(newVisible); + mVisibilityChangedListener.run(); + } + } + + private boolean hasCutout() { + if (mInfo.displayCutout == null) { + return false; + } + DisplayCutout displayCutout = mInfo.displayCutout.calculateRelativeTo( + new Rect(0, 0, mInfo.logicalWidth, mInfo.logicalHeight)); + if (mStart) { + return displayCutout.getSafeInsetLeft() > 0 + || displayCutout.getSafeInsetTop() > 0; + } else { + return displayCutout.getSafeInsetRight() > 0 + || displayCutout.getSafeInsetBottom() > 0; + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mBoundingRect.isEmpty()) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } + setMeasuredDimension( + resolveSizeAndState(mBoundingRect.width(), widthMeasureSpec, 0), + resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0)); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java similarity index 66% rename from packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java rename to packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 2a447715fb6..2f05b06ae0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/RoundedCornersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -14,9 +14,13 @@ package com.android.systemui; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; + import static com.android.systemui.tuner.TunablePadding.FLAG_END; import static com.android.systemui.tuner.TunablePadding.FLAG_START; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -29,6 +33,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Fragment; +import android.content.res.Configuration; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.view.Display; @@ -36,7 +41,7 @@ import android.view.View; import android.view.WindowManager; import com.android.systemui.R.dimen; -import com.android.systemui.RoundedCorners.TunablePaddingTagListener; +import com.android.systemui.ScreenDecorations.TunablePaddingTagListener; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; import com.android.systemui.statusbar.phone.StatusBar; @@ -51,9 +56,9 @@ import org.junit.runner.RunWith; @RunWith(AndroidTestingRunner.class) @SmallTest -public class RoundedCornersTest extends SysuiTestCase { +public class ScreenDecorationsTest extends SysuiTestCase { - private RoundedCorners mRoundedCorners; + private ScreenDecorations mScreenDecorations; private StatusBar mStatusBar; private WindowManager mWindowManager; private FragmentService mFragmentService; @@ -81,20 +86,22 @@ public class RoundedCornersTest extends SysuiTestCase { mTunerService = mDependency.injectMockDependency(TunerService.class); - mRoundedCorners = new RoundedCorners(); - mRoundedCorners.mContext = mContext; - mRoundedCorners.mComponents = mContext.getComponents(); + mScreenDecorations = new ScreenDecorations(); + mScreenDecorations.mContext = mContext; + mScreenDecorations.mComponents = mContext.getComponents(); mTunablePaddingService = mDependency.injectMockDependency(TunablePaddingService.class); } @Test - public void testNoRounding() { + public void testNoRounding_NoCutout() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false); mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 0); mContext.getOrCreateTestableResources() .addOverride(dimen.rounded_corner_content_padding, 0); - mRoundedCorners.start(); + mScreenDecorations.start(); // No views added. verify(mWindowManager, never()).addView(any(), any()); // No Fragments watched. @@ -105,11 +112,13 @@ public class RoundedCornersTest extends SysuiTestCase { @Test public void testRounding() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false); mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 20); mContext.getOrCreateTestableResources() .addOverride(dimen.rounded_corner_content_padding, 20); - mRoundedCorners.start(); + mScreenDecorations.start(); // Add 2 windows for rounded corners (top and bottom). verify(mWindowManager, times(2)).addView(any(), any()); @@ -121,6 +130,44 @@ public class RoundedCornersTest extends SysuiTestCase { verify(mTunablePaddingService, times(1)).add(any(), anyString(), anyInt(), anyInt()); } + @Test + public void testCutout() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true); + mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 0); + mContext.getOrCreateTestableResources() + .addOverride(dimen.rounded_corner_content_padding, 0); + + mScreenDecorations.start(); + // Add 2 windows for rounded corners (top and bottom). + verify(mWindowManager, times(2)).addView(any(), any()); + } + + @Test + public void testDelayedCutout() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false); + mContext.getOrCreateTestableResources().addOverride(dimen.rounded_corner_radius, 0); + mContext.getOrCreateTestableResources() + .addOverride(dimen.rounded_corner_content_padding, 0); + + mScreenDecorations.start(); + + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, true); + mScreenDecorations.onConfigurationChanged(new Configuration()); + + // Add 2 windows for rounded corners (top and bottom). + verify(mWindowManager, times(2)).addView(any(), any()); + } + + @Test + public void hasRoundedCornerOverlayFlagSet() { + assertThat(mScreenDecorations.getWindowLayoutParams().privateFlags + & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, + is(PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY)); + } + @Test public void testPaddingTagListener() { TunablePaddingTagListener tagListener = new TunablePaddingTagListener(14, 5); @@ -136,7 +183,7 @@ public class RoundedCornersTest extends SysuiTestCase { // Trigger callback and verify we get a TunablePadding created. tagListener.onFragmentViewCreated(null, f); - verify(mTunablePaddingService).add(eq(child), eq(RoundedCorners.PADDING), eq(14), + verify(mTunablePaddingService).add(eq(child), eq(ScreenDecorations.PADDING), eq(14), eq(FLAG_START | FLAG_END)); // Call again and verify destroy is called. -- GitLab From 10d69ea7d38d712bb064fabaa257875c0a02fce9 Mon Sep 17 00:00:00 2001 From: Mihai Popa Date: Fri, 26 Jan 2018 15:09:48 +0000 Subject: [PATCH 092/416] [Magnifier - 18] Make #update() public The CL adds the Magnifier#update() method in the public API. The method is used to refresh the content of the magnifier, whenever this is desired (usually when there is a chance that the magnifier content became stale). The initial plan was that this method would not be included in the public API. This was relying on a feature request we made to the graphics team, asking for support to have a callback called whenever the surface the magnifier is attached to changes. This way, we could refresh the magnifier content whenever the surface changes, without requiring the user to call #update(). Once the feature request is implemented (probably in Q according to the last discussion), we will be able to deprecate #update(). Bug: 63531115 Test: atest CtsWidgetTestCases:android.widget.cts.MagnifierTest Change-Id: I62c5794c3227e6a5d36d351c10d6bcf18e1d931a --- api/current.txt | 1 + core/java/android/widget/Magnifier.java | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/api/current.txt b/api/current.txt index 2794a766174..8305ee9793f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -52292,6 +52292,7 @@ package android.widget { ctor public Magnifier(android.view.View); method public void dismiss(); method public void show(float, float); + method public void update(); } public class MediaController extends android.widget.FrameLayout { diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index e42217f710c..7a4c800ba15 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -182,8 +182,6 @@ public final class Magnifier { /** * Forces the magnifier to update its content. It uses the previous coordinates passed to * {@link #show(float, float)}. This only happens if the magnifier is currently showing. - * - * @hide */ public void update() { if (mWindow.isShowing()) { -- GitLab From 435e84b9fcaf129dbd0d9f8c86c3ead5c51e9405 Mon Sep 17 00:00:00 2001 From: yuanhao Date: Mon, 15 Jan 2018 15:37:02 +0800 Subject: [PATCH 093/416] Fix "zygote is killed by signal 1" Ignore the signal SIGHUP in Zygote and all its forks. Bug:71965619 Test: manual Change-Id: I7987bb044d97ae21ec27beca6e9aefcbe77197f5 Signed-off-by: yuanhao --- core/jni/com_android_internal_os_Zygote.cpp | 42 ++++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 63dba43a5eb..fa86c7537d1 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -141,32 +141,45 @@ static void SigChldHandler(int /*signal_number*/) { errno = saved_errno; } -// Configures the SIGCHLD handler for the zygote process. This is configured -// very late, because earlier in the runtime we may fork() and exec() -// other processes, and we want to waitpid() for those rather than +// Configures the SIGCHLD/SIGHUP handlers for the zygote process. This is +// configured very late, because earlier in the runtime we may fork() and +// exec() other processes, and we want to waitpid() for those rather than // have them be harvested immediately. // +// Ignore SIGHUP because all processes forked by the zygote are in the same +// process group as the zygote and we don't want to be notified if we become +// an orphaned group and have one or more stopped processes. This is not a +// theoretical concern : +// - we can become an orphaned group if one of our direct descendants forks +// and is subsequently killed before its children. +// - crash_dump routinely STOPs the process it's tracing. +// +// See issues b/71965619 and b/25567761 for further details. +// // This ends up being called repeatedly before each fork(), but there's // no real harm in that. -static void SetSigChldHandler() { - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = SigChldHandler; +static void SetSignalHandlers() { + struct sigaction sig_chld = {}; + sig_chld.sa_handler = SigChldHandler; - int err = sigaction(SIGCHLD, &sa, NULL); - if (err < 0) { + if (sigaction(SIGCHLD, &sig_chld, NULL) < 0) { ALOGW("Error setting SIGCHLD handler: %s", strerror(errno)); } + + struct sigaction sig_hup = {}; + sig_hup.sa_handler = SIG_IGN; + if (sigaction(SIGHUP, &sig_hup, NULL) < 0) { + ALOGW("Error setting SIGHUP handler: %s", strerror(errno)); + } } // Sets the SIGCHLD handler back to default behavior in zygote children. -static void UnsetSigChldHandler() { +static void UnsetChldSignalHandler() { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = SIG_DFL; - int err = sigaction(SIGCHLD, &sa, NULL); - if (err < 0) { + if (sigaction(SIGCHLD, &sa, NULL) < 0) { ALOGW("Error unsetting SIGCHLD handler: %s", strerror(errno)); } } @@ -505,7 +518,7 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra bool is_system_server, jintArray fdsToClose, jintArray fdsToIgnore, jstring instructionSet, jstring dataDir) { - SetSigChldHandler(); + SetSignalHandlers(); sigset_t sigchld; sigemptyset(&sigchld); @@ -682,7 +695,8 @@ static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArra delete se_info; delete se_name; - UnsetSigChldHandler(); + // Unset the SIGCHLD handler, but keep ignoring SIGHUP (rationale in SetSignalHandlers). + UnsetChldSignalHandler(); env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, runtime_flags, is_system_server, instructionSet); -- GitLab From 156ed92c94fb5accceb96ef00390be2894cd3b11 Mon Sep 17 00:00:00 2001 From: David Srbecky Date: Tue, 30 Jan 2018 14:37:37 +0000 Subject: [PATCH 094/416] Propagate the "dalvik.vm.minidebuginfo" property to ART run-time. Change-Id: I27e230fe91490defde4cc38ca8cbc3aa0765fed1 --- core/java/com/android/internal/os/Zygote.java | 2 ++ .../java/com/android/server/am/ActivityManagerService.java | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index d0f5ba75e8a..03e5667666c 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -57,6 +57,8 @@ public final class Zygote { public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10; /** Do not enfore hidden API access restrictions. */ public static final int DISABLE_HIDDEN_API_CHECKS = 1 << 11; + /** Force generation of native debugging information for backtraces. */ + public static final int DEBUG_GENERATE_MINI_DEBUG_INFO = 1 << 12; /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = 0; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e944326c9f7..f0d6757a7fe 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -3863,9 +3863,13 @@ public class ActivityManagerService extends IActivityManager.Stub runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; } String genDebugInfoProperty = SystemProperties.get("debug.generate-debug-info"); - if ("true".equals(genDebugInfoProperty)) { + if ("1".equals(genDebugInfoProperty) || "true".equals(genDebugInfoProperty)) { runtimeFlags |= Zygote.DEBUG_GENERATE_DEBUG_INFO; } + String genMiniDebugInfoProperty = SystemProperties.get("dalvik.vm.minidebuginfo"); + if ("1".equals(genMiniDebugInfoProperty) || "true".equals(genMiniDebugInfoProperty)) { + runtimeFlags |= Zygote.DEBUG_GENERATE_MINI_DEBUG_INFO; + } if ("1".equals(SystemProperties.get("debug.jni.logging"))) { runtimeFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING; } -- GitLab From 3a1d2e97918aed14f7c8cea59a159ed337c25bfb Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Mon, 29 Jan 2018 16:58:11 -0500 Subject: [PATCH 095/416] Fix slice listener permissions When trying to listen to slices without permission before hand, it would previously crash. Now it works properly with the grant dialog and whatnot. Test: uiservicestests Bug: 68751119 Change-Id: I3aedab9c75ac8486026723dea5c93ee950995295 --- .../java/android/app/slice/SliceProvider.java | 28 +++- .../server/slice/PinnedSliceState.java | 130 ++++++++++++----- .../server/slice/SliceManagerService.java | 78 +++++++---- .../server/slice/PinnedSliceStateTest.java | 132 ++++++++++++++---- 4 files changed, 271 insertions(+), 97 deletions(-) diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index 00e8ccad0f5..336bd478215 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -147,6 +147,14 @@ public abstract class SliceProvider extends ContentProvider { * @hide */ public static final String EXTRA_OVERRIDE_PKG = "override_pkg"; + /** + * @hide + */ + public static final String EXTRA_OVERRIDE_UID = "override_uid"; + /** + * @hide + */ + public static final String EXTRA_OVERRIDE_PID = "override_pid"; private static final boolean DEBUG = false; @@ -302,13 +310,20 @@ public abstract class SliceProvider extends ContentProvider { List supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); String callingPackage = getCallingPackage(); + int callingUid = Binder.getCallingUid(); + int callingPid = Binder.getCallingPid(); if (extras.containsKey(EXTRA_OVERRIDE_PKG)) { if (Binder.getCallingUid() != Process.SYSTEM_UID) { throw new SecurityException("Only the system can override calling pkg"); } + // This is safe because we would grant SYSTEM_UID access to all slices + // and want to allow it to bind slices as if it were a less privileged app + // to check their permission levels. callingPackage = extras.getString(EXTRA_OVERRIDE_PKG); + callingUid = extras.getInt(EXTRA_OVERRIDE_UID); + callingPid = extras.getInt(EXTRA_OVERRIDE_PID); } - Slice s = handleBindSlice(uri, supportedSpecs, callingPackage); + Slice s = handleBindSlice(uri, supportedSpecs, callingPackage, callingUid, callingPid); Bundle b = new Bundle(); b.putParcelable(EXTRA_SLICE, s); return b; @@ -319,7 +334,8 @@ public abstract class SliceProvider extends ContentProvider { List supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); Bundle b = new Bundle(); if (uri != null) { - Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage()); + Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage(), + Binder.getCallingUid(), Binder.getCallingPid()); b.putParcelable(EXTRA_SLICE, s); } else { b.putParcelable(EXTRA_SLICE, null); @@ -401,15 +417,15 @@ public abstract class SliceProvider extends ContentProvider { } private Slice handleBindSlice(Uri sliceUri, List supportedSpecs, - String callingPkg) { + String callingPkg, int callingUid, int callingPid) { // This can be removed once Slice#bindSlice is removed and everyone is using // SliceManager#bindSlice. String pkg = callingPkg != null ? callingPkg - : getContext().getPackageManager().getNameForUid(Binder.getCallingUid()); - if (!UserHandle.isSameApp(Binder.getCallingUid(), Process.myUid())) { + : getContext().getPackageManager().getNameForUid(callingUid); + if (!UserHandle.isSameApp(callingUid, Process.myUid())) { try { mSliceManager.enforceSlicePermission(sliceUri, pkg, - Binder.getCallingPid(), Binder.getCallingUid()); + callingPid, callingUid); } catch (SecurityException e) { return createPermissionSlice(getContext(), sliceUri, pkg); } diff --git a/services/core/java/com/android/server/slice/PinnedSliceState.java b/services/core/java/com/android/server/slice/PinnedSliceState.java index 192fd63a9af..8da16d7ea14 100644 --- a/services/core/java/com/android/server/slice/PinnedSliceState.java +++ b/services/core/java/com/android/server/slice/PinnedSliceState.java @@ -14,12 +14,15 @@ package com.android.server.slice; +import static android.app.slice.SliceManager.PERMISSION_GRANTED; + import android.app.slice.ISliceListener; import android.app.slice.Slice; import android.app.slice.SliceProvider; import android.app.slice.SliceSpec; import android.content.ContentProviderClient; import android.net.Uri; +import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IBinder.DeathRecipient; @@ -51,18 +54,16 @@ public class PinnedSliceState { @GuardedBy("mLock") private final ArraySet mPinnedPkgs = new ArraySet<>(); @GuardedBy("mLock") - private final ArrayMap mListeners = new ArrayMap<>(); + private final ArrayMap mListeners = new ArrayMap<>(); @GuardedBy("mLock") private SliceSpec[] mSupportedSpecs = null; - @GuardedBy("mLock") - private final ArrayMap mPkgMap = new ArrayMap<>(); private final DeathRecipient mDeathRecipient = this::handleRecheckListeners; + private boolean mSlicePinned; public PinnedSliceState(SliceManagerService service, Uri uri) { mService = service; mUri = uri; - mService.getHandler().post(this::handleSendPinned); mLock = mService.getLock(); } @@ -102,14 +103,27 @@ public class PinnedSliceState { } public void destroy() { - mService.getHandler().post(this::handleSendUnpinned); + setSlicePinned(false); } public void onChange() { mService.getHandler().post(this::handleBind); } - public void addSliceListener(ISliceListener listener, String pkg, SliceSpec[] specs) { + private void setSlicePinned(boolean pinned) { + synchronized (mLock) { + if (mSlicePinned == pinned) return; + mSlicePinned = pinned; + if (pinned) { + mService.getHandler().post(this::handleSendPinned); + } else { + mService.getHandler().post(this::handleSendUnpinned); + } + } + } + + public void addSliceListener(ISliceListener listener, String pkg, SliceSpec[] specs, + boolean hasPermission) { synchronized (mLock) { if (mListeners.size() == 0) { mService.listen(mUri); @@ -118,26 +132,27 @@ public class PinnedSliceState { listener.asBinder().linkToDeath(mDeathRecipient, 0); } catch (RemoteException e) { } - mListeners.put(listener.asBinder(), listener); - mPkgMap.put(listener.asBinder(), pkg); + mListeners.put(listener.asBinder(), new ListenerInfo(listener, pkg, hasPermission, + Binder.getCallingUid(), Binder.getCallingPid())); mergeSpecs(specs); + setSlicePinned(hasPermission); } } public boolean removeSliceListener(ISliceListener listener) { synchronized (mLock) { listener.asBinder().unlinkToDeath(mDeathRecipient, 0); - mPkgMap.remove(listener.asBinder()); if (mListeners.containsKey(listener.asBinder()) && mListeners.size() == 1) { mService.unlisten(mUri); } mListeners.remove(listener.asBinder()); } - return !isPinned(); + return !hasPinOrListener(); } public void pin(String pkg, SliceSpec[] specs) { synchronized (mLock) { + setSlicePinned(true); mPinnedPkgs.add(pkg); mergeSpecs(specs); } @@ -147,7 +162,7 @@ public class PinnedSliceState { synchronized (mLock) { mPinnedPkgs.remove(pkg); } - return !isPinned(); + return !hasPinOrListener(); } public boolean isListening() { @@ -156,8 +171,32 @@ public class PinnedSliceState { } } + public void recheckPackage(String pkg) { + synchronized (mLock) { + for (int i = 0; i < mListeners.size(); i++) { + ListenerInfo info = mListeners.valueAt(i); + if (!info.hasPermission && Objects.equals(info.pkg, pkg)) { + mService.getHandler().post(() -> { + // This bind lets the app itself participate in the permission grant. + Slice s = doBind(info); + if (mService.checkAccess(info.pkg, mUri, info.callingUid, info.callingPid) + == PERMISSION_GRANTED) { + info.hasPermission = true; + setSlicePinned(true); + try { + info.listener.onSliceUpdated(s); + } catch (RemoteException e) { + checkSelfRemove(); + } + } + }); + } + } + } + } + @VisibleForTesting - public boolean isPinned() { + public boolean hasPinOrListener() { synchronized (mLock) { return !mPinnedPkgs.isEmpty() || !mListeners.isEmpty(); } @@ -171,61 +210,66 @@ public class PinnedSliceState { return client; } + private void checkSelfRemove() { + if (!hasPinOrListener()) { + // All the listeners died, remove from pinned state. + mService.unlisten(mUri); + mService.removePinnedSlice(mUri); + } + } + private void handleRecheckListeners() { - if (!isPinned()) return; + if (!hasPinOrListener()) return; synchronized (mLock) { for (int i = mListeners.size() - 1; i >= 0; i--) { - ISliceListener l = mListeners.valueAt(i); - if (!l.asBinder().isBinderAlive()) { + ListenerInfo l = mListeners.valueAt(i); + if (!l.listener.asBinder().isBinderAlive()) { mListeners.removeAt(i); } } - if (!isPinned()) { - // All the listeners died, remove from pinned state. - mService.unlisten(mUri); - mService.removePinnedSlice(mUri); - } + checkSelfRemove(); } } private void handleBind() { Slice cachedSlice = doBind(null); synchronized (mLock) { - if (!isPinned()) return; + if (!hasPinOrListener()) return; for (int i = mListeners.size() - 1; i >= 0; i--) { - ISliceListener l = mListeners.valueAt(i); + ListenerInfo info = mListeners.valueAt(i); Slice s = cachedSlice; - if (s == null || s.hasHint(Slice.HINT_CALLER_NEEDED)) { - s = doBind(mPkgMap.get(l)); + if (s == null || s.hasHint(Slice.HINT_CALLER_NEEDED) + || !info.hasPermission) { + s = doBind(info); } if (s == null) { mListeners.removeAt(i); continue; } try { - l.onSliceUpdated(s); + info.listener.onSliceUpdated(s); } catch (RemoteException e) { Log.e(TAG, "Unable to notify slice " + mUri, e); mListeners.removeAt(i); continue; } } - if (!isPinned()) { - // All the listeners died, remove from pinned state. - mService.unlisten(mUri); - mService.removePinnedSlice(mUri); - } + checkSelfRemove(); } } - private Slice doBind(String overridePkg) { + private Slice doBind(ListenerInfo info) { try (ContentProviderClient client = getClient()) { if (client == null) return null; Bundle extras = new Bundle(); extras.putParcelable(SliceProvider.EXTRA_BIND_URI, mUri); extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, new ArrayList<>(Arrays.asList(mSupportedSpecs))); - extras.putString(SliceProvider.EXTRA_OVERRIDE_PKG, overridePkg); + if (info != null) { + extras.putString(SliceProvider.EXTRA_OVERRIDE_PKG, info.pkg); + extras.putInt(SliceProvider.EXTRA_OVERRIDE_UID, info.callingUid); + extras.putInt(SliceProvider.EXTRA_OVERRIDE_PID, info.callingPid); + } final Bundle res; try { res = client.call(SliceProvider.METHOD_SLICE, null, extras); @@ -236,6 +280,10 @@ public class PinnedSliceState { if (res == null) return null; Bundle.setDefusable(res, true); return res.getParcelable(SliceProvider.EXTRA_SLICE); + } catch (Throwable t) { + // Calling out of the system process, make sure they don't throw anything at us. + Log.e(TAG, "Caught throwable while binding " + mUri, t); + return null; } } @@ -264,4 +312,22 @@ public class PinnedSliceState { } } } + + private class ListenerInfo { + + private ISliceListener listener; + private String pkg; + private boolean hasPermission; + private int callingUid; + private int callingPid; + + public ListenerInfo(ISliceListener listener, String pkg, boolean hasPermission, + int callingUid, int callingPid) { + this.listener = listener; + this.pkg = pkg; + this.hasPermission = hasPermission; + this.callingUid = callingUid; + this.callingPid = callingPid; + } + } } diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java index c1915801b61..8e7daeef390 100644 --- a/services/core/java/com/android/server/slice/SliceManagerService.java +++ b/services/core/java/com/android/server/slice/SliceManagerService.java @@ -19,6 +19,8 @@ package com.android.server.slice; import static android.content.ContentProvider.getUriWithoutUserId; import static android.content.ContentProvider.getUserIdFromUri; import static android.content.ContentProvider.maybeAddUserId; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest.permission; import android.app.ActivityManager; @@ -32,7 +34,6 @@ import android.app.slice.SliceSpec; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.database.ContentObserver; @@ -43,6 +44,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Process; import android.os.RemoteException; +import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -120,8 +122,10 @@ public class SliceManagerService extends ISliceManager.Stub { throws RemoteException { verifyCaller(pkg); uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier()); - enforceAccess(pkg, uri); - getOrCreatePinnedSlice(uri).addSliceListener(listener, pkg, specs); + enforceCrossUser(pkg, uri); + getOrCreatePinnedSlice(uri).addSliceListener(listener, pkg, specs, + checkAccess(pkg, uri, Binder.getCallingUid(), Binder.getCallingUid()) + == PERMISSION_GRANTED); } @Override @@ -129,7 +133,6 @@ public class SliceManagerService extends ISliceManager.Stub { throws RemoteException { verifyCaller(pkg); uri = maybeAddUserId(uri, Binder.getCallingUserHandle().getIdentifier()); - enforceAccess(pkg, uri); if (getPinnedSlice(uri).removeSliceListener(listener)) { removePinnedSlice(uri); } @@ -169,7 +172,7 @@ public class SliceManagerService extends ISliceManager.Stub { @Override public int checkSlicePermission(Uri uri, String pkg, int pid, int uid) throws RemoteException { if (mContext.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - == PackageManager.PERMISSION_GRANTED) { + == PERMISSION_GRANTED) { return SliceManager.PERMISSION_GRANTED; } if (hasFullSliceAccess(pkg, uid)) { @@ -201,6 +204,11 @@ public class SliceManagerService extends ISliceManager.Stub { Binder.restoreCallingIdentity(ident); } } + synchronized (mLock) { + for (PinnedSliceState p : mPinnedSlicesByUri.values()) { + p.recheckPackage(pkg); + } + } } /// ----- internal code ----- @@ -249,17 +257,13 @@ public class SliceManagerService extends ISliceManager.Stub { return mHandler; } - private void enforceAccess(String pkg, Uri uri) throws RemoteException { - int user = Binder.getCallingUserHandle().getIdentifier(); + protected int checkAccess(String pkg, Uri uri, int uid, int pid) { + int user = UserHandle.getUserId(uid); // Check for default launcher/assistant. - if (!hasFullSliceAccess(pkg, Binder.getCallingUid())) { - try { - // Also allow things with uri access. - getContext().enforceUriPermission(uri, Binder.getCallingPid(), - Binder.getCallingUid(), - Intent.FLAG_GRANT_WRITE_URI_PERMISSION, - "Slice binding requires permission to the Uri"); - } catch (SecurityException e) { + if (!hasFullSliceAccess(pkg, uid)) { + // Also allow things with uri access. + if (getContext().checkUriPermission(uri, pid, uid, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PERMISSION_GRANTED) { // Last fallback (if the calling app owns the authority, then it can have access). long ident = Binder.clearCallingIdentity(); try { @@ -268,17 +272,21 @@ public class SliceManagerService extends ISliceManager.Stub { ContentProviderHolder holder = null; String providerName = getUriWithoutUserId(uri).getAuthority(); try { - holder = activityManager.getContentProviderExternal( - providerName, getUserIdFromUri(uri, user), token); - if (holder == null || holder.info == null - || !Objects.equals(holder.info.packageName, pkg)) { - // No more fallbacks, no access. - throw e; - } - } finally { - if (holder != null && holder.provider != null) { - activityManager.removeContentProviderExternal(providerName, token); + try { + holder = activityManager.getContentProviderExternal( + providerName, getUserIdFromUri(uri, user), token); + if (holder == null || holder.info == null + || !Objects.equals(holder.info.packageName, pkg)) { + return PERMISSION_DENIED; + } + } finally { + if (holder != null && holder.provider != null) { + activityManager.removeContentProviderExternal(providerName, token); + } } + } catch (RemoteException e) { + // Can't happen. + e.rethrowAsRuntimeException(); } } finally { // I know, the double finally seems ugly, but seems safest for the identity. @@ -286,23 +294,31 @@ public class SliceManagerService extends ISliceManager.Stub { } } } - // Lastly check for any multi-userness. Any return statements above here will break this - // important check. + return PERMISSION_GRANTED; + } + + private void enforceCrossUser(String pkg, Uri uri) { + int user = Binder.getCallingUserHandle().getIdentifier(); if (getUserIdFromUri(uri, user) != user) { getContext().enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL, "Slice interaction across users requires INTERACT_ACROSS_USERS_FULL"); } } + private void enforceAccess(String pkg, Uri uri) throws RemoteException { + if (checkAccess(pkg, uri, Binder.getCallingUid(), Binder.getCallingPid()) + != PERMISSION_GRANTED) { + throw new SecurityException("Access to slice " + uri + " is required"); + } + enforceCrossUser(pkg, uri); + } + private void enforceFullAccess(String pkg, String name, Uri uri) { int user = Binder.getCallingUserHandle().getIdentifier(); if (!hasFullSliceAccess(pkg, user)) { throw new SecurityException(String.format("Call %s requires full slice access", name)); } - if (getUserIdFromUri(uri, user) != user) { - getContext().enforceCallingOrSelfPermission(permission.INTERACT_ACROSS_USERS_FULL, - "Slice interaction across users requires INTERACT_ACROSS_USERS_FULL"); - } + enforceCrossUser(pkg, uri); } private void verifyCaller(String pkg) { diff --git a/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java b/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java index 1606bd9c6fd..cfd155e81a6 100644 --- a/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java +++ b/services/tests/uiservicestests/src/com/android/server/slice/PinnedSliceStateTest.java @@ -1,5 +1,7 @@ package com.android.server.slice; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -11,7 +13,9 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -20,6 +24,7 @@ import android.app.slice.Slice; import android.app.slice.SliceProvider; import android.app.slice.SliceSpec; import android.content.ContentProvider; +import android.content.Context; import android.content.IContentProvider; import android.net.Uri; import android.os.Binder; @@ -70,8 +75,8 @@ public class PinnedSliceStateTest extends UiServiceTestCase { @Before public void setup() { mSliceService = mock(SliceManagerService.class); - when(mSliceService.getLock()).thenReturn(new Object()); when(mSliceService.getContext()).thenReturn(mContext); + when(mSliceService.getLock()).thenReturn(new Object()); when(mSliceService.getHandler()).thenReturn(new Handler(TestableLooper.get(this).getLooper())); mContentProvider = mock(ContentProvider.class); mIContentProvider = mock(IContentProvider.class); @@ -99,8 +104,11 @@ public class PinnedSliceStateTest extends UiServiceTestCase { } @Test - public void testSendPinnedOnCreate() throws RemoteException { - // When created, a pinned message should be sent. + public void testSendPinnedOnPin() throws RemoteException { + TestableLooper.get(this).processAllMessages(); + + // When pinned for the first time, a pinned message should be sent. + mPinnedSliceManager.pin("pkg", FIRST_SPECS); TestableLooper.get(this).processAllMessages(); verify(mIContentProvider).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null), @@ -110,11 +118,47 @@ public class PinnedSliceStateTest extends UiServiceTestCase { })); } + @Test + public void testSendPinnedOnListen() throws RemoteException { + TestableLooper.get(this).processAllMessages(); + + // When a listener is added for the first time, a pinned message should be sent. + ISliceListener listener = mock(ISliceListener.class); + when(listener.asBinder()).thenReturn(new Binder()); + + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + true); + TestableLooper.get(this).processAllMessages(); + + verify(mIContentProvider).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null), + argThat(b -> { + assertEquals(TEST_URI, b.getParcelable(SliceProvider.EXTRA_BIND_URI)); + return true; + })); + } + + @Test + public void testNoSendPinnedWithoutPermission() throws RemoteException { + TestableLooper.get(this).processAllMessages(); + + // When a listener is added for the first time, a pinned message should be sent. + ISliceListener listener = mock(ISliceListener.class); + when(listener.asBinder()).thenReturn(new Binder()); + + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + false); + TestableLooper.get(this).processAllMessages(); + + verify(mIContentProvider, never()).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null), + any()); + } + @Test public void testSendUnpinnedOnDestroy() throws RemoteException { TestableLooper.get(this).processAllMessages(); clearInvocations(mIContentProvider); + mPinnedSliceManager.pin("pkg", FIRST_SPECS); mPinnedSliceManager.destroy(); TestableLooper.get(this).processAllMessages(); @@ -127,39 +171,40 @@ public class PinnedSliceStateTest extends UiServiceTestCase { @Test public void testPkgPin() { - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); mPinnedSliceManager.pin("pkg", FIRST_SPECS); - assertTrue(mPinnedSliceManager.isPinned()); + assertTrue(mPinnedSliceManager.hasPinOrListener()); assertTrue(mPinnedSliceManager.unpin("pkg")); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); } @Test public void testMultiPkgPin() { - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); mPinnedSliceManager.pin("pkg", FIRST_SPECS); - assertTrue(mPinnedSliceManager.isPinned()); + assertTrue(mPinnedSliceManager.hasPinOrListener()); mPinnedSliceManager.pin("pkg2", FIRST_SPECS); assertFalse(mPinnedSliceManager.unpin("pkg")); assertTrue(mPinnedSliceManager.unpin("pkg2")); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); } @Test public void testListenerPin() { ISliceListener listener = mock(ISliceListener.class); when(listener.asBinder()).thenReturn(new Binder()); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); - mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); - assertTrue(mPinnedSliceManager.isPinned()); + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + true); + assertTrue(mPinnedSliceManager.hasPinOrListener()); assertTrue(mPinnedSliceManager.removeSliceListener(listener)); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); } @Test @@ -170,15 +215,17 @@ public class PinnedSliceStateTest extends UiServiceTestCase { ISliceListener listener2 = mock(ISliceListener.class); Binder value2 = new Binder(); when(listener2.asBinder()).thenReturn(value2); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); - mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); - assertTrue(mPinnedSliceManager.isPinned()); - mPinnedSliceManager.addSliceListener(listener2, mContext.getPackageName(), FIRST_SPECS); + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + true); + assertTrue(mPinnedSliceManager.hasPinOrListener()); + mPinnedSliceManager.addSliceListener(listener2, mContext.getPackageName(), FIRST_SPECS, + true); assertFalse(mPinnedSliceManager.removeSliceListener(listener)); assertTrue(mPinnedSliceManager.removeSliceListener(listener2)); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); } @Test @@ -187,10 +234,11 @@ public class PinnedSliceStateTest extends UiServiceTestCase { IBinder binder = mock(IBinder.class); when(binder.isBinderAlive()).thenReturn(true); when(listener.asBinder()).thenReturn(binder); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); - mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); - assertTrue(mPinnedSliceManager.isPinned()); + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + true); + assertTrue(mPinnedSliceManager.hasPinOrListener()); ArgumentCaptor arg = ArgumentCaptor.forClass(DeathRecipient.class); verify(binder).linkToDeath(arg.capture(), anyInt()); @@ -200,22 +248,23 @@ public class PinnedSliceStateTest extends UiServiceTestCase { verify(mSliceService).unlisten(eq(TEST_URI)); verify(mSliceService).removePinnedSlice(eq(TEST_URI)); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); } @Test public void testPkgListenerPin() { ISliceListener listener = mock(ISliceListener.class); when(listener.asBinder()).thenReturn(new Binder()); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); - mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); - assertTrue(mPinnedSliceManager.isPinned()); + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + true); + assertTrue(mPinnedSliceManager.hasPinOrListener()); mPinnedSliceManager.pin("pkg", FIRST_SPECS); assertFalse(mPinnedSliceManager.removeSliceListener(listener)); assertTrue(mPinnedSliceManager.unpin("pkg")); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); } @Test @@ -231,9 +280,10 @@ public class PinnedSliceStateTest extends UiServiceTestCase { when(mIContentProvider.call(anyString(), eq(SliceProvider.METHOD_SLICE), eq(null), any())).thenReturn(b); - assertFalse(mPinnedSliceManager.isPinned()); + assertFalse(mPinnedSliceManager.hasPinOrListener()); - mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS); + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + true); mPinnedSliceManager.onChange(); TestableLooper.get(this).processAllMessages(); @@ -245,4 +295,30 @@ public class PinnedSliceStateTest extends UiServiceTestCase { })); verify(listener).onSliceUpdated(eq(s)); } + + @Test + public void testRecheckPackage() throws RemoteException { + TestableLooper.get(this).processAllMessages(); + + ISliceListener listener = mock(ISliceListener.class); + when(listener.asBinder()).thenReturn(new Binder()); + + mPinnedSliceManager.addSliceListener(listener, mContext.getPackageName(), FIRST_SPECS, + false); + TestableLooper.get(this).processAllMessages(); + + verify(mIContentProvider, never()).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null), + any()); + + when(mSliceService.checkAccess(any(), any(), anyInt(), anyInt())) + .thenReturn(PERMISSION_GRANTED); + mPinnedSliceManager.recheckPackage(mContext.getPackageName()); + TestableLooper.get(this).processAllMessages(); + + verify(mIContentProvider).call(anyString(), eq(SliceProvider.METHOD_PIN), eq(null), + argThat(b -> { + assertEquals(TEST_URI, b.getParcelable(SliceProvider.EXTRA_BIND_URI)); + return true; + })); + } } \ No newline at end of file -- GitLab From fa9ed96f1e9970e8867daa0b76175006c082b890 Mon Sep 17 00:00:00 2001 From: Jorim Jaggi Date: Thu, 25 Jan 2018 00:16:49 +0100 Subject: [PATCH 096/416] Fix mNoAnimActivities Since we split tasks into separate stacks, mNoAnimActivites from other tasks/stacks were pretty much never cleared, leading to skipped animations in the following cases: - Enter/exit multi-window, relaunch anything, animation was always skipped. - Reopen PIP activity from notification shade Now, we make this a global list which we immediately clear after using it. Test: go/wm-smoke Test: Enter/exit multi-window, relaunch Bug: 64674361 Change-Id: Ic36d278b263d6b3c6a7ec7416c8b94c4eed8e440 --- .../com/android/server/am/ActivityRecord.java | 2 +- .../com/android/server/am/ActivityStack.java | 28 ++++++------------- .../server/am/ActivityStackSupervisor.java | 6 ++++ .../android/server/am/RecentsAnimation.java | 2 +- .../com/android/server/am/TaskRecord.java | 2 +- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 3bef87794c2..cae0d2bc5dd 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1695,7 +1695,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo resumeKeyDispatchingLocked(); final ActivityStack stack = getStack(); - stack.mNoAnimActivities.clear(); + mStackSupervisor.mNoAnimActivities.clear(); // Mark the point when the activity is resuming // TODO: To be more accurate, the mark should be before the onCreate, diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index fd3f8ec92cf..ab2dc36d66d 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -276,12 +276,6 @@ class ActivityStack extends ConfigurationContai */ final ArrayList mLRUActivities = new ArrayList<>(); - /** - * Animations that for the current transition have requested not to - * be considered for the transition animation. - */ - final ArrayList mNoAnimActivities = new ArrayList<>(); - /** * When we are in the process of pausing an activity, before starting the * next one, this variable holds the activity that is currently being paused. @@ -550,7 +544,7 @@ class ActivityStack extends ConfigurationContai wm.deferSurfaceLayout(); try { if (!animate && topActivity != null) { - mNoAnimActivities.add(topActivity); + mStackSupervisor.mNoAnimActivities.add(topActivity); } super.setWindowingMode(windowingMode); @@ -2460,7 +2454,7 @@ class ActivityStack extends ConfigurationContai if (prev.finishing) { if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev); - if (mNoAnimActivities.contains(prev)) { + if (mStackSupervisor.mNoAnimActivities.contains(prev)) { anim = false; mWindowManager.prepareAppTransition(TRANSIT_NONE, false); } else { @@ -2472,7 +2466,7 @@ class ActivityStack extends ConfigurationContai } else { if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: prev=" + prev); - if (mNoAnimActivities.contains(next)) { + if (mStackSupervisor.mNoAnimActivities.contains(next)) { anim = false; mWindowManager.prepareAppTransition(TRANSIT_NONE, false); } else { @@ -2485,7 +2479,7 @@ class ActivityStack extends ConfigurationContai } } else { if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous"); - if (mNoAnimActivities.contains(next)) { + if (mStackSupervisor.mNoAnimActivities.contains(next)) { anim = false; mWindowManager.prepareAppTransition(TRANSIT_NONE, false); } else { @@ -2493,17 +2487,14 @@ class ActivityStack extends ConfigurationContai } } - Bundle resumeAnimOptions = null; if (anim) { - ActivityOptions opts = next.getOptionsForTargetActivityLocked(); - if (opts != null) { - resumeAnimOptions = opts.toBundle(); - } next.applyOptionsLocked(); } else { next.clearOptionsLocked(); } + mStackSupervisor.mNoAnimActivities.clear(); + ActivityStack lastStack = mStackSupervisor.getLastStack(); if (next.app != null && next.app.thread != null) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resume running: " + next @@ -2859,7 +2850,7 @@ class ActivityStack extends ConfigurationContai "Prepare open transition: starting " + r); if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { mWindowManager.prepareAppTransition(TRANSIT_NONE, keepCurTransition); - mNoAnimActivities.add(r); + mStackSupervisor.mNoAnimActivities.add(r); } else { int transit = TRANSIT_ACTIVITY_OPEN; if (newTask) { @@ -2878,7 +2869,7 @@ class ActivityStack extends ConfigurationContai } } mWindowManager.prepareAppTransition(transit, keepCurTransition); - mNoAnimActivities.remove(r); + mStackSupervisor.mNoAnimActivities.remove(r); } boolean doShow = true; if (newTask) { @@ -4497,7 +4488,7 @@ class ActivityStack extends ConfigurationContai if (noAnimation) { mWindowManager.prepareAppTransition(TRANSIT_NONE, false); if (r != null) { - mNoAnimActivities.add(r); + mStackSupervisor.mNoAnimActivities.add(r); } ActivityOptions.abort(options); } else { @@ -5190,7 +5181,6 @@ class ActivityStack extends ConfigurationContai void executeAppTransition(ActivityOptions options) { mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); ActivityOptions.abort(options); } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 7c9160a2e4c..bf388258769 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -357,6 +357,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * application */ final ArrayList mPipModeChangedActivities = new ArrayList<>(); + /** + * Animations that for the current transition have requested not to + * be considered for the transition animation. + */ + final ArrayList mNoAnimActivities = new ArrayList<>(); + /** The target stack bounds for the picture-in-picture mode changed that we need to report to * the application */ Rect mPipModeChangedTargetStackBounds; diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java index c188ccb4d27..e7b067b1ab7 100644 --- a/services/core/java/com/android/server/am/RecentsAnimation.java +++ b/services/core/java/com/android/server/am/RecentsAnimation.java @@ -150,7 +150,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks { if (moveHomeToTop) { // Bring the home stack to the front final ActivityStack homeStack = homeActivity.getStack(); - homeStack.mNoAnimActivities.add(homeActivity); + mStackSupervisor.mNoAnimActivities.add(homeActivity); homeStack.moveToFront("RecentsAnimation.onAnimationFinished()"); } else { // Restore the home stack to its previous position diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 809f19f6431..d679439d3b7 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -695,7 +695,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi wasPaused, reason); } if (!animate) { - toStack.mNoAnimActivities.add(topActivity); + mService.mStackSupervisor.mNoAnimActivities.add(topActivity); } // We might trigger a configuration change. Save the current task bounds for freezing. -- GitLab From dcf50a4f4e930b806fccf9f287d2453a50abfca1 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Mon, 29 Jan 2018 18:31:34 +0100 Subject: [PATCH 097/416] ViewRootImpl: Notify SurfaceHolder.Callback if the surface size changed Bug: 72492508 Test: Add SurfaceHolder window, rotate screen, verify surfaceChanged is called Change-Id: Ifd7d4577367d8bd65e4a9f246bc29d667ecf0cc3 --- core/java/android/view/ViewRootImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 30f584c570c..ced6d4a3e5d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1974,6 +1974,7 @@ public final class ViewRootImpl implements ViewParent, final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets); final boolean surfaceSizeChanged = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0; + surfaceChanged |= surfaceSizeChanged; final boolean alwaysConsumeNavBarChanged = mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar; if (contentInsetsChanged) { -- GitLab From 3a28570b285d9248206fc0ab8b30e3b2a51ae5b3 Mon Sep 17 00:00:00 2001 From: Brian Young Date: Mon, 29 Jan 2018 23:56:59 +0000 Subject: [PATCH 098/416] Revert "Add "Unlocked device required" parameter to keys" This reverts commit 55fff3a89d96d0d0f8b8cb161bb0dda170c21ccb. Reason for revert: Build breakages on elfin, gce_x86_phone. Bug: 67752510 Bug: 72679761 Change-Id: Ia495e9cb158b64fcf015e37b170554a7ed6810a7 --- api/current.txt | 6 -- .../android/security/IKeystoreService.aidl | 2 +- .../security/keymaster/KeymasterDefs.java | 3 - keystore/java/android/security/KeyStore.java | 4 +- .../AndroidKeyStoreKeyGeneratorSpi.java | 17 +++++- .../AndroidKeyStoreKeyPairGeneratorSpi.java | 14 ++++- .../security/keystore/AndroidKeyStoreSpi.java | 14 ++++- .../keystore/KeyGenParameterSpec.java | 40 +------------ .../security/keystore/KeyProtection.java | 56 +------------------ .../security/keystore/KeymasterUtils.java | 36 +++++------- .../security/keystore/UserAuthArgs.java | 37 ------------ .../fingerprint/FingerprintService.java | 2 +- .../policy/keyguard/KeyguardStateMonitor.java | 13 ----- 13 files changed, 64 insertions(+), 180 deletions(-) delete mode 100644 keystore/java/android/security/keystore/UserAuthArgs.java diff --git a/api/current.txt b/api/current.txt index 5646f6f6ade..b7016a2221f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -38150,7 +38150,6 @@ package android.security.keystore { method public boolean isRandomizedEncryptionRequired(); method public boolean isStrongBoxBacked(); method public boolean isTrustedUserPresenceRequired(); - method public boolean isUnlockedDeviceRequired(); method public boolean isUserAuthenticationRequired(); method public boolean isUserAuthenticationValidWhileOnBody(); } @@ -38177,7 +38176,6 @@ package android.security.keystore { method public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...); method public android.security.keystore.KeyGenParameterSpec.Builder setTrustedUserPresenceRequired(boolean); - method public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int); @@ -38267,8 +38265,6 @@ package android.security.keystore { method public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); method public boolean isRandomizedEncryptionRequired(); - method public boolean isTrustedUserPresenceRequired(); - method public boolean isUnlockedDeviceRequired(); method public boolean isUserAuthenticationRequired(); method public boolean isUserAuthenticationValidWhileOnBody(); } @@ -38286,8 +38282,6 @@ package android.security.keystore { method public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date); method public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean); method public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...); - method public android.security.keystore.KeyProtection.Builder setTrustedUserPresenceRequired(boolean); - method public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean); method public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean); method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean); method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int); diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl index c4b7715b458..738eb686523 100644 --- a/core/java/android/security/IKeystoreService.aidl +++ b/core/java/android/security/IKeystoreService.aidl @@ -71,7 +71,7 @@ interface IKeystoreService { in byte[] entropy); int abort(IBinder handle); boolean isOperationAuthorized(IBinder token); - int addAuthToken(in byte[] authToken, in int androidId); + int addAuthToken(in byte[] authToken); int onUserAdded(int userId, int parentId); int onUserRemoved(int userId); int attestKey(String alias, in KeymasterArguments params, out KeymasterCertificateChain chain); diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index 479231db70b..34643703284 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -74,7 +74,6 @@ public final class KeymasterDefs { public static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505; public static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506; public static final int KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED = KM_BOOL | 507; - public static final int KM_TAG_UNLOCKED_DEVICE_REQUIRED = KM_BOOL | 509; public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600; public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601; @@ -216,7 +215,6 @@ public final class KeymasterDefs { public static final int KM_ERROR_MISSING_MIN_MAC_LENGTH = -58; public static final int KM_ERROR_UNSUPPORTED_MIN_MAC_LENGTH = -59; public static final int KM_ERROR_CANNOT_ATTEST_IDS = -66; - public static final int KM_ERROR_DEVICE_LOCKED = -72; public static final int KM_ERROR_UNIMPLEMENTED = -100; public static final int KM_ERROR_VERSION_MISMATCH = -101; public static final int KM_ERROR_UNKNOWN_ERROR = -1000; @@ -263,7 +261,6 @@ public final class KeymasterDefs { sErrorCodeToString.put(KM_ERROR_INVALID_MAC_LENGTH, "Invalid MAC or authentication tag length"); sErrorCodeToString.put(KM_ERROR_CANNOT_ATTEST_IDS, "Unable to attest device ids"); - sErrorCodeToString.put(KM_ERROR_DEVICE_LOCKED, "Device locked"); sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented"); sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error"); } diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java index c429fd382d6..ded427eb244 100644 --- a/keystore/java/android/security/KeyStore.java +++ b/keystore/java/android/security/KeyStore.java @@ -618,9 +618,9 @@ public class KeyStore { * @return {@code KeyStore.NO_ERROR} on success, otherwise an error value corresponding to * a {@code KeymasterDefs.KM_ERROR_} value or {@code KeyStore} ResponseCode. */ - public int addAuthToken(byte[] authToken, int userId) { + public int addAuthToken(byte[] authToken) { try { - return mBinder.addAuthToken(authToken, userId); + return mBinder.addAuthToken(authToken); } catch (RemoteException e) { Log.w(TAG, "Cannot connect to keystore", e); return SYSTEM_ERROR; diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java index 419eb24e1cc..f721ed3af7b 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java @@ -243,7 +243,12 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { // Check that user authentication related parameters are acceptable. This method // will throw an IllegalStateException if there are issues (e.g., secure lock screen // not set up). - KeymasterUtils.addUserAuthArgs(new KeymasterArguments(), spec); + KeymasterUtils.addUserAuthArgs(new KeymasterArguments(), + spec.isUserAuthenticationRequired(), + spec.getUserAuthenticationValidityDurationSeconds(), + spec.isUserAuthenticationValidWhileOnBody(), + spec.isInvalidatedByBiometricEnrollment(), + GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */); } catch (IllegalStateException | IllegalArgumentException e) { throw new InvalidAlgorithmParameterException(e); } @@ -279,7 +284,15 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { args.addEnums(KeymasterDefs.KM_TAG_BLOCK_MODE, mKeymasterBlockModes); args.addEnums(KeymasterDefs.KM_TAG_PADDING, mKeymasterPaddings); args.addEnums(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests); - KeymasterUtils.addUserAuthArgs(args, spec); + KeymasterUtils.addUserAuthArgs(args, + spec.isUserAuthenticationRequired(), + spec.getUserAuthenticationValidityDurationSeconds(), + spec.isUserAuthenticationValidWhileOnBody(), + spec.isInvalidatedByBiometricEnrollment(), + GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */); + if (spec.isTrustedUserPresenceRequired()) { + args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED); + } KeymasterUtils.addMinMacLengthAuthorizationIfNecessary( args, mKeymasterAlgorithm, diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index d68a33de2c6..d1eb6888bbf 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -344,7 +344,12 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato // Check that user authentication related parameters are acceptable. This method // will throw an IllegalStateException if there are issues (e.g., secure lock screen // not set up). - KeymasterUtils.addUserAuthArgs(new KeymasterArguments(), mSpec); + KeymasterUtils.addUserAuthArgs(new KeymasterArguments(), + mSpec.isUserAuthenticationRequired(), + mSpec.getUserAuthenticationValidityDurationSeconds(), + mSpec.isUserAuthenticationValidWhileOnBody(), + mSpec.isInvalidatedByBiometricEnrollment(), + GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */); } catch (IllegalArgumentException | IllegalStateException e) { throw new InvalidAlgorithmParameterException(e); } @@ -535,7 +540,12 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato args.addEnums(KeymasterDefs.KM_TAG_PADDING, mKeymasterSignaturePaddings); args.addEnums(KeymasterDefs.KM_TAG_DIGEST, mKeymasterDigests); - KeymasterUtils.addUserAuthArgs(args, mSpec); + KeymasterUtils.addUserAuthArgs(args, + mSpec.isUserAuthenticationRequired(), + mSpec.getUserAuthenticationValidityDurationSeconds(), + mSpec.isUserAuthenticationValidWhileOnBody(), + mSpec.isInvalidatedByBiometricEnrollment(), + GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */); args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart()); args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, mSpec.getKeyValidityForOriginationEnd()); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java index fc86ca0443b..440e0863fbb 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java @@ -497,7 +497,12 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { importArgs.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterEncryptionPaddings); importArgs.addEnums(KeymasterDefs.KM_TAG_PADDING, KeyProperties.SignaturePadding.allToKeymaster(spec.getSignaturePaddings())); - KeymasterUtils.addUserAuthArgs(importArgs, spec); + KeymasterUtils.addUserAuthArgs(importArgs, + spec.isUserAuthenticationRequired(), + spec.getUserAuthenticationValidityDurationSeconds(), + spec.isUserAuthenticationValidWhileOnBody(), + spec.isInvalidatedByBiometricEnrollment(), + spec.getBoundToSpecificSecureUserId()); importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart()); importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, @@ -694,7 +699,12 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { int[] keymasterPaddings = KeyProperties.EncryptionPadding.allToKeymaster( params.getEncryptionPaddings()); args.addEnums(KeymasterDefs.KM_TAG_PADDING, keymasterPaddings); - KeymasterUtils.addUserAuthArgs(args, params); + KeymasterUtils.addUserAuthArgs(args, + params.isUserAuthenticationRequired(), + params.getUserAuthenticationValidityDurationSeconds(), + params.isUserAuthenticationValidWhileOnBody(), + params.isInvalidatedByBiometricEnrollment(), + params.getBoundToSpecificSecureUserId()); KeymasterUtils.addMinMacLengthAuthorizationIfNecessary( args, keymasterAlgorithm, diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 0291b8ac698..a896c72463f 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -21,7 +21,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.KeyguardManager; import android.hardware.fingerprint.FingerprintManager; -import android.security.GateKeeper; import android.security.KeyStore; import android.text.TextUtils; @@ -233,7 +232,7 @@ import javax.security.auth.x500.X500Principal; * key = (SecretKey) keyStore.getKey("key2", null); * } */ -public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs { +public final class KeyGenParameterSpec implements AlgorithmParameterSpec { private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake"); private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1"); @@ -265,7 +264,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final boolean mUserAuthenticationValidWhileOnBody; private final boolean mInvalidatedByBiometricEnrollment; private final boolean mIsStrongBoxBacked; - private final boolean mUnlockedDeviceRequired; /** * @hide should be built with Builder @@ -295,8 +293,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu boolean uniqueIdIncluded, boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, - boolean isStrongBoxBacked, - boolean unlockedDeviceRequired) { + boolean isStrongBoxBacked) { if (TextUtils.isEmpty(keyStoreAlias)) { throw new IllegalArgumentException("keyStoreAlias must not be empty"); } @@ -344,7 +341,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; mIsStrongBoxBacked = isStrongBoxBacked; - mUnlockedDeviceRequired = unlockedDeviceRequired; } /** @@ -649,22 +645,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu return mIsStrongBoxBacked; } - /** - * Returns {@code true} if the key cannot be used unless the device screen is unlocked. - * - * @see Builder#SetUnlockedDeviceRequired(boolean) - */ - public boolean isUnlockedDeviceRequired() { - return mUnlockedDeviceRequired; - } - - /** - * @hide - */ - public long getBoundToSpecificSecureUserId() { - return GateKeeper.INVALID_SECURE_USER_ID; - } - /** * Builder of {@link KeyGenParameterSpec} instances. */ @@ -695,7 +675,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private boolean mUserAuthenticationValidWhileOnBody; private boolean mInvalidatedByBiometricEnrollment = true; private boolean mIsStrongBoxBacked = false; - private boolean mUnlockedDeviceRequired = false; /** * Creates a new instance of the {@code Builder}. @@ -1240,18 +1219,6 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu return this; } - /** - * Sets whether the keystore requires the screen to be unlocked before allowing decryption - * using this key. If this is set to {@code true}, any attempt to decrypt using this key - * while the screen is locked will fail. A locked device requires a PIN, password, - * fingerprint, or other trusted factor to access. - */ - @NonNull - public Builder setUnlockedDeviceRequired(boolean unlockedDeviceRequired) { - mUnlockedDeviceRequired = unlockedDeviceRequired; - return this; - } - /** * Builds an instance of {@code KeyGenParameterSpec}. */ @@ -1282,8 +1249,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUniqueIdIncluded, mUserAuthenticationValidWhileOnBody, mInvalidatedByBiometricEnrollment, - mIsStrongBoxBacked, - mUnlockedDeviceRequired); + mIsStrongBoxBacked); } } } diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index a5b85ceec1a..dbacb9c53dd 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -212,7 +212,7 @@ import javax.crypto.Mac; * ... * } */ -public final class KeyProtection implements ProtectionParameter, UserAuthArgs { +public final class KeyProtection implements ProtectionParameter { private final Date mKeyValidityStart; private final Date mKeyValidityForOriginationEnd; private final Date mKeyValidityForConsumptionEnd; @@ -228,8 +228,6 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private final boolean mInvalidatedByBiometricEnrollment; private final long mBoundToSecureUserId; private final boolean mCriticalToDeviceEncryption; - private final boolean mTrustedUserPresenceRequired; - private final boolean mUnlockedDeviceRequired; private KeyProtection( Date keyValidityStart, @@ -243,12 +241,10 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { boolean randomizedEncryptionRequired, boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds, - boolean trustedUserPresenceRequired, boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, long boundToSecureUserId, - boolean criticalToDeviceEncryption, - boolean unlockedDeviceRequired) { + boolean criticalToDeviceEncryption) { mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart); mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd); mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd); @@ -266,8 +262,6 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; mBoundToSecureUserId = boundToSecureUserId; mCriticalToDeviceEncryption = criticalToDeviceEncryption; - mTrustedUserPresenceRequired = trustedUserPresenceRequired; - mUnlockedDeviceRequired = unlockedDeviceRequired; } /** @@ -419,14 +413,6 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { return mUserAuthenticationValidityDurationSeconds; } - /** - * Returns {@code true} if the key is authorized to be used only if a test of user presence has - * been performed between the {@code Signature.initSign()} and {@code Signature.sign()} calls. - */ - public boolean isTrustedUserPresenceRequired() { - return mTrustedUserPresenceRequired; - } - /** * Returns {@code true} if the key will be de-authorized when the device is removed from the * user's body. This option has no effect on keys that don't have an authentication validity @@ -484,15 +470,6 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { return mCriticalToDeviceEncryption; } - /** - * Returns {@code true} if the key cannot be used unless the device screen is unlocked. - * - * @see Builder#SetRequireDeviceUnlocked(boolean) - */ - public boolean isUnlockedDeviceRequired() { - return mUnlockedDeviceRequired; - } - /** * Builder of {@link KeyProtection} instances. */ @@ -511,9 +488,6 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private int mUserAuthenticationValidityDurationSeconds = -1; private boolean mUserAuthenticationValidWhileOnBody; private boolean mInvalidatedByBiometricEnrollment = true; - private boolean mTrustedUserPresenceRequired = false; - private boolean mUnlockedDeviceRequired = false; - private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID; private boolean mCriticalToDeviceEncryption = false; @@ -789,16 +763,6 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { return this; } - /** - * Sets whether a test of user presence is required to be performed between the - * {@code Signature.initSign()} and {@code Signature.sign()} method calls. - */ - @NonNull - public Builder setTrustedUserPresenceRequired(boolean required) { - mTrustedUserPresenceRequired = required; - return this; - } - /** * Sets whether the key will remain authorized only until the device is removed from the * user's body up to the limit of the authentication validity period (see @@ -880,18 +844,6 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { return this; } - /** - * Sets whether the keystore requires the screen to be unlocked before allowing decryption - * using this key. If this is set to {@code true}, any attempt to decrypt using this key - * while the screen is locked will fail. A locked device requires a PIN, password, - * fingerprint, or other trusted factor to access. - */ - @NonNull - public Builder setUnlockedDeviceRequired(boolean unlockedDeviceRequired) { - mUnlockedDeviceRequired = unlockedDeviceRequired; - return this; - } - /** * Builds an instance of {@link KeyProtection}. * @@ -911,12 +863,10 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { mRandomizedEncryptionRequired, mUserAuthenticationRequired, mUserAuthenticationValidityDurationSeconds, - mTrustedUserPresenceRequired, mUserAuthenticationValidWhileOnBody, mInvalidatedByBiometricEnrollment, mBoundToSecureUserId, - mCriticalToDeviceEncryption, - mUnlockedDeviceRequired); + mCriticalToDeviceEncryption); } } } diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java index eb6a2a2c9e6..34c8d1f75f6 100644 --- a/keystore/java/android/security/keystore/KeymasterUtils.java +++ b/keystore/java/android/security/keystore/KeymasterUtils.java @@ -98,23 +98,17 @@ public abstract class KeymasterUtils { * require user authentication. */ public static void addUserAuthArgs(KeymasterArguments args, - UserAuthArgs spec) { - args.addUnsignedInt(KeymasterDefs.KM_TAG_USER_ID, 0); - - if (spec.isTrustedUserPresenceRequired()) { - args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED); - } - - if (spec.isUnlockedDeviceRequired()) { - args.addBoolean(KeymasterDefs.KM_TAG_UNLOCKED_DEVICE_REQUIRED); - } - - if (!spec.isUserAuthenticationRequired()) { + boolean userAuthenticationRequired, + int userAuthenticationValidityDurationSeconds, + boolean userAuthenticationValidWhileOnBody, + boolean invalidatedByBiometricEnrollment, + long boundToSpecificSecureUserId) { + if (!userAuthenticationRequired) { args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); return; } - if (spec.getUserAuthenticationValidityDurationSeconds() == -1) { + if (userAuthenticationValidityDurationSeconds == -1) { // Every use of this key needs to be authorized by the user. This currently means // fingerprint-only auth. FingerprintManager fingerprintManager = @@ -130,9 +124,9 @@ public abstract class KeymasterUtils { } long sid; - if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) { - sid = spec.getBoundToSpecificSecureUserId(); - } else if (spec.isInvalidatedByBiometricEnrollment()) { + if (boundToSpecificSecureUserId != GateKeeper.INVALID_SECURE_USER_ID) { + sid = boundToSpecificSecureUserId; + } else if (invalidatedByBiometricEnrollment) { // The fingerprint-only SID will change on fingerprint enrollment or removal of all, // enrolled fingerprints, invalidating the key. sid = fingerprintOnlySid; @@ -145,14 +139,14 @@ public abstract class KeymasterUtils { args.addUnsignedLong( KeymasterDefs.KM_TAG_USER_SECURE_ID, KeymasterArguments.toUint64(sid)); args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_FINGERPRINT); - if (spec.isUserAuthenticationValidWhileOnBody()) { + if (userAuthenticationValidWhileOnBody) { throw new ProviderException("Key validity extension while device is on-body is not " + "supported for keys requiring fingerprint authentication"); } } else { long sid; - if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) { - sid = spec.getBoundToSpecificSecureUserId(); + if (boundToSpecificSecureUserId != GateKeeper.INVALID_SECURE_USER_ID) { + sid = boundToSpecificSecureUserId; } else { // The key is authorized for use for the specified amount of time after the user has // authenticated. Whatever unlocks the secure lock screen should authorize this key. @@ -163,8 +157,8 @@ public abstract class KeymasterUtils { args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_PASSWORD | KeymasterDefs.HW_AUTH_FINGERPRINT); args.addUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT, - spec.getUserAuthenticationValidityDurationSeconds()); - if (spec.isUserAuthenticationValidWhileOnBody()) { + userAuthenticationValidityDurationSeconds); + if (userAuthenticationValidWhileOnBody) { args.addBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY); } } diff --git a/keystore/java/android/security/keystore/UserAuthArgs.java b/keystore/java/android/security/keystore/UserAuthArgs.java deleted file mode 100644 index 6fb34863959..00000000000 --- a/keystore/java/android/security/keystore/UserAuthArgs.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.security.keystore; - -/** - * @hide - * - * This is an interface to encapsulate the user authentication arguments that - * are passed to KeymasterUtils.addUserAuthArgs. Classes that represent - * authorization characteristics for new or imported keys can implement this - * interface to be passed to that method. - */ -public interface UserAuthArgs { - - boolean isUserAuthenticationRequired(); - int getUserAuthenticationValidityDurationSeconds(); - boolean isUserAuthenticationValidWhileOnBody(); - boolean isInvalidatedByBiometricEnrollment(); - boolean isTrustedUserPresenceRequired(); - boolean isUnlockedDeviceRequired(); - long getBoundToSpecificSecureUserId(); - -} diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 25a2100ff88..b5f94b1ce38 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -421,7 +421,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death byteToken[i] = token.get(i); } // Send to Keystore - KeyStore.getInstance().addAuthToken(byteToken, mCurrentUserId); + KeyStore.getInstance().addAuthToken(byteToken); } if (client != null && client.onAuthenticated(fingerId, groupId)) { removeClient(client); diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java index efcadadce3f..941cd4441e2 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardStateMonitor.java @@ -19,8 +19,6 @@ package com.android.server.policy.keyguard; import android.app.ActivityManager; import android.content.Context; import android.os.RemoteException; -import android.os.ServiceManager; -import android.security.IKeystoreService; import android.util.Slog; import com.android.internal.policy.IKeyguardService; @@ -53,16 +51,11 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { private final LockPatternUtils mLockPatternUtils; private final StateCallback mCallback; - IKeystoreService mKeystoreService; - public KeyguardStateMonitor(Context context, IKeyguardService service, StateCallback callback) { mLockPatternUtils = new LockPatternUtils(context); mCurrentUserId = ActivityManager.getCurrentUser(); mCallback = callback; - mKeystoreService = IKeystoreService.Stub.asInterface(ServiceManager - .getService("android.security.keystore")); - try { service.addStateMonitorCallback(this); } catch (RemoteException e) { @@ -93,12 +86,6 @@ public class KeyguardStateMonitor extends IKeyguardStateCallback.Stub { @Override // Binder interface public void onShowingStateChanged(boolean showing) { mIsShowing = showing; - - if (showing) try { - mKeystoreService.lock(mCurrentUserId); // as long as this doesn't recur... - } catch (RemoteException e) { - Slog.e(TAG, "Error locking keystore", e); - } } @Override // Binder interface -- GitLab From 4e0b244341c61f2c2680bb56755a813d4342d774 Mon Sep 17 00:00:00 2001 From: Ricky Wai Date: Mon, 29 Jan 2018 18:58:27 +0000 Subject: [PATCH 099/416] Report all watchlist events before last midnight Also, remove all watchlist events before last midnight when we want to clear db Bug: 63908748 Test: Visit watchlist site, then set date to tomorrow and reboot will see records in db is removed and watchlist report has this record Change-Id: I9543441a49c3ad3a0bf79ad9a9ec231ff10c0748 --- .../watchlist/WatchlistReportDbHelper.java | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java index c73b0cf1c8f..4b577bb9c91 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java @@ -141,20 +141,19 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper { } /** - * Aggregate the records in database, and return a rappor encoded result. + * Aggregate all records before most recent local midnight in database, and return a + * rappor encoded result. */ public AggregatedResult getAggregatedRecords() { - final long twoDaysBefore = getTwoDaysBeforeTimestamp(); - final long yesterday = getYesterdayTimestamp(); - final String selectStatement = WhiteListReportContract.TIMESTAMP + " >= ? AND " + - WhiteListReportContract.TIMESTAMP + " <= ?"; + final long lastMidnightTime = getLastMidnightTime(); + final String selectStatement = WhiteListReportContract.TIMESTAMP + " < ?"; final SQLiteDatabase db = getReadableDatabase(); Cursor c = null; try { c = db.query(true /* distinct */, WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement, - new String[]{"" + twoDaysBefore, "" + yesterday}, null, null, + new String[]{"" + lastMidnightTime}, null, null, null, null); if (c == null) { return null; @@ -182,23 +181,19 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper { } /** - * Remove all the records before yesterday. + * Remove all the records before most recent local midnight. * * @return True if success. */ public boolean cleanup() { final SQLiteDatabase db = getWritableDatabase(); - final long twoDaysBefore = getTwoDaysBeforeTimestamp(); - final String clause = WhiteListReportContract.TIMESTAMP + "< " + twoDaysBefore; + final long midnightTime = getLastMidnightTime(); + final String clause = WhiteListReportContract.TIMESTAMP + "< " + midnightTime; return db.delete(WhiteListReportContract.TABLE, clause, null) != 0; } - static long getTwoDaysBeforeTimestamp() { - return getMidnightTimestamp(2); - } - - static long getYesterdayTimestamp() { - return getMidnightTimestamp(1); + static long getLastMidnightTime() { + return getMidnightTimestamp(0); } static long getMidnightTimestamp(int daysBefore) { -- GitLab From 60593c244432334770eee772f1492411159cf617 Mon Sep 17 00:00:00 2001 From: Amin Shaikh Date: Tue, 30 Jan 2018 10:35:59 -0500 Subject: [PATCH 100/416] Update slice constants for range/input range. Test: make Bug: 68378584 Change-Id: Ie731edd779f60d0555ea3a573f465f4f3910a054 --- api/current.txt | 6 ++++-- core/java/android/app/slice/Slice.java | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/api/current.txt b/api/current.txt index 800412bd2e1..9e70ebb09fd 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7161,7 +7161,8 @@ package android.app.slice { method public android.net.Uri getUri(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; - field public static final java.lang.String EXTRA_SLIDER_VALUE = "android.app.slice.extra.SLIDER_VALUE"; + field public static final java.lang.String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE"; + field public static final deprecated java.lang.String EXTRA_SLIDER_VALUE = "android.app.slice.extra.SLIDER_VALUE"; field public static final java.lang.String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; field public static final java.lang.String HINT_ACTIONS = "actions"; field public static final java.lang.String HINT_CALLER_NEEDED = "caller_needed"; @@ -7181,7 +7182,8 @@ package android.app.slice { field public static final java.lang.String SUBTYPE_MAX = "max"; field public static final java.lang.String SUBTYPE_MESSAGE = "message"; field public static final java.lang.String SUBTYPE_PRIORITY = "priority"; - field public static final java.lang.String SUBTYPE_SLIDER = "slider"; + field public static final java.lang.String SUBTYPE_RANGE = "range"; + field public static final deprecated java.lang.String SUBTYPE_SLIDER = "slider"; field public static final java.lang.String SUBTYPE_SOURCE = "source"; field public static final java.lang.String SUBTYPE_TOGGLE = "toggle"; field public static final java.lang.String SUBTYPE_VALUE = "value"; diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index 4c24a2dd881..126deefae28 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -164,8 +164,14 @@ public final class Slice implements Parcelable { public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; /** * Key to retrieve an extra added to an intent when the value of a slider is changed. + * @deprecated remove once support lib is update to use EXTRA_RANGE_VALUE instead */ + @Deprecated public static final String EXTRA_SLIDER_VALUE = "android.app.slice.extra.SLIDER_VALUE"; + /** + * Key to retrieve an extra added to an intent when the value of an input range is changed. + */ + public static final String EXTRA_RANGE_VALUE = "android.app.slice.extra.RANGE_VALUE"; /** * Subtype to indicate that this is a message as part of a communication * sequence in this slice. @@ -181,14 +187,20 @@ public final class Slice implements Parcelable { public static final String SUBTYPE_COLOR = "color"; /** * Subtype to tag an item as representing a slider. + * @deprecated remove once support lib is update to use SUBTYPE_RANGE instead */ + @Deprecated public static final String SUBTYPE_SLIDER = "slider"; /** - * Subtype to tag an item as representing the max int value for a {@link #SUBTYPE_SLIDER}. + * Subtype to tag an item as representing a range. + */ + public static final String SUBTYPE_RANGE = "range"; + /** + * Subtype to tag an item as representing the max int value for a {@link #SUBTYPE_RANGE}. */ public static final String SUBTYPE_MAX = "max"; /** - * Subtype to tag an item as representing the current int value for a {@link #SUBTYPE_SLIDER}. + * Subtype to tag an item as representing the current int value for a {@link #SUBTYPE_RANGE}. */ public static final String SUBTYPE_VALUE = "value"; /** -- GitLab From 67d234d726a7101d48faa379cd4b02b1cde98574 Mon Sep 17 00:00:00 2001 From: Jan Althaus Date: Fri, 26 Jan 2018 17:53:31 +0100 Subject: [PATCH 101/416] Updating the text classifier model path The lib2 implementation of libtextclassifier is incompatible with models for lib1. To avoid trying to load a lib1 model and failing (which happens when devices are upgraded from O to P), we need a new update model path. Using this opportunity to remove smartselection from the filename, which isn't appropriate any more given what the model is used for. Test: Ran core framework tests Change-Id: I79a80d10d920019f5091fe9884f370149d39fe88 --- core/java/android/view/textclassifier/TextClassifierImpl.java | 2 +- .../android/server/updates/SmartSelectionInstallReceiver.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 6a44fb38ee1..9128d9765a0 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -72,7 +72,7 @@ final class TextClassifierImpl implements TextClassifier { private static final String MODEL_DIR = "/etc/textclassifier/"; private static final String MODEL_FILE_REGEX = "textclassifier\\.smartselection\\.(.*)\\.model"; private static final String UPDATED_MODEL_FILE_PATH = - "/data/misc/textclassifier/textclassifier.smartselection.model"; + "/data/misc/textclassifier/textclassifier.model"; private static final List ENTITY_TYPES_ALL = Collections.unmodifiableList(Arrays.asList( TextClassifier.TYPE_ADDRESS, diff --git a/services/core/java/com/android/server/updates/SmartSelectionInstallReceiver.java b/services/core/java/com/android/server/updates/SmartSelectionInstallReceiver.java index 1457366a17a..eff9a9a5b50 100644 --- a/services/core/java/com/android/server/updates/SmartSelectionInstallReceiver.java +++ b/services/core/java/com/android/server/updates/SmartSelectionInstallReceiver.java @@ -21,8 +21,8 @@ public class SmartSelectionInstallReceiver extends ConfigUpdateInstallReceiver { public SmartSelectionInstallReceiver() { super( "/data/misc/textclassifier/", - "textclassifier.smartselection.model", - "metadata/smartselection", + "textclassifier.model", + "metadata/classification", "version"); } -- GitLab From de956b7f9b9ffc1dd7b106605e9135938d6a34af Mon Sep 17 00:00:00 2001 From: "Torne (Richard Coles)" Date: Mon, 29 Jan 2018 16:58:44 -0500 Subject: [PATCH 102/416] Improve docs for WebView data dir methods. Update the javadoc for the WebView data directory methods to make it more clear in what cases these APIs should be used, and what limitations apply to applications with multiple data directories. Bug: 63748219 Test: n/a Change-Id: I98f49c7a75df00aedf2472731b915c3e30e6ba13 --- core/java/android/webkit/WebView.java | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index d2cb70e027c..65deb3b8557 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2106,6 +2106,18 @@ public class WebView extends AbsoluteLayout * the default directory, and other processes must call this API to define * a unique suffix. *

+ * This means that different processes in the same application cannot directly + * share WebView-related data, since the data directories must be distinct. + * Applications that use this API may have to explicitly pass data between + * processes. For example, login cookies may have to be copied from one + * process's cookie jar to the other using {@link CookieManager} if both + * processes' WebViews are intended to be logged in. + *

+ * Most applications should simply ensure that all components of the app + * that rely on WebView are in the same process, to avoid needing multiple + * data directories. The {@link #disableWebView} method can be used to ensure + * that the other processes do not use WebView by accident in this case. + *

* This API must be called before any instances of WebView are created in * this process and before any other methods in the android.webkit package * are called by this process. @@ -2126,10 +2138,14 @@ public class WebView extends AbsoluteLayout * methods in the android.webkit package are used. *

* Applications with multiple processes may wish to call this in processes - * which are not intended to use WebView to prevent potential data directory - * conflicts (see {@link #setDataDirectorySuffix}) and to avoid accidentally - * incurring the memory usage of initializing WebView in long-lived - * processes which have no need for it. + * that are not intended to use WebView to avoid accidentally incurring + * the memory usage of initializing WebView in long-lived processes that + * have no need for it, and to prevent potential data directory conflicts + * (see {@link #setDataDirectorySuffix}). + *

+ * For example, an audio player application with one process for its + * activities and another process for its playback service may wish to call + * this method in the playback service's {@link android.app.Service#onCreate}. * * @throws IllegalStateException if WebView has already been initialized * in the current process. -- GitLab From 34ea1103a0faa0e01df0e2b0ac1ce98e7ec3e3f1 Mon Sep 17 00:00:00 2001 From: Yangster-mac Date: Mon, 29 Jan 2018 12:40:55 -0800 Subject: [PATCH 103/416] Extend gauge metric to support memory metric. Test: statd unit test passed. Test: statsd unit test passed Change-Id: I2e3f26563678ae77d44afe168454b6d1ea449f3a --- .../src/metrics/GaugeMetricProducer.cpp | 74 +++++++++++++------ cmds/statsd/src/metrics/GaugeMetricProducer.h | 17 +++-- cmds/statsd/src/stats_log.proto | 4 +- cmds/statsd/src/statsd_config.proto | 6 ++ .../statsd/tests/e2e/GaugeMetric_e2e_test.cpp | 28 ++++--- .../metrics/GaugeMetricProducer_test.cpp | 29 +++++--- 6 files changed, 107 insertions(+), 51 deletions(-) diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 24dc5b0fba5..1072c5aae6e 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -58,6 +58,7 @@ const int FIELD_ID_BUCKET_INFO = 3; const int FIELD_ID_START_BUCKET_NANOS = 1; const int FIELD_ID_END_BUCKET_NANOS = 2; const int FIELD_ID_ATOM = 3; +const int FIELD_ID_TIMESTAMP = 4; GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& metric, const int conditionIndex, @@ -67,7 +68,7 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard), mStatsPullerManager(statsPullerManager), mPullTagId(pullTagId) { - mCurrentSlicedBucket = std::make_shared(); + mCurrentSlicedBucket = std::make_shared(); mCurrentSlicedBucketForAnomaly = std::make_shared(); int64_t bucketSizeMills = 0; if (metric.has_bucket()) { @@ -77,6 +78,7 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric } mBucketSizeNs = bucketSizeMills * 1000000; + mSamplingType = metric.sampling_type(); mFieldFilter = metric.gauge_fields_filter(); // TODO: use UidMap if uid->pkg_name is required @@ -89,7 +91,7 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric } // Kicks off the puller immediately. - if (mPullTagId != -1) { + if (mPullTagId != -1 && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { mStatsPullerManager->RegisterReceiver(mPullTagId, this, bucketSizeMills); } @@ -154,12 +156,23 @@ void GaugeMetricProducer::onDumpReportLocked(const uint64_t dumpTimeNs, (long long)bucket.mBucketStartNs); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS, (long long)bucket.mBucketEndNs); - long long atomToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM); - writeFieldValueTreeToStream(*bucket.mGaugeFields, protoOutput); - protoOutput->end(atomToken); + + if (!bucket.mGaugeAtoms.empty()) { + long long atomsToken = + protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_ATOM); + for (const auto& atom : bucket.mGaugeAtoms) { + writeFieldValueTreeToStream(*atom.mFields, protoOutput); + } + protoOutput->end(atomsToken); + + for (const auto& atom : bucket.mGaugeAtoms) { + protoOutput->write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_TIMESTAMP, + (long long)atom.mTimestamps); + } + } protoOutput->end(bucketInfoToken); - VLOG("\t bucket [%lld - %lld] includes %d gauge fields.", (long long)bucket.mBucketStartNs, - (long long)bucket.mBucketEndNs, (int)bucket.mGaugeFields->size()); + VLOG("\t bucket [%lld - %lld] includes %d atoms.", (long long)bucket.mBucketStartNs, + (long long)bucket.mBucketEndNs, (int)bucket.mGaugeAtoms.size()); } protoOutput->end(wrapperToken); } @@ -181,14 +194,26 @@ void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet, if (mPullTagId == -1) { return; } - // No need to pull again. Either scheduled pull or condition on true happened - if (!mCondition) { - return; + + bool triggerPuller = false; + switch(mSamplingType) { + // When the metric wants to do random sampling and there is already one gauge atom for the + // current bucket, do not do it again. + case GaugeMetric::RANDOM_ONE_SAMPLE: { + triggerPuller = mCondition && mCurrentSlicedBucket->empty(); + break; + } + case GaugeMetric::ALL_CONDITION_CHANGES: { + triggerPuller = true; + break; + } + default: + break; } - // Already have gauge metric for the current bucket, do not do it again. - if (mCurrentSlicedBucket->size() > 0) { + if (!triggerPuller) { return; } + vector> allData; if (!mStatsPullerManager->Pull(mPullTagId, &allData)) { ALOGE("Stats puller failed for tag: %d", mPullTagId); @@ -257,20 +282,24 @@ void GaugeMetricProducer::onMatchedLogEventInternalLocked( } flushIfNeededLocked(eventTimeNs); - // For gauge metric, we just simply use the first gauge in the given bucket. - if (mCurrentSlicedBucket->find(eventKey) != mCurrentSlicedBucket->end()) { + // When gauge metric wants to randomly sample the output atom, we just simply use the first + // gauge in the given bucket. + if (mCurrentSlicedBucket->find(eventKey) != mCurrentSlicedBucket->end() && + mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { return; } - std::shared_ptr gaugeFields = getGaugeFields(event); if (hitGuardRailLocked(eventKey)) { return; } - (*mCurrentSlicedBucket)[eventKey] = gaugeFields; + GaugeAtom gaugeAtom; + gaugeAtom.mFields = getGaugeFields(event); + gaugeAtom.mTimestamps = eventTimeNs; + (*mCurrentSlicedBucket)[eventKey].push_back(gaugeAtom); // Anomaly detection on gauge metric only works when there is one numeric // field specified. if (mAnomalyTrackers.size() > 0) { - if (gaugeFields->size() == 1) { - const DimensionsValue& dimensionsValue = gaugeFields->begin()->second; + if (gaugeAtom.mFields->size() == 1) { + const DimensionsValue& dimensionsValue = gaugeAtom.mFields->begin()->second; long gaugeVal = 0; if (dimensionsValue.has_value_int()) { gaugeVal = (long)dimensionsValue.value_int(); @@ -289,7 +318,10 @@ void GaugeMetricProducer::updateCurrentSlicedBucketForAnomaly() { mCurrentSlicedBucketForAnomaly->clear(); status_t err = NO_ERROR; for (const auto& slice : *mCurrentSlicedBucket) { - const DimensionsValue& dimensionsValue = slice.second->begin()->second; + if (slice.second.empty() || slice.second.front().mFields->empty()) { + continue; + } + const DimensionsValue& dimensionsValue = slice.second.front().mFields->begin()->second; long gaugeVal = 0; if (dimensionsValue.has_value_int()) { gaugeVal = (long)dimensionsValue.value_int(); @@ -318,7 +350,7 @@ void GaugeMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { info.mBucketNum = mCurrentBucketNum; for (const auto& slice : *mCurrentSlicedBucket) { - info.mGaugeFields = slice.second; + info.mGaugeAtoms = slice.second; auto& bucketList = mPastBuckets[slice.first]; bucketList.push_back(info); VLOG("gauge metric %lld, dump key value: %s", @@ -334,7 +366,7 @@ void GaugeMetricProducer::flushIfNeededLocked(const uint64_t& eventTimeNs) { } mCurrentSlicedBucketForAnomaly = std::make_shared(); - mCurrentSlicedBucket = std::make_shared(); + mCurrentSlicedBucket = std::make_shared(); // Adjusts the bucket start time int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs; diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index 1895edf8f72..6c013477af3 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -32,15 +32,20 @@ namespace android { namespace os { namespace statsd { +struct GaugeAtom { + std::shared_ptr mFields; + int64_t mTimestamps; +}; + struct GaugeBucket { int64_t mBucketStartNs; int64_t mBucketEndNs; - std::shared_ptr mGaugeFields; + std::vector mGaugeAtoms; uint64_t mBucketNum; }; -typedef std::unordered_map> - DimToGaugeFieldsMap; +typedef std::unordered_map> + DimToGaugeAtomsMap; // This gauge metric producer first register the puller to automatically pull the gauge at the // beginning of each bucket. If the condition is met, insert it to the bucket info. Otherwise @@ -48,7 +53,7 @@ typedef std::unordered_map> // producer always reports the guage at the earliest time of the bucket when the condition is met. class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver { public: - GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& countMetric, + GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex, const sp& wizard, const int pullTagId, const int64_t startTimeNs); @@ -97,7 +102,7 @@ private: std::unordered_map> mPastBuckets; // The current bucket. - std::shared_ptr mCurrentSlicedBucket; + std::shared_ptr mCurrentSlicedBucket; // The current bucket for anomaly detection. std::shared_ptr mCurrentSlicedBucketForAnomaly; @@ -108,6 +113,8 @@ private: // Whitelist of fields to report. Empty means all are reported. FieldFilter mFieldFilter; + GaugeMetric::SamplingType mSamplingType; + // apply a whitelist on the original input std::shared_ptr getGaugeFields(const LogEvent& event); diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index a4ccbd42883..af21ca4d82e 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -100,7 +100,9 @@ message GaugeBucketInfo { optional int64 end_bucket_nanos = 2; - optional Atom atom = 3; + repeated Atom atom = 3; + + repeated int64 timestamp_nanos = 4; } message GaugeMetricData { diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto index 07bbcb2190e..2ea79a64a5e 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -222,6 +222,12 @@ message GaugeMetric { optional TimeUnit bucket = 6; repeated MetricConditionLink links = 7; + + enum SamplingType { + RANDOM_ONE_SAMPLE = 1; + ALL_CONDITION_CHANGES = 2; + } + optional SamplingType sampling_type = 9 [default = RANDOM_ONE_SAMPLE] ; } message ValueMetric { diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp index e56a6c57848..a80fdc5606b 100644 --- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_test.cpp @@ -153,23 +153,26 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1 /* uid field */); EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), appUid1); EXPECT_EQ(data.bucket_info_size(), 3); + EXPECT_EQ(data.bucket_info(0).atom_size(), 1); EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs); EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::HOT); - EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name2"); - EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 102L); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_changed().type(), AppStartChanged::HOT); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_changed().activity_name(), "activity_name2"); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_changed().activity_start_msec(), 102L); + EXPECT_EQ(data.bucket_info(1).atom_size(), 1); EXPECT_EQ(data.bucket_info(1).start_bucket_nanos(), bucketStartTimeNs + bucketSizeNs); EXPECT_EQ(data.bucket_info(1).end_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); - EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().type(), AppStartChanged::WARM); - EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_name(), "activity_name4"); - EXPECT_EQ(data.bucket_info(1).atom().app_start_changed().activity_start_msec(), 104L); + EXPECT_EQ(data.bucket_info(1).atom(0).app_start_changed().type(), AppStartChanged::WARM); + EXPECT_EQ(data.bucket_info(1).atom(0).app_start_changed().activity_name(), "activity_name4"); + EXPECT_EQ(data.bucket_info(1).atom(0).app_start_changed().activity_start_msec(), 104L); + EXPECT_EQ(data.bucket_info(2).atom_size(), 1); EXPECT_EQ(data.bucket_info(2).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); EXPECT_EQ(data.bucket_info(2).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); - EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().type(), AppStartChanged::COLD); - EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_name(), "activity_name5"); - EXPECT_EQ(data.bucket_info(2).atom().app_start_changed().activity_start_msec(), 105L); + EXPECT_EQ(data.bucket_info(2).atom(0).app_start_changed().type(), AppStartChanged::COLD); + EXPECT_EQ(data.bucket_info(2).atom(0).app_start_changed().activity_name(), "activity_name5"); + EXPECT_EQ(data.bucket_info(2).atom(0).app_start_changed().activity_start_msec(), 105L); data = gaugeMetrics.data(1); @@ -178,11 +181,12 @@ TEST(GaugeMetricE2eTest, TestMultipleFieldsForPushedEvent) { EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 1 /* uid field */); EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), appUid2); EXPECT_EQ(data.bucket_info_size(), 1); + EXPECT_EQ(data.bucket_info(0).atom_size(), 1); EXPECT_EQ(data.bucket_info(0).start_bucket_nanos(), bucketStartTimeNs + 2 * bucketSizeNs); EXPECT_EQ(data.bucket_info(0).end_bucket_nanos(), bucketStartTimeNs + 3 * bucketSizeNs); - EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().type(), AppStartChanged::COLD); - EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_name(), "activity_name7"); - EXPECT_EQ(data.bucket_info(0).atom().app_start_changed().activity_start_msec(), 201L); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_changed().type(), AppStartChanged::COLD); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_changed().activity_name(), "activity_name7"); + EXPECT_EQ(data.bucket_info(0).atom(0).app_start_changed().activity_start_msec(), 201L); } #else diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 82772d854db..4533ac61005 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -78,7 +78,7 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { gaugeProducer.onDataPulled(allData); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second->begin(); + auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin(); EXPECT_EQ(10, it->second.value_int()); it++; EXPECT_EQ(11, it->second.value_int()); @@ -94,14 +94,14 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { allData.push_back(event2); gaugeProducer.onDataPulled(allData); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - it = gaugeProducer.mCurrentSlicedBucket->begin()->second->begin(); + it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin(); EXPECT_EQ(24, it->second.value_int()); it++; EXPECT_EQ(25, it->second.value_int()); // One dimension. EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.size()); - it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeFields->begin(); + it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin(); EXPECT_EQ(10L, it->second.value_int()); it++; EXPECT_EQ(11L, it->second.value_int()); @@ -112,7 +112,7 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { // One dimension. EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); - it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeFields->begin(); + it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin(); EXPECT_EQ(24L, it->second.value_int()); it++; EXPECT_EQ(25L, it->second.value_int()); @@ -151,7 +151,8 @@ TEST(GaugeMetricProducerTest, TestWithCondition) { gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 8); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(100, - gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + gaugeProducer.mCurrentSlicedBucket->begin()-> + second.front().mFields->begin()->second.value_int()); EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size()); vector> allData; @@ -165,17 +166,18 @@ TEST(GaugeMetricProducerTest, TestWithCondition) { EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(110, - gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + gaugeProducer.mCurrentSlicedBucket->begin()-> + second.front().mFields->begin()->second.value_int()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(100, gaugeProducer.mPastBuckets.begin()->second.back() - .mGaugeFields->begin()->second.value_int()); + .mGaugeAtoms.front().mFields->begin()->second.value_int()); gaugeProducer.onConditionChanged(false, bucket2StartTimeNs + 10); gaugeProducer.flushIfNeededLocked(bucket3StartTimeNs + 10); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); EXPECT_EQ(110L, gaugeProducer.mPastBuckets.begin()->second.back() - .mGaugeFields->begin()->second.value_int()); + .mGaugeAtoms.front().mFields->begin()->second.value_int()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.back().mBucketNum); } @@ -214,7 +216,8 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { gaugeProducer.onDataPulled({event1}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(13L, - gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + gaugeProducer.mCurrentSlicedBucket->begin()-> + second.front().mFields->begin()->second.value_int()); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), 0U); std::shared_ptr event2 = @@ -226,7 +229,8 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { gaugeProducer.onDataPulled({event2}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(15L, - gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + gaugeProducer.mCurrentSlicedBucket->begin()-> + second.front().mFields->begin()->second.value_int()); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec); @@ -239,7 +243,8 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { gaugeProducer.onDataPulled({event3}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(26L, - gaugeProducer.mCurrentSlicedBucket->begin()->second->begin()->second.value_int()); + gaugeProducer.mCurrentSlicedBucket->begin()-> + second.front().mFields->begin()->second.value_int()); EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(DEFAULT_DIMENSION_KEY), event2->GetTimestampNs() / NS_PER_SEC + refPeriodSec); @@ -250,7 +255,7 @@ TEST(GaugeMetricProducerTest, TestAnomalyDetection) { event4->init(); gaugeProducer.onDataPulled({event4}); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); - EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second->empty()); + EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->empty()); } } // namespace statsd -- GitLab From 596c2880b3ae0aae1bde030650c1511119da9917 Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Mon, 29 Jan 2018 14:39:37 +0000 Subject: [PATCH 104/416] API Review: Return status code for user management APIs - Replaced boolean return value with error code Bug: 71866621 Test: com.android.cts.devicepolicy.DeviceOwnerTest#testCreateAndManageUser_StartInBackground Test: com.android.cts.devicepolicy.DeviceOwnerTest#testCreateAndManageUser_StartInBackground_MaxRunningUsers Test: com.android.cts.devicepolicy.DeviceOwnerTest#testCreateAndManageUser_CannotStopCurrentUser Test: com.android.cts.devicepolicy.DeviceOwnerTest#testCreateAndManageUser_StopUser Test: com.android.cts.devicepolicy.DeviceOwnerTest#testCreateAndManageUser_LogoutUser Test: com.android.cts.devicepolicy.DeviceOwnerPlusProfileOwnerTest#testCannotStartManagedProfileInBackground Test: com.android.cts.devicepolicy.DeviceOwnerPlusProfileOwnerTest#testCannotStopManagedProfile Test: com.android.cts.devicepolicy.DeviceOwnerPlusProfileOwnerTest#testCannotLogoutManagedProfile Change-Id: Iddc3e33c91c3f9584d53e537dbab3f61b8772fb1 --- api/current.txt | 11 ++- .../app/admin/DevicePolicyManager.java | 89 +++++++++++++++++-- .../app/admin/IDevicePolicyManager.aidl | 6 +- .../BaseIDevicePolicyManager.java | 7 -- .../DevicePolicyManagerService.java | 66 ++++++++------ 5 files changed, 131 insertions(+), 48 deletions(-) diff --git a/api/current.txt b/api/current.txt index 2794a766174..922c56a5181 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6498,7 +6498,7 @@ package android.app.admin { method public boolean isUsingUnifiedPassword(android.content.ComponentName); method public void lockNow(); method public void lockNow(int); - method public boolean logoutUser(android.content.ComponentName); + method public int logoutUser(android.content.ComponentName); method public void reboot(android.content.ComponentName); method public void removeActiveAdmin(android.content.ComponentName); method public boolean removeCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); @@ -6583,8 +6583,8 @@ package android.app.admin { method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle); method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean); method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap); - method public boolean startUserInBackground(android.content.ComponentName, android.os.UserHandle); - method public boolean stopUser(android.content.ComponentName, android.os.UserHandle); + method public int startUserInBackground(android.content.ComponentName, android.os.UserHandle); + method public int stopUser(android.content.ComponentName, android.os.UserHandle); method public boolean switchUser(android.content.ComponentName, android.os.UserHandle); method public void transferOwnership(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle); method public void uninstallAllUserCaCerts(android.content.ComponentName); @@ -6698,6 +6698,11 @@ package android.app.admin { field public static final int RESET_PASSWORD_DO_NOT_ASK_CREDENTIALS_ON_BOOT = 2; // 0x2 field public static final int RESET_PASSWORD_REQUIRE_ENTRY = 1; // 0x1 field public static final int SKIP_SETUP_WIZARD = 1; // 0x1 + field public static final int USER_OPERATION_ERROR_CURRENT_USER = 4; // 0x4 + field public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2; // 0x2 + field public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3; // 0x3 + field public static final int USER_OPERATION_ERROR_UNKNOWN = 1; // 0x1 + field public static final int USER_OPERATION_SUCCESS = 0; // 0x0 field public static final int WIPE_EXTERNAL_STORAGE = 1; // 0x1 field public static final int WIPE_RESET_PROTECTION_DATA = 2; // 0x2 } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 131abb5e466..e190fd4cf6c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6608,16 +6608,82 @@ public class DevicePolicyManager { } } + /** + * Indicates user operation is successful. + * + * @see #startUserInBackground(ComponentName, UserHandle) + * @see #stopUser(ComponentName, UserHandle) + * @see #logoutUser(ComponentName) + */ + public static final int USER_OPERATION_SUCCESS = 0; + + /** + * Indicates user operation failed for unknown reason. + * + * @see #startUserInBackground(ComponentName, UserHandle) + * @see #stopUser(ComponentName, UserHandle) + * @see #logoutUser(ComponentName) + */ + public static final int USER_OPERATION_ERROR_UNKNOWN = 1; + + /** + * Indicates user operation failed because target user is a managed profile. + * + * @see #startUserInBackground(ComponentName, UserHandle) + * @see #stopUser(ComponentName, UserHandle) + * @see #logoutUser(ComponentName) + */ + public static final int USER_OPERATION_ERROR_MANAGED_PROFILE = 2; + + /** + * Indicates user operation failed because maximum running user limit has reached. + * + * @see #startUserInBackground(ComponentName, UserHandle) + */ + public static final int USER_OPERATION_ERROR_MAX_RUNNING_USERS = 3; + + /** + * Indicates user operation failed because the target user is in foreground. + * + * @see #stopUser(ComponentName, UserHandle) + * @see #logoutUser(ComponentName) + */ + public static final int USER_OPERATION_ERROR_CURRENT_USER = 4; + + /** + * Result returned from + *

    + *
  • {@link #startUserInBackground(ComponentName, UserHandle)}
  • + *
  • {@link #stopUser(ComponentName, UserHandle)}
  • + *
  • {@link #logoutUser(ComponentName)}
  • + *
+ * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = { "USER_OPERATION_" }, value = { + USER_OPERATION_SUCCESS, + USER_OPERATION_ERROR_UNKNOWN, + USER_OPERATION_ERROR_MANAGED_PROFILE, + USER_OPERATION_ERROR_MAX_RUNNING_USERS, + USER_OPERATION_ERROR_CURRENT_USER + }) + public @interface UserOperationResult {} + /** * Called by a device owner to start the specified secondary user in background. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param userHandle the user to be stopped. - * @return {@code true} if the user can be started, {@code false} otherwise. + * @param userHandle the user to be started in background. + * @return one of the following result codes: + * {@link #USER_OPERATION_ERROR_UNKNOWN}, + * {@link #USER_OPERATION_SUCCESS}, + * {@link #USER_OPERATION_ERROR_MANAGED_PROFILE}, + * {@link #USER_OPERATION_ERROR_MAX_RUNNING_USERS}, * @throws SecurityException if {@code admin} is not a device owner. * @see #getSecondaryUsers(ComponentName) */ - public boolean startUserInBackground( + public @UserOperationResult int startUserInBackground( @NonNull ComponentName admin, @NonNull UserHandle userHandle) { throwIfParentInstance("startUserInBackground"); try { @@ -6632,11 +6698,16 @@ public class DevicePolicyManager { * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param userHandle the user to be stopped. - * @return {@code true} if the user can be stopped, {@code false} otherwise. + * @return one of the following result codes: + * {@link #USER_OPERATION_ERROR_UNKNOWN}, + * {@link #USER_OPERATION_SUCCESS}, + * {@link #USER_OPERATION_ERROR_MANAGED_PROFILE}, + * {@link #USER_OPERATION_ERROR_CURRENT_USER} * @throws SecurityException if {@code admin} is not a device owner. * @see #getSecondaryUsers(ComponentName) */ - public boolean stopUser(@NonNull ComponentName admin, @NonNull UserHandle userHandle) { + public @UserOperationResult int stopUser( + @NonNull ComponentName admin, @NonNull UserHandle userHandle) { throwIfParentInstance("stopUser"); try { return mService.stopUser(admin, userHandle); @@ -6650,11 +6721,15 @@ public class DevicePolicyManager { * calling user and switch back to primary. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @return {@code true} if the exit was successful, {@code false} otherwise. + * @return one of the following result codes: + * {@link #USER_OPERATION_ERROR_UNKNOWN}, + * {@link #USER_OPERATION_SUCCESS}, + * {@link #USER_OPERATION_ERROR_MANAGED_PROFILE}, + * {@link #USER_OPERATION_ERROR_CURRENT_USER} * @throws SecurityException if {@code admin} is not a profile owner affiliated with the device. * @see #getSecondaryUsers(ComponentName) */ - public boolean logoutUser(@NonNull ComponentName admin) { + public @UserOperationResult int logoutUser(@NonNull ComponentName admin) { throwIfParentInstance("logoutUser"); try { return mService.logoutUser(admin); diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index cba9311ff68..5197de4cef9 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -227,9 +227,9 @@ interface IDevicePolicyManager { UserHandle createAndManageUser(in ComponentName who, in String name, in ComponentName profileOwner, in PersistableBundle adminExtras, in int flags); boolean removeUser(in ComponentName who, in UserHandle userHandle); boolean switchUser(in ComponentName who, in UserHandle userHandle); - boolean startUserInBackground(in ComponentName who, in UserHandle userHandle); - boolean stopUser(in ComponentName who, in UserHandle userHandle); - boolean logoutUser(in ComponentName who); + int startUserInBackground(in ComponentName who, in UserHandle userHandle); + int stopUser(in ComponentName who, in UserHandle userHandle); + int logoutUser(in ComponentName who); List getSecondaryUsers(in ComponentName who); void enableSystemApp(in ComponentName admin, in String callerPackage, in String packageName); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index d1cc5de821c..9fcf3eedd98 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -19,12 +19,10 @@ import android.annotation.UserIdInt; import android.app.admin.IDevicePolicyManager; import android.content.ComponentName; import android.os.PersistableBundle; -import android.os.UserHandle; import android.security.keymaster.KeymasterCertificateChain; import android.security.keystore.ParcelableKeyGenParameterSpec; import android.telephony.data.ApnSetting; -import com.android.internal.R; import com.android.server.SystemService; import java.util.ArrayList; @@ -106,11 +104,6 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { return false; } - @Override - public boolean startUserInBackground(ComponentName who, UserHandle userHandle) { - return false; - } - @Override public void setStartUserSessionMessage( ComponentName admin, CharSequence startUserSessionMessage) {} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index dae760576b4..7c7811a8de4 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -19,7 +19,6 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.BIND_DEVICE_ADMIN; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; -import static android.app.ActivityManager.USER_OP_SUCCESS; import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER; import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY; @@ -68,9 +67,6 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker .STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; -import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_ADB; -import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW; - import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER; import static com.android.server.devicepolicy.TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER; @@ -249,7 +245,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; -import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -9030,7 +9025,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public boolean startUserInBackground(ComponentName who, UserHandle userHandle) { + public int startUserInBackground(ComponentName who, UserHandle userHandle) { Preconditions.checkNotNull(who, "ComponentName is null"); Preconditions.checkNotNull(userHandle, "UserHandle is null"); @@ -9041,27 +9036,31 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int userId = userHandle.getIdentifier(); if (isManagedProfile(userId)) { Log.w(LOG_TAG, "Managed profile cannot be started in background"); - return false; + return DevicePolicyManager.USER_OPERATION_ERROR_MANAGED_PROFILE; } final long id = mInjector.binderClearCallingIdentity(); try { if (!mInjector.getActivityManagerInternal().canStartMoreUsers()) { Log.w(LOG_TAG, "Cannot start more users in background"); - return false; + return DevicePolicyManager.USER_OPERATION_ERROR_MAX_RUNNING_USERS; } - return mInjector.getIActivityManager().startUserInBackground(userId); + if (mInjector.getIActivityManager().startUserInBackground(userId)) { + return DevicePolicyManager.USER_OPERATION_SUCCESS; + } else { + return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN; + } } catch (RemoteException e) { // Same process, should not happen. - return false; + return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN; } finally { mInjector.binderRestoreCallingIdentity(id); } } @Override - public boolean stopUser(ComponentName who, UserHandle userHandle) { + public int stopUser(ComponentName who, UserHandle userHandle) { Preconditions.checkNotNull(who, "ComponentName is null"); Preconditions.checkNotNull(userHandle, "UserHandle is null"); @@ -9072,23 +9071,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final int userId = userHandle.getIdentifier(); if (isManagedProfile(userId)) { Log.w(LOG_TAG, "Managed profile cannot be stopped"); - return false; + return DevicePolicyManager.USER_OPERATION_ERROR_MANAGED_PROFILE; } - final long id = mInjector.binderClearCallingIdentity(); - try { - return mInjector.getIActivityManager().stopUser(userId, true /*force*/, null) - == USER_OP_SUCCESS; - } catch (RemoteException e) { - // Same process, should not happen. - return false; - } finally { - mInjector.binderRestoreCallingIdentity(id); - } + return stopUserUnchecked(userId); } @Override - public boolean logoutUser(ComponentName who) { + public int logoutUser(ComponentName who) { Preconditions.checkNotNull(who, "ComponentName is null"); final int callingUserId = mInjector.userHandleGetCallingUserId(); @@ -9102,20 +9092,40 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (isManagedProfile(callingUserId)) { Log.w(LOG_TAG, "Managed profile cannot be logout"); - return false; + return DevicePolicyManager.USER_OPERATION_ERROR_MANAGED_PROFILE; } final long id = mInjector.binderClearCallingIdentity(); try { if (!mInjector.getIActivityManager().switchUser(UserHandle.USER_SYSTEM)) { Log.w(LOG_TAG, "Failed to switch to primary user"); - return false; + // This should never happen as target user is UserHandle.USER_SYSTEM + return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN; } - return mInjector.getIActivityManager().stopUser(callingUserId, true /*force*/, null) - == USER_OP_SUCCESS; } catch (RemoteException e) { // Same process, should not happen. - return false; + return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN; + } finally { + mInjector.binderRestoreCallingIdentity(id); + } + + return stopUserUnchecked(callingUserId); + } + + private int stopUserUnchecked(int userId) { + final long id = mInjector.binderClearCallingIdentity(); + try { + switch (mInjector.getIActivityManager().stopUser(userId, true /*force*/, null)) { + case ActivityManager.USER_OP_SUCCESS: + return DevicePolicyManager.USER_OPERATION_SUCCESS; + case ActivityManager.USER_OP_IS_CURRENT: + return DevicePolicyManager.USER_OPERATION_ERROR_CURRENT_USER; + default: + return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN; + } + } catch (RemoteException e) { + // Same process, should not happen. + return DevicePolicyManager.USER_OPERATION_ERROR_UNKNOWN; } finally { mInjector.binderRestoreCallingIdentity(id); } -- GitLab From 628bb61c30a8912452ea533926a74466c0674a8f Mon Sep 17 00:00:00 2001 From: David Ogutu Date: Mon, 29 Jan 2018 12:56:40 -0500 Subject: [PATCH 105/416] Fix TimePicker separator l10n issue. This was surfacing in the android clock with certain locales e.g. fr_CA. Fixed for all unqiue locale patterns. Bug: 71572309 Test: manual - changed locale to Fr_CA and made sure clock seperator WAI Test: atest CtsWidgetTestCases:TextViewTest CtsWidgetTestCases:EditTextTest CtsWidgetTestCases:TextViewFadingEdgeTest FrameworksCoreTests:TextViewFallbackLineSpacingTest FrameworksCoreTests:TextViewTest Change-Id: Ie43bf9428e8c5ef2fe2e9545cb5a6dada25d6e52 --- core/java/android/text/format/DateFormat.java | 40 ++++++++----- .../widget/TimePickerClockDelegate.java | 57 +++++++++++++++---- 2 files changed, 73 insertions(+), 24 deletions(-) diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java index de2dcce5bc5..413cd103561 100755 --- a/core/java/android/text/format/DateFormat.java +++ b/core/java/android/text/format/DateFormat.java @@ -431,7 +431,7 @@ public class DateFormat { int c = s.charAt(i); if (c == QUOTE) { - count = appendQuotedText(s, i, len); + count = appendQuotedText(s, i); len = s.length(); continue; } @@ -574,36 +574,48 @@ public class DateFormat { : String.format(Locale.getDefault(), "%d", year); } - private static int appendQuotedText(SpannableStringBuilder s, int i, int len) { - if (i + 1 < len && s.charAt(i + 1) == QUOTE) { - s.delete(i, i + 1); + + /** + * Strips quotation marks from the {@code formatString} and appends the result back to the + * {@code formatString}. + * + * @param formatString the format string, as described in + * {@link android.text.format.DateFormat}, to be modified + * @param index index of the first quote + * @return the length of the quoted text that was appended. + * @hide + */ + public static int appendQuotedText(SpannableStringBuilder formatString, int index) { + int length = formatString.length(); + if (index + 1 < length && formatString.charAt(index + 1) == QUOTE) { + formatString.delete(index, index + 1); return 1; } int count = 0; // delete leading quote - s.delete(i, i + 1); - len--; + formatString.delete(index, index + 1); + length--; - while (i < len) { - char c = s.charAt(i); + while (index < length) { + char c = formatString.charAt(index); if (c == QUOTE) { // QUOTEQUOTE -> QUOTE - if (i + 1 < len && s.charAt(i + 1) == QUOTE) { + if (index + 1 < length && formatString.charAt(index + 1) == QUOTE) { - s.delete(i, i + 1); - len--; + formatString.delete(index, index + 1); + length--; count++; - i++; + index++; } else { // Closing QUOTE ends quoted text copying - s.delete(i, i + 1); + formatString.delete(index, index + 1); break; } } else { - i++; + index++; count++; } } diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java index 706b0ce225d..77670b35a1e 100644 --- a/core/java/android/widget/TimePickerClockDelegate.java +++ b/core/java/android/widget/TimePickerClockDelegate.java @@ -50,6 +50,7 @@ import com.android.internal.R; import com.android.internal.widget.NumericTextView; import com.android.internal.widget.NumericTextView.OnValueChangedListener; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Calendar; @@ -804,20 +805,56 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate { private void updateHeaderSeparator() { final String bestDateTimePattern = DateFormat.getBestDateTimePattern(mLocale, (mIs24Hour) ? "Hm" : "hm"); - final String separatorText; - // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats - final char[] hourFormats = {'H', 'h', 'K', 'k'}; - int hIndex = lastIndexOfAny(bestDateTimePattern, hourFormats); - if (hIndex == -1) { - // Default case - separatorText = ":"; - } else { - separatorText = Character.toString(bestDateTimePattern.charAt(hIndex + 1)); - } + final String separatorText = getHourMinSeparatorFromPattern(bestDateTimePattern); mSeparatorView.setText(separatorText); mTextInputPickerView.updateSeparator(separatorText); } + /** + * This helper method extracts the time separator from the {@code datetimePattern}. + * + * The time separator is defined in the Unicode CLDR and cannot be supposed to be ":". + * + * See http://unicode.org/cldr/trac/browser/trunk/common/main + * + * @return Separator string. This is the character or set of quoted characters just after the + * hour marker in {@code dateTimePattern}. Returns a colon (:) if it can't locate the + * separator. + * + * @hide + */ + private static String getHourMinSeparatorFromPattern(String dateTimePattern) { + final String defaultSeparator = ":"; + boolean foundHourPattern = false; + for (int i = 0; i < dateTimePattern.length(); i++) { + switch (dateTimePattern.charAt(i)) { + // See http://www.unicode.org/reports/tr35/tr35-dates.html for hour formats. + case 'H': + case 'h': + case 'K': + case 'k': + foundHourPattern = true; + continue; + case ' ': // skip spaces + continue; + case '\'': + if (!foundHourPattern) { + continue; + } + SpannableStringBuilder quotedSubstring = new SpannableStringBuilder( + dateTimePattern.substring(i)); + int quotedTextLength = DateFormat.appendQuotedText(quotedSubstring, 0); + return quotedSubstring.subSequence(0, quotedTextLength).toString(); + default: + if (!foundHourPattern) { + continue; + } + return Character.toString(dateTimePattern.charAt(i)); + } + } + return defaultSeparator; + } + static private int lastIndexOfAny(String str, char[] any) { final int lengthAny = any.length; if (lengthAny > 0) { -- GitLab From 4f1297a9d336291c6fccdcf7e621989361331814 Mon Sep 17 00:00:00 2001 From: Alison Cichowlas Date: Tue, 30 Jan 2018 11:40:36 -0500 Subject: [PATCH 106/416] Grant read/write permissions on share/edit screenshot. Test: Manually tested, being sure to cover receiving apps without auto-granted permissions. Change-Id: I868240449a4ec07715607f82981eee6a5bee210e --- .../com/android/systemui/screenshot/GlobalScreenshot.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 0132fa850f9..bf4a225a00e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -293,6 +293,7 @@ class SaveImageInBackgroundTask extends AsyncTask { sharingIntent.setType("image/png"); sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); + sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Create a share action for the notification. Note, we proxy the call to // ScreenshotActionReceiver because RemoteViews currently forces an activity options @@ -310,7 +311,9 @@ class SaveImageInBackgroundTask extends AsyncTask { Intent editIntent = new Intent(Intent.ACTION_EDIT); editIntent.setType("image/png"); - editIntent.putExtra(Intent.EXTRA_STREAM, uri); + editIntent.setData(uri); + editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // Create a edit action for the notification the same way. PendingIntent editAction = PendingIntent.getBroadcast(context, 1, @@ -902,6 +905,7 @@ class GlobalScreenshot { Intent chooserIntent = Intent.createChooser(sharingIntent, null, chooseAction.getIntentSender()) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + ActivityOptions opts = ActivityOptions.makeBasic(); opts.setDisallowEnterPictureInPictureWhileLaunching(true); context.startActivityAsUser(chooserIntent, opts.toBundle(), UserHandle.CURRENT); -- GitLab From 03b767733c3efea4bcb0f57122a809091966d90d Mon Sep 17 00:00:00 2001 From: Bernardo Rufino Date: Tue, 30 Jan 2018 17:43:47 +0000 Subject: [PATCH 107/416] Annotation for package in FrameworkRobolectricTestRunner Makes it easier to maintain the tests. Test: m -j RunFrameworksServicesRoboTests Change-Id: I798b7c980b4e3426baa6a205d4ca16f82b42109d --- .../backup/BackupManagerConstantsTest.java | 3 +- .../backup/BackupManagerServiceTest.java | 9 +-- .../server/backup/PerformBackupTaskTest.java | 5 +- .../server/backup/TransportManagerTest.java | 4 +- .../internal/PerformInitializeTaskTest.java | 8 +-- .../backup/transport/TransportClientTest.java | 3 +- .../FrameworkRobolectricTestRunner.java | 60 ++++++++++--------- .../server/testing/SystemLoaderPackages.java | 35 +++++++++++ 8 files changed, 79 insertions(+), 48 deletions(-) create mode 100644 services/robotests/src/com/android/server/testing/SystemLoaderPackages.java diff --git a/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java index c397f23b2b5..0752537abfc 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerConstantsTest.java @@ -26,6 +26,7 @@ import android.provider.Settings; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.SystemLoaderPackages; import org.junit.Before; import org.junit.Test; @@ -36,7 +37,7 @@ import org.robolectric.annotation.Config; @RunWith(FrameworkRobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 26) -@SystemLoaderClasses({BackupManagerConstants.class}) +@SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class BackupManagerConstantsTest { private static final String PACKAGE_NAME = "some.package.name"; diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java index b60ad4b3f81..df09780ecdd 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -48,7 +48,8 @@ import com.android.server.backup.testing.TransportData; import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.FrameworkRobolectricTestRunner; -import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.SystemLoaderPackages; + import java.io.File; import java.util.HashMap; import java.util.List; @@ -74,11 +75,7 @@ import org.robolectric.shadows.ShadowSystemClock; sdk = 26, shadows = {ShadowAppBackupUtils.class, ShadowBackupPolicyEnforcer.class} ) -@SystemLoaderClasses({ - BackupManagerService.class, - TransportManager.class, - PackageManagerBackupAgent.class -}) +@SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class BackupManagerServiceTest { private static final String TAG = "BMSTest"; diff --git a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java index 1360828bc81..e103464207d 100644 --- a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java +++ b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java @@ -69,6 +69,7 @@ import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportClient; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.SystemLoaderPackages; import com.android.server.testing.shadows.FrameworkShadowPackageManager; import com.android.server.testing.shadows.ShadowBackupDataInput; import com.android.server.testing.shadows.ShadowBackupDataOutput; @@ -102,12 +103,10 @@ import java.util.stream.Stream; ShadowQueuedWork.class } ) +@SystemLoaderPackages({"com.android.server.backup"}) @SystemLoaderClasses({ - BackupManagerService.class, - PerformBackupTask.class, BackupDataOutput.class, FullBackupDataOutput.class, - TransportManager.class, BackupAgent.class, IBackupTransport.class, IBackupAgent.class, diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java index 068fe813746..44ac8039bbe 100644 --- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java +++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java @@ -51,7 +51,7 @@ import com.android.server.backup.transport.TransportClient; import com.android.server.backup.transport.TransportClientManager; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.FrameworkRobolectricTestRunner; -import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.SystemLoaderPackages; import com.android.server.testing.shadows.FrameworkShadowContextImpl; import com.android.server.testing.shadows.FrameworkShadowPackageManager; import java.util.ArrayList; @@ -75,7 +75,7 @@ import org.robolectric.shadows.ShadowPackageManager; sdk = 26, shadows = {FrameworkShadowPackageManager.class, FrameworkShadowContextImpl.class} ) -@SystemLoaderClasses({TransportManager.class}) +@SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class TransportManagerTest { private static final String PACKAGE_A = "some.package.a"; diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java index 55fb46068ee..5810c30acbf 100644 --- a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java +++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java @@ -49,7 +49,7 @@ import com.android.server.backup.testing.TransportData; import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportClient; import com.android.server.testing.FrameworkRobolectricTestRunner; -import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.SystemLoaderPackages; import org.junit.Before; import org.junit.Test; @@ -67,11 +67,7 @@ import java.util.stream.Stream; @RunWith(FrameworkRobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 26) -@SystemLoaderClasses({ - BackupManagerService.class, - PerformInitializeTaskTest.class, - TransportManager.class -}) +@SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class PerformInitializeTaskTest { @Mock private BackupManagerService mBackupManagerService; diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java index db6e62f87a3..ff1644cb75a 100644 --- a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java +++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java @@ -46,6 +46,7 @@ import com.android.server.EventLogTags; import com.android.server.backup.TransportManager; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.SystemLoaderPackages; import com.android.server.testing.shadows.ShadowCloseGuard; import com.android.server.testing.shadows.ShadowEventLog; import com.android.server.testing.shadows.ShadowSlog; @@ -66,7 +67,7 @@ import org.robolectric.shadows.ShadowLooper; sdk = 26, shadows = {ShadowEventLog.class, ShadowCloseGuard.class, ShadowSlog.class} ) -@SystemLoaderClasses({TransportManager.class, TransportClient.class}) +@SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class TransportClientTest { private static final String PACKAGE_NAME = "some.package.name"; diff --git a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java index c94d5983d2f..d2a4d06dfbd 100644 --- a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java +++ b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java @@ -16,8 +16,7 @@ package com.android.server.testing; -import com.android.server.backup.PerformBackupTaskTest; -import com.android.server.backup.internal.PerformBackupTask; +import static java.util.Arrays.asList; import com.google.common.collect.ImmutableSet; @@ -33,10 +32,11 @@ import org.robolectric.util.Util; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; +import java.util.Arrays; import java.util.Enumeration; +import java.util.HashSet; import java.util.Set; +import java.util.stream.Stream; import javax.annotation.Nonnull; @@ -51,9 +51,9 @@ import javax.annotation.Nonnull; * against the actual classes that are in the tree, not a past version of them. Ideally we would * have a locally built jar referenced by Robolectric, but until that happens one can use this * class. - * This class reads the {@link SystemLoaderClasses} annotation on test classes and for each class - * in that annotation value it will bypass the android jar and load it from the system class loader. - * Allowing the test to test the actual class in the tree. + * This class reads the {@link SystemLoaderClasses} or {@link SystemLoaderPackages} annotations on + * test classes, for classes that match the annotations it will bypass the android jar and load it + * from the system class loader. Allowing the test to test the actual class in the tree. * * Implementation note: One could think about overriding * {@link RobolectricTestRunner#createClassLoaderConfig(FrameworkMethod)} method and putting the @@ -72,11 +72,21 @@ public class FrameworkRobolectricTestRunner extends RobolectricTestRunner { public FrameworkRobolectricTestRunner(Class testClass) throws InitializationError { super(testClass); - SystemLoaderClasses annotation = testClass.getAnnotation(SystemLoaderClasses.class); - Class[] systemLoaderClasses = - (annotation != null) ? annotation.value() : new Class[0]; - Set systemLoaderClassNames = classesToClassNames(systemLoaderClasses); - mSandboxFactory = new FrameworkSandboxFactory(systemLoaderClassNames); + Set classPrefixes = getSystemLoaderClassPrefixes(testClass); + mSandboxFactory = new FrameworkSandboxFactory(classPrefixes); + } + + private Set getSystemLoaderClassPrefixes(Class testClass) { + Set classPrefixes = new HashSet<>(); + SystemLoaderClasses byClass = testClass.getAnnotation(SystemLoaderClasses.class); + if (byClass != null) { + Stream.of(byClass.value()).map(Class::getName).forEach(classPrefixes::add); + } + SystemLoaderPackages byPackage = testClass.getAnnotation(SystemLoaderPackages.class); + if (byPackage != null) { + classPrefixes.addAll(asList(byPackage.value())); + } + return classPrefixes; } @Nonnull @@ -92,15 +102,15 @@ public class FrameworkRobolectricTestRunner extends RobolectricTestRunner { } private static class FrameworkClassLoader extends SandboxClassLoader { - private final Set mSystemLoaderClasses; + private final Set mSystemLoaderClassPrefixes; private FrameworkClassLoader( - Set systemLoaderClasses, + Set systemLoaderClassPrefixes, ClassLoader systemClassLoader, InstrumentationConfiguration instrumentationConfig, URL... urls) { super(systemClassLoader, instrumentationConfig, urls); - mSystemLoaderClasses = systemLoaderClasses; + mSystemLoaderClassPrefixes = systemLoaderClassPrefixes; } @Override @@ -146,8 +156,8 @@ public class FrameworkRobolectricTestRunner extends RobolectricTestRunner { * loader, so we test if the classes in the annotation are prefixes of the class to load. */ private boolean shouldLoadFromSystemLoader(String className) { - for (String classNamePrefix : mSystemLoaderClasses) { - if (className.startsWith(classNamePrefix)) { + for (String classPrefix : mSystemLoaderClassPrefixes) { + if (className.startsWith(classPrefix)) { return true; } } @@ -156,10 +166,10 @@ public class FrameworkRobolectricTestRunner extends RobolectricTestRunner { } private static class FrameworkSandboxFactory extends SandboxFactory { - private final Set mSystemLoaderClasses; + private final Set mSystemLoaderClassPrefixes; - private FrameworkSandboxFactory(Set systemLoaderClasses) { - mSystemLoaderClasses = systemLoaderClasses; + private FrameworkSandboxFactory(Set systemLoaderClassPrefixes) { + mSystemLoaderClassPrefixes = systemLoaderClassPrefixes; } @Nonnull @@ -167,18 +177,10 @@ public class FrameworkRobolectricTestRunner extends RobolectricTestRunner { public ClassLoader createClassLoader( InstrumentationConfiguration instrumentationConfig, URL... urls) { return new FrameworkClassLoader( - mSystemLoaderClasses, + mSystemLoaderClassPrefixes, ClassLoader.getSystemClassLoader(), instrumentationConfig, urls); } } - - private static Set classesToClassNames(Class[] classes) { - ImmutableSet.Builder builder = ImmutableSet.builder(); - for (Class classObject : classes) { - builder.add(classObject.getName()); - } - return builder.build(); - } } diff --git a/services/robotests/src/com/android/server/testing/SystemLoaderPackages.java b/services/robotests/src/com/android/server/testing/SystemLoaderPackages.java new file mode 100644 index 00000000000..e01c0a4cffa --- /dev/null +++ b/services/robotests/src/com/android/server/testing/SystemLoaderPackages.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.testing; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to be used in test classes that run with {@link FrameworkRobolectricTestRunner}. + * This will make the classes under the specified packages be loaded from the system class loader, + * NOT from the Robolectric android jar. + * + * @see FrameworkRobolectricTestRunner + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface SystemLoaderPackages { + String[] value() default {}; +} -- GitLab From 561d3f4d09b8f810f59d0e5838dee9d50ec7fde4 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Fri, 26 Jan 2018 11:31:02 -0500 Subject: [PATCH 108/416] Add content description to volume button Change-Id: I910139816a5d57acc72cf803d421b00c21564e38 Fixes: 72458451 Test: runtest systemui --- .../SystemUI/res/layout/volume_dialog_row.xml | 1 + packages/SystemUI/res/values/strings.xml | 3 + .../policy/AccessibilityManagerWrapper.java | 27 +++++ .../volume/VolumeDialogControllerImpl.java | 2 +- .../systemui/volume/VolumeDialogImpl.java | 16 ++- .../systemui/volume/VolumeDialogImplTest.java | 114 ++++++++++++++++++ 6 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index 1d1f95b4465..3e80085225e 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -61,6 +61,7 @@ android:layout_width="24dp" android:layout_height="24dp" android:background="?android:selectableItemBackgroundBorderless" + android:contentDescription="@string/accessibility_output_chooser" style="@style/VolumeButtons" android:layout_centerVertical="true" android:src="@drawable/ic_swap" diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9b6af43a192..72615ce690e 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1272,6 +1272,9 @@ Collapse + + Switch output device + Screen is pinned diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java index 6a573f593a9..d85e18c1725 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityManagerWrapper.java @@ -14,10 +14,14 @@ package com.android.systemui.statusbar.policy; +import android.accessibilityservice.AccessibilityServiceInfo; import android.content.Context; +import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; +import java.util.List; + /** * For mocking because AccessibilityManager is final for some reason... */ @@ -39,4 +43,27 @@ public class AccessibilityManagerWrapper implements public void removeCallback(AccessibilityServicesStateChangeListener listener) { mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener); } + + public void addAccessibilityStateChangeListener( + AccessibilityManager.AccessibilityStateChangeListener listener) { + mAccessibilityManager.addAccessibilityStateChangeListener(listener); + } + + public void removeAccessibilityStateChangeListener( + AccessibilityManager.AccessibilityStateChangeListener listener) { + mAccessibilityManager.removeAccessibilityStateChangeListener(listener); + } + + public boolean isEnabled() { + return mAccessibilityManager.isEnabled(); + } + + public void sendAccessibilityEvent(AccessibilityEvent event) { + mAccessibilityManager.sendAccessibilityEvent(event); + } + + public List getEnabledAccessibilityServiceList( + int feedbackTypeFlags) { + return mAccessibilityManager.getEnabledAccessibilityServiceList(feedbackTypeFlags); + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index a166db51956..b031bdd71e7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -76,7 +76,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa private static final int DYNAMIC_STREAM_START_INDEX = 100; private static final int VIBRATE_HINT_DURATION = 50; - private static final ArrayMap STREAMS = new ArrayMap<>(); + static final ArrayMap STREAMS = new ArrayMap<>(); static { STREAMS.put(AudioSystem.STREAM_ALARM, R.string.stream_alarm); STREAMS.put(AudioSystem.STREAM_BLUETOOTH_SCO, R.string.stream_bluetooth_sco); diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 8cfdeeb67d9..001a582297a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -63,7 +63,6 @@ import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; import android.view.animation.DecelerateInterpolator; import android.widget.ImageButton; @@ -79,6 +78,7 @@ import com.android.systemui.plugins.VolumeDialog; import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.plugins.VolumeDialogController.StreamState; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import java.io.PrintWriter; import java.util.ArrayList; @@ -111,7 +111,7 @@ public class VolumeDialogImpl implements VolumeDialog { private ConfigurableTexts mConfigurableTexts; private final SparseBooleanArray mDynamic = new SparseBooleanArray(); private final KeyguardManager mKeyguard; - private final AccessibilityManager mAccessibilityMgr; + private final AccessibilityManagerWrapper mAccessibilityMgr; private final Object mSafetyWarningLock = new Object(); private final Object mOutputChooserLock = new Object(); private final Accessibility mAccessibility = new Accessibility(); @@ -134,8 +134,7 @@ public class VolumeDialogImpl implements VolumeDialog { mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); mController = Dependency.get(VolumeDialogController.class); mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - mAccessibilityMgr = - (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); + mAccessibilityMgr = Dependency.get(AccessibilityManagerWrapper.class); mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext)); mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive); } @@ -231,6 +230,10 @@ public class VolumeDialogImpl implements VolumeDialog { initRingerH(); } + protected ViewGroup getDialogView() { + return mDialogView; + } + private ColorStateList loadColorStateList(int colorResId) { return ColorStateList.valueOf(mContext.getColor(colorResId)); } @@ -258,6 +261,7 @@ public class VolumeDialogImpl implements VolumeDialog { private void addRow(int stream, int iconRes, int iconMuteRes, boolean important, boolean defaultStream, boolean dynamic) { + if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream); VolumeRow row = new VolumeRow(); initRow(row, stream, iconRes, iconMuteRes, important, defaultStream); int rowSize; @@ -621,7 +625,7 @@ public class VolumeDialogImpl implements VolumeDialog { } } - private void onStateChangedH(State state) { + protected void onStateChangedH(State state) { mState = state; mDynamic.clear(); // add any new dynamic rows @@ -894,7 +898,7 @@ public class VolumeDialogImpl implements VolumeDialog { return ss.remoteLabel; } try { - return mContext.getString(ss.name); + return mContext.getResources().getString(ss.name); } catch (Resources.NotFoundException e) { Slog.e(TAG, "Can't find translation for stream " + ss); return ""; diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java new file mode 100644 index 00000000000..2d28c9f214f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume; + +import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; +import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; +import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; + +import static junit.framework.Assert.assertTrue; + +import android.app.KeyguardManager; +import android.media.AudioManager; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.text.TextUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.Predicate; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class VolumeDialogImplTest extends SysuiTestCase { + + VolumeDialogImpl mDialog; + + @Mock + VolumeDialogController mController; + + @Mock + KeyguardManager mKeyguard; + + @Mock + AccessibilityManagerWrapper mAccessibilityMgr; + + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + + mController = mDependency.injectMockDependency(VolumeDialogController.class); + mAccessibilityMgr = mDependency.injectMockDependency(AccessibilityManagerWrapper.class); + getContext().addMockSystemService(KeyguardManager.class, mKeyguard); + + mDialog = new VolumeDialogImpl(getContext()); + mDialog.init(0, null); + VolumeDialogController.State state = new VolumeDialogController.State(); + for (int i = AudioManager.STREAM_VOICE_CALL; i <= AudioManager.STREAM_ACCESSIBILITY; i++) { + VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState(); + ss.name = STREAMS.get(i); + state.states.append(i, ss); + } + mDialog.onStateChangedH(state); + } + + private void navigateViews(View view, Predicate condition) { + if (view instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) view; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + navigateViews(viewGroup.getChildAt(i), condition); + } + } else { + String resourceName = null; + try { + resourceName = getContext().getResources().getResourceName(view.getId()); + } catch (Exception e) {} + assertTrue("View " + resourceName != null ? resourceName : view.getId() + + " failed test", condition.test(view)); + } + } + + @Test + public void testContentDescriptions() { + mDialog.show(SHOW_REASON_UNKNOWN); + ViewGroup dialog = mDialog.getDialogView(); + + navigateViews(dialog, view -> { + if (view instanceof ImageView) { + return !TextUtils.isEmpty(view.getContentDescription()); + } else { + return true; + } + }); + + mDialog.dismiss(DISMISS_REASON_UNKNOWN); + } + +} -- GitLab From 60747d28230c5a78e30fc8836946a8a8806ab738 Mon Sep 17 00:00:00 2001 From: Jeff Vander Stoep Date: Mon, 29 Jan 2018 12:12:10 -0800 Subject: [PATCH 109/416] pm: Apps with shared UID must also share selinux domain There are two existing cases where apps that share a sharedUserId potentially end up in separate SELinux domains. 1. An app installed in /system/priv-app runs in the priv_app domain. An app installed on the /data partition which shares a sharedUserId with that priv_app would run in the untrusted_app domain (e.g. GTS b/72235911). This issue has existed since Android N. 2. The untrusted_app domain may now deprecate permissions based on targetSdkVersion, so apps with sharedUserId may have different permissions based on which targetSdkVersion they use. This issue has existed since Android O, but is particularly problematic for feature "Deprecate world accessible app data" which puts every app targeting P+ into its own selinux domain. This change fixes #1 and adds a temporary workaround to prevent #2. Test: cts-tradefed run cts -m CtsSelinuxTargetSdkCurrentTestCases Test: cts-tradefed run cts -m CtsSelinuxTargetSdk27TestCases Test: cts-tradefed run cts -m CtsSelinuxTargetSdk25TestCases Bug: 72290969 Change-Id: I211de05ad6f10b69e3e082cfe977f2dd43d90549 --- .../server/pm/PackageManagerService.java | 17 ++++++++++++++--- .../java/com/android/server/pm/SELinuxMMAC.java | 8 +++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index fdb157e7e92..6402b5d2e4b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -9988,8 +9988,7 @@ Slog.e("TODD", // priv-apps. synchronized (mPackages) { PackageSetting platformPkgSetting = mSettings.mPackages.get("android"); - if (!pkg.packageName.equals("android") - && (compareSignatures(platformPkgSetting.signatures.mSigningDetails.signatures, + if ((compareSignatures(platformPkgSetting.signatures.mSigningDetails.signatures, pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH)) { scanFlags |= SCAN_AS_PRIVILEGED; } @@ -10440,7 +10439,19 @@ Slog.e("TODD", pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; } - SELinuxMMAC.assignSeInfoValue(pkg); + // SELinux sandboxes become more restrictive as targetSdkVersion increases. + // To ensure that apps with sharedUserId are placed in the same selinux domain + // without breaking any assumptions about access, put them into the least + // restrictive targetSdkVersion=25 domain. + // TODO(b/72290969): Base this on the actual targetSdkVersion(s) of the apps within the + // sharedUserSetting, instead of defaulting to the least restrictive domain. + final int targetSdk = (sharedUserSetting != null) ? 25 + : pkg.applicationInfo.targetSdkVersion; + // TODO(b/71593002): isPrivileged for sharedUser and appInfo should never be out of sync. + // They currently can be if the sharedUser apps are signed with the platform key. + final boolean isPrivileged = (sharedUserSetting != null) ? sharedUserSetting.isPrivileged() + : pkg.applicationInfo.isPrivilegedApp(); + SELinuxMMAC.assignSeInfoValue(pkg, isPrivileged, targetSdk); pkg.mExtras = pkgSetting; pkg.applicationInfo.processName = fixProcessName( diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index 2552643a6a2..805734bcd9d 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -287,7 +287,8 @@ public final class SELinuxMMAC { * * @param pkg object representing the package to be labeled. */ - public static void assignSeInfoValue(PackageParser.Package pkg) { + public static void assignSeInfoValue(PackageParser.Package pkg, boolean isPrivileged, + int targetSdkVersion) { synchronized (sPolicies) { if (!sPolicyRead) { if (DEBUG_POLICY) { @@ -307,10 +308,11 @@ public final class SELinuxMMAC { if (pkg.applicationInfo.targetSandboxVersion == 2) pkg.applicationInfo.seInfo += SANDBOX_V2_STR; - if (pkg.applicationInfo.isPrivilegedApp()) + if (isPrivileged) { pkg.applicationInfo.seInfo += PRIVILEGED_APP_STR; + } - pkg.applicationInfo.seInfo += TARGETSDKVERSION_STR + pkg.applicationInfo.targetSdkVersion; + pkg.applicationInfo.seInfo += TARGETSDKVERSION_STR + targetSdkVersion; if (DEBUG_POLICY_INSTALL) { Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " + -- GitLab From 739811a5c53e435b3f0cfd5abf58389416be429c Mon Sep 17 00:00:00 2001 From: Steven Moreland Date: Tue, 30 Jan 2018 10:11:40 -0800 Subject: [PATCH 110/416] Update HIDL-related documentation. This is a followup CL to previous @SystemApi CLs. Bug: N/A Test: N/A Change-Id: I9c7dcc776dcfb89fd90afa4fc5d74e40ff0a5f94 --- core/java/android/os/HwBinder.java | 9 +++++++++ core/java/android/os/IHwBinder.java | 13 +++++++++++++ core/java/android/os/IHwInterface.java | 2 +- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java index 2a088a659b0..cdee1101c27 100644 --- a/core/java/android/os/HwBinder.java +++ b/core/java/android/os/HwBinder.java @@ -87,6 +87,9 @@ public abstract class HwBinder implements IHwBinder { * Configures how many threads the process-wide hwbinder threadpool * has to process incoming requests. * + * @param maxThreads total number of threads to create (includes this thread if + * callerWillJoin is true) + * @param callerWillJoin whether joinRpcThreadpool will be called in advance * @hide */ @SystemApi @@ -125,6 +128,12 @@ public abstract class HwBinder implements IHwBinder { /** * Enable instrumentation if available. + * + * On a non-user build, this method: + * - tries to enable atracing (if enabled) + * - tries to enable coverage dumps (if running in VTS) + * - tries to enable record and replay (if running in VTS) + * * @hide */ @SystemApi diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java index 0c592e1f04b..a565dee5ddd 100644 --- a/core/java/android/os/IHwBinder.java +++ b/core/java/android/os/IHwBinder.java @@ -30,6 +30,11 @@ public interface IHwBinder { /** * Process a hwbinder transaction. * + * @param code interface specific code for interface. + * @param request parceled transaction + * @param reply object to parcel reply into + * @param flags transaction flags to be chosen by wire protocol + * * @hide */ @SystemApi @@ -39,6 +44,7 @@ public interface IHwBinder { /** * Return as IHwInterface instance only if this implements descriptor. + * * @param descriptor for example foo.bar@1.0::IBaz * @hide */ @@ -53,6 +59,8 @@ public interface IHwBinder { public interface DeathRecipient { /** * Callback for a registered process dying. + * + * @param cookie cookie this death recipient was registered with. */ @SystemApi public void serviceDied(long cookie); @@ -61,11 +69,16 @@ public interface IHwBinder { /** * Notifies the death recipient with the cookie when the process containing * this binder dies. + * + * @param recipient callback object to be called on object death. + * @param cookie value to be given to callback on object death. */ @SystemApi public boolean linkToDeath(DeathRecipient recipient, long cookie); /** * Unregisters the death recipient from this binder. + * + * @param recipient callback to no longer recieve death notifications on this binder. */ @SystemApi public boolean unlinkToDeath(DeathRecipient recipient); diff --git a/core/java/android/os/IHwInterface.java b/core/java/android/os/IHwInterface.java index a2f59a9abb8..1d9e2b0197c 100644 --- a/core/java/android/os/IHwInterface.java +++ b/core/java/android/os/IHwInterface.java @@ -21,7 +21,7 @@ import android.annotation.SystemApi; @SystemApi public interface IHwInterface { /** - * Returns the binder object that corresponds to an interface. + * @return the binder object that corresponds to this interface. */ @SystemApi public IHwBinder asBinder(); -- GitLab From d679a767b49ea4bd4aee83a4bc2425fdce67b950 Mon Sep 17 00:00:00 2001 From: Siddharth Ray Date: Sat, 20 Jan 2018 18:57:58 -0800 Subject: [PATCH 111/416] GPS power metrics Power metrics is added to GPS metrics BUG:72383800 Test: Manual Change-Id: I6b01c04984b750c6e079e26b2ad4730d647be382 --- .../location/gnssmetrics/GnssMetrics.java | 55 ++++++++++++++++++- proto/src/gnss.proto | 18 +++++- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java index 603926f4fe4..98e67c21c1a 100644 --- a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java +++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java @@ -17,7 +17,9 @@ package com.android.internal.location.gnssmetrics; import android.os.SystemClock; +import android.os.connectivity.GpsBatteryStats; +import android.text.format.DateUtils; import android.util.Base64; import android.util.Log; import android.util.TimeUtils; @@ -26,6 +28,7 @@ import java.util.Arrays; import com.android.internal.app.IBatteryStats; import com.android.internal.location.nano.GnssLogsProto.GnssLog; +import com.android.internal.location.nano.GnssLogsProto.PowerMetrics; /** * GnssMetrics: Is used for logging GNSS metrics @@ -171,6 +174,7 @@ public class GnssMetrics { msg.standardDeviationTopFourAverageCn0DbHz = topFourAverageCn0Statistics.getStandardDeviation(); } + msg.powerMetrics = mGnssPowerMetrics.buildProto(); String s = Base64.encodeToString(GnssLog.toByteArray(msg), Base64.DEFAULT); reset(); return s; @@ -218,6 +222,21 @@ public class GnssMetrics { topFourAverageCn0Statistics.getStandardDeviation()).append("\n"); } s.append("GNSS_KPI_END").append("\n"); + GpsBatteryStats stats = mGnssPowerMetrics.getGpsBatteryStats(); + if (stats != null) { + s.append("Power Metrics").append('\n'); + long[] t = stats.getTimeInGpsSignalQualityLevel(); + if (t != null && t.length == NUM_GPS_SIGNAL_QUALITY_LEVELS) { + s.append(" Amount of time (while on battery) Top 4 Avg CN0 > " + + Double.toString(GnssPowerMetrics.POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ) + + " dB-Hz (min): ").append(t[1] / ((double) DateUtils.MINUTE_IN_MILLIS)).append("\n"); + s.append(" Amount of time (while on battery) Top 4 Avg CN0 <= " + + Double.toString(GnssPowerMetrics.POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ) + + " dB-Hz (min): ").append(t[0] / ((double) DateUtils.MINUTE_IN_MILLIS)).append("\n"); + } + s.append(" Energy consumed while on battery (mAh): ").append( + stats.getEnergyConsumedMaMs() / ((double) DateUtils.HOUR_IN_MILLIS)).append("\n"); + } return s.toString(); } @@ -294,7 +313,7 @@ public class GnssMetrics { private class GnssPowerMetrics { /* Threshold for Top Four Average CN0 below which GNSS signal quality is declared poor */ - private static final double POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ = 20.0; + public static final double POOR_TOP_FOUR_AVG_CN0_THRESHOLD_DB_HZ = 20.0; /* Minimum change in Top Four Average CN0 needed to trigger a report */ private static final double REPORTING_THRESHOLD_DB_HZ = 1.0; @@ -312,6 +331,38 @@ public class GnssMetrics { mLastAverageCn0 = -100.0; } + /** + * Builds power metrics proto buf. This is included in the gnss proto buf. + * @return PowerMetrics + */ + public PowerMetrics buildProto() { + PowerMetrics p = new PowerMetrics(); + GpsBatteryStats stats = mGnssPowerMetrics.getGpsBatteryStats(); + if (stats != null) { + p.loggingDurationMs = stats.getLoggingDurationMs(); + p.energyConsumedMah = stats.getEnergyConsumedMaMs() / ((double) DateUtils.HOUR_IN_MILLIS); + long[] t = stats.getTimeInGpsSignalQualityLevel(); + p.timeInSignalQualityLevelMs = new long[t.length]; + for (int i = 0; i < t.length; i++) { + p.timeInSignalQualityLevelMs[i] = t[i]; + } + } + return p; + } + + /** + * Returns the GPS power stats + * @return GpsBatteryStats + */ + public GpsBatteryStats getGpsBatteryStats() { + try { + return mBatteryStats.getGpsBatteryStats(); + } catch (Exception e) { + Log.w(TAG, "Exception", e); + return null; + } + } + /** * Reports signal quality to BatteryStats. Signal quality is based on Top four average CN0. If * the number of SVs seen is less than 4, then signal quality is the average CN0. @@ -347,4 +398,4 @@ public class GnssMetrics { return GnssMetrics.GPS_SIGNAL_QUALITY_POOR; } } -} \ No newline at end of file +} diff --git a/proto/src/gnss.proto b/proto/src/gnss.proto index c54ddadd07d..01683923225 100644 --- a/proto/src/gnss.proto +++ b/proto/src/gnss.proto @@ -42,4 +42,20 @@ message GnssLog { // Standard deviation of top 4 average CN0 (dB-Hz) optional double standard_deviation_top_four_average_cn0_db_hz = 11; -} \ No newline at end of file + + // Power metrics + optional PowerMetrics power_metrics = 12; +} + +// Power metrics +message PowerMetrics { + + // Duration of power log (ms) + optional int64 logging_duration_ms = 1; + + // Energy consumed (mAh) + optional double energy_consumed_mah = 2; + + // Time spent in signal quality level (ms) + repeated int64 time_in_signal_quality_level_ms = 3; +} -- GitLab From 53d0661f38d6ada39aefaac5ce016f802e74bd44 Mon Sep 17 00:00:00 2001 From: Kenny Guy Date: Tue, 30 Jan 2018 14:19:13 +0000 Subject: [PATCH 112/416] Add information about brightness config to slider events. Whether the config is using the default curve. Whether the config has a user data point. Whether the config has a power save offset. Bug: 72482479 Test: atest BrightnessTrackerTest Change-Id: I137a919ae2604244b0b2f78e00e31b7a72df4967 --- api/system-current.txt | 3 + api/test-current.txt | 3 + .../display/BrightnessChangeEvent.java | 51 ++++++++++++++- .../AutomaticBrightnessController.java | 8 +++ .../display/BrightnessMappingStrategy.java | 26 ++++++++ .../server/display/BrightnessTracker.java | 65 +++++++++++++++++-- .../display/DisplayPowerController.java | 18 +++-- .../server/display/BrightnessTrackerTest.java | 33 +++++++--- 8 files changed, 185 insertions(+), 22 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index 396ff1a9aa3..53798abf931 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1135,11 +1135,14 @@ package android.hardware.display { field public final float batteryLevel; field public final float brightness; field public final int colorTemperature; + field public final boolean isDefaultBrightnessConfig; + field public final boolean isUserSetBrightness; field public final float lastBrightness; field public final long[] luxTimestamps; field public final float[] luxValues; field public final boolean nightMode; field public final java.lang.String packageName; + field public final float powerBrightnessFactor; field public final long timeStamp; } diff --git a/api/test-current.txt b/api/test-current.txt index 4e8f904b96b..82ccd37bd3e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -305,11 +305,14 @@ package android.hardware.display { field public final float batteryLevel; field public final float brightness; field public final int colorTemperature; + field public final boolean isDefaultBrightnessConfig; + field public final boolean isUserSetBrightness; field public final float lastBrightness; field public final long[] luxTimestamps; field public final float[] luxValues; field public final boolean nightMode; field public final java.lang.String packageName; + field public final float powerBrightnessFactor; field public final long timeStamp; } diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java index 2301824cb9d..02eb28ceb4b 100644 --- a/core/java/android/hardware/display/BrightnessChangeEvent.java +++ b/core/java/android/hardware/display/BrightnessChangeEvent.java @@ -54,19 +54,30 @@ public final class BrightnessChangeEvent implements Parcelable { /** Most recent battery level when brightness was changed or Float.NaN */ public final float batteryLevel; + /** Factor applied to brightness due to battery level, 0.0-1.0 */ + public final float powerBrightnessFactor; + /** Color filter active to provide night mode */ public final boolean nightMode; /** If night mode color filter is active this will be the temperature in kelvin */ public final int colorTemperature; - /** Brightness le vel before slider adjustment */ + /** Brightness level before slider adjustment */ public final float lastBrightness; + /** Whether brightness configuration is default version */ + public final boolean isDefaultBrightnessConfig; + + /** Whether brightness curve includes a user brightness point */ + public final boolean isUserSetBrightness; + + /** @hide */ private BrightnessChangeEvent(float brightness, long timeStamp, String packageName, int userId, float[] luxValues, long[] luxTimestamps, float batteryLevel, - boolean nightMode, int colorTemperature, float lastBrightness) { + float powerBrightnessFactor, boolean nightMode, int colorTemperature, + float lastBrightness, boolean isDefaultBrightnessConfig, boolean isUserSetBrightness) { this.brightness = brightness; this.timeStamp = timeStamp; this.packageName = packageName; @@ -74,9 +85,12 @@ public final class BrightnessChangeEvent implements Parcelable { this.luxValues = luxValues; this.luxTimestamps = luxTimestamps; this.batteryLevel = batteryLevel; + this.powerBrightnessFactor = powerBrightnessFactor; this.nightMode = nightMode; this.colorTemperature = colorTemperature; this.lastBrightness = lastBrightness; + this.isDefaultBrightnessConfig = isDefaultBrightnessConfig; + this.isUserSetBrightness = isUserSetBrightness; } /** @hide */ @@ -88,9 +102,12 @@ public final class BrightnessChangeEvent implements Parcelable { this.luxValues = other.luxValues; this.luxTimestamps = other.luxTimestamps; this.batteryLevel = other.batteryLevel; + this.powerBrightnessFactor = other.powerBrightnessFactor; this.nightMode = other.nightMode; this.colorTemperature = other.colorTemperature; this.lastBrightness = other.lastBrightness; + this.isDefaultBrightnessConfig = other.isDefaultBrightnessConfig; + this.isUserSetBrightness = other.isUserSetBrightness; } private BrightnessChangeEvent(Parcel source) { @@ -101,9 +118,12 @@ public final class BrightnessChangeEvent implements Parcelable { luxValues = source.createFloatArray(); luxTimestamps = source.createLongArray(); batteryLevel = source.readFloat(); + powerBrightnessFactor = source.readFloat(); nightMode = source.readBoolean(); colorTemperature = source.readInt(); lastBrightness = source.readFloat(); + isDefaultBrightnessConfig = source.readBoolean(); + isUserSetBrightness = source.readBoolean(); } public static final Creator CREATOR = @@ -130,9 +150,12 @@ public final class BrightnessChangeEvent implements Parcelable { dest.writeFloatArray(luxValues); dest.writeLongArray(luxTimestamps); dest.writeFloat(batteryLevel); + dest.writeFloat(powerBrightnessFactor); dest.writeBoolean(nightMode); dest.writeInt(colorTemperature); dest.writeFloat(lastBrightness); + dest.writeBoolean(isDefaultBrightnessConfig); + dest.writeBoolean(isUserSetBrightness); } /** @hide */ @@ -144,9 +167,12 @@ public final class BrightnessChangeEvent implements Parcelable { private float[] mLuxValues; private long[] mLuxTimestamps; private float mBatteryLevel; + private float mPowerBrightnessFactor; private boolean mNightMode; private int mColorTemperature; private float mLastBrightness; + private boolean mIsDefaultBrightnessConfig; + private boolean mIsUserSetBrightness; /** {@see BrightnessChangeEvent#brightness} */ public Builder setBrightness(float brightness) { @@ -190,6 +216,12 @@ public final class BrightnessChangeEvent implements Parcelable { return this; } + /** {@see BrightnessChangeEvent#powerSaveBrightness} */ + public Builder setPowerBrightnessFactor(float powerBrightnessFactor) { + mPowerBrightnessFactor = powerBrightnessFactor; + return this; + } + /** {@see BrightnessChangeEvent#nightMode} */ public Builder setNightMode(boolean nightMode) { mNightMode = nightMode; @@ -208,11 +240,24 @@ public final class BrightnessChangeEvent implements Parcelable { return this; } + /** {@see BrightnessChangeEvent#isDefaultBrightnessConfig} */ + public Builder setIsDefaultBrightnessConfig(boolean isDefaultBrightnessConfig) { + mIsDefaultBrightnessConfig = isDefaultBrightnessConfig; + return this; + } + + /** {@see BrightnessChangeEvent#userBrightnessPoint} */ + public Builder setUserBrightnessPoint(boolean isUserSetBrightness) { + mIsUserSetBrightness = isUserSetBrightness; + return this; + } + /** Builds a BrightnessChangeEvent */ public BrightnessChangeEvent build() { return new BrightnessChangeEvent(mBrightness, mTimeStamp, mPackageName, mUserId, mLuxValues, mLuxTimestamps, mBatteryLevel, - mNightMode, mColorTemperature, mLastBrightness); + mPowerBrightnessFactor, mNightMode, mColorTemperature, mLastBrightness, + mIsDefaultBrightnessConfig, mIsUserSetBrightness); } } } diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 6a88b1e1735..e2aee88d331 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -269,6 +269,14 @@ class AutomaticBrightnessController { } } + public boolean hasUserDataPoints() { + return mBrightnessMapper.hasUserDataPoints(); + } + + public boolean isDefaultConfig() { + return mBrightnessMapper.isDefaultConfig(); + } + private boolean setDisplayPolicy(int policy) { if (mDisplayPolicy == policy) { return false; diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index 001d4d903f3..c0d259992a4 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -200,6 +200,12 @@ public abstract class BrightnessMappingStrategy { */ public abstract void clearUserDataPoints(); + /** @return true if there are any short term adjustments applied to the curve */ + public abstract boolean hasUserDataPoints(); + + /** @return true if the current brightness config is the default one */ + public abstract boolean isDefaultConfig(); + public abstract void dump(PrintWriter pw); private static float normalizeAbsoluteBrightness(int brightness) { @@ -389,6 +395,16 @@ public abstract class BrightnessMappingStrategy { } } + @Override + public boolean hasUserDataPoints() { + return mUserLux != -1; + } + + @Override + public boolean isDefaultConfig() { + return true; + } + @Override public void dump(PrintWriter pw) { pw.println("SimpleMappingStrategy"); @@ -506,6 +522,16 @@ public abstract class BrightnessMappingStrategy { } } + @Override + public boolean hasUserDataPoints() { + return mUserLux != -1; + } + + @Override + public boolean isDefaultConfig() { + return mDefaultConfig.equals(mConfig); + } + @Override public void dump(PrintWriter pw) { pw.println("PhysicalMappingStrategy"); diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index bcf8bfe6aaa..e1f9a88cc03 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -99,6 +99,9 @@ public class BrightnessTracker { private static final String ATTR_NIGHT_MODE = "nightMode"; private static final String ATTR_COLOR_TEMPERATURE = "colorTemperature"; private static final String ATTR_LAST_NITS = "lastNits"; + private static final String ATTR_DEFAULT_CONFIG = "defaultConfig"; + private static final String ATTR_POWER_SAVE = "powerSaveFactor"; + private static final String ATTR_USER_POINT = "userPoint"; private static final int MSG_BACKGROUND_START = 0; private static final int MSG_BRIGHTNESS_CHANGED = 1; @@ -235,17 +238,22 @@ public class BrightnessTracker { /** * Notify the BrightnessTracker that the user has changed the brightness of the display. */ - public void notifyBrightnessChanged(float brightness, boolean userInitiated) { + public void notifyBrightnessChanged(float brightness, boolean userInitiated, + float powerBrightnessFactor, boolean isUserSetBrightness, + boolean isDefaultBrightnessConfig) { if (DEBUG) { Slog.d(TAG, String.format("notifyBrightnessChanged(brightness=%f, userInitiated=%b)", brightness, userInitiated)); } Message m = mBgHandler.obtainMessage(MSG_BRIGHTNESS_CHANGED, - userInitiated ? 1 : 0, 0 /*unused*/, (Float) brightness); + userInitiated ? 1 : 0, 0 /*unused*/, new BrightnessChangeValues(brightness, + powerBrightnessFactor, isUserSetBrightness, isDefaultBrightnessConfig)); m.sendToTarget(); } - private void handleBrightnessChanged(float brightness, boolean userInitiated) { + private void handleBrightnessChanged(float brightness, boolean userInitiated, + float powerBrightnessFactor, boolean isUserSetBrightness, + boolean isDefaultBrightnessConfig) { BrightnessChangeEvent.Builder builder; synchronized (mDataCollectionLock) { @@ -267,6 +275,9 @@ public class BrightnessTracker { builder = new BrightnessChangeEvent.Builder(); builder.setBrightness(brightness); builder.setTimeStamp(mInjector.currentTimeMillis()); + builder.setPowerBrightnessFactor(powerBrightnessFactor); + builder.setUserBrightnessPoint(isUserSetBrightness); + builder.setIsDefaultBrightnessConfig(isDefaultBrightnessConfig); final int readingCount = mLastSensorReadings.size(); if (readingCount == 0) { @@ -412,6 +423,12 @@ public class BrightnessTracker { toWrite[i].colorTemperature)); out.attribute(null, ATTR_LAST_NITS, Float.toString(toWrite[i].lastBrightness)); + out.attribute(null, ATTR_DEFAULT_CONFIG, + Boolean.toString(toWrite[i].isDefaultBrightnessConfig)); + out.attribute(null, ATTR_POWER_SAVE, + Float.toString(toWrite[i].powerBrightnessFactor)); + out.attribute(null, ATTR_USER_POINT, + Boolean.toString(toWrite[i].isUserSetBrightness)); StringBuilder luxValues = new StringBuilder(); StringBuilder luxTimestamps = new StringBuilder(); for (int j = 0; j < toWrite[i].luxValues.length; ++j) { @@ -496,6 +513,21 @@ public class BrightnessTracker { builder.setLuxValues(luxValues); builder.setLuxTimestamps(luxTimestamps); + String defaultConfig = parser.getAttributeValue(null, ATTR_DEFAULT_CONFIG); + if (defaultConfig != null) { + builder.setIsDefaultBrightnessConfig(Boolean.parseBoolean(defaultConfig)); + } + String powerSave = parser.getAttributeValue(null, ATTR_POWER_SAVE); + if (powerSave != null) { + builder.setPowerBrightnessFactor(Float.parseFloat(powerSave)); + } else { + builder.setPowerBrightnessFactor(1.0f); + } + String userPoint = parser.getAttributeValue(null, ATTR_USER_POINT); + if (userPoint != null) { + builder.setUserBrightnessPoint(Boolean.parseBoolean(userPoint)); + } + BrightnessChangeEvent event = builder.build(); if (DEBUG) { Slog.i(TAG, "Read event " + event.brightness @@ -535,7 +567,11 @@ public class BrightnessTracker { BrightnessChangeEvent[] events = mEvents.toArray(); for (int i = 0; i < events.length; ++i) { pw.print(" " + events[i].timeStamp + ", " + events[i].userId); - pw.print(", " + events[i].lastBrightness + "->" + events[i].brightness + ", {"); + pw.print(", " + events[i].lastBrightness + "->" + events[i].brightness); + pw.print(", isUserSetBrightness=" + events[i].isUserSetBrightness); + pw.print(", powerBrightnessFactor=" + events[i].powerBrightnessFactor); + pw.print(", isDefaultBrightnessConfig=" + events[i].isDefaultBrightnessConfig); + pw.print(" {"); for (int j = 0; j < events[i].luxValues.length; ++j){ if (j != 0) { pw.print(", "); @@ -637,14 +673,31 @@ public class BrightnessTracker { backgroundStart((float)msg.obj /*initial brightness*/); break; case MSG_BRIGHTNESS_CHANGED: - float newBrightness = (float) msg.obj; + BrightnessChangeValues values = (BrightnessChangeValues) msg.obj; boolean userInitiatedChange = (msg.arg1 == 1); - handleBrightnessChanged(newBrightness, userInitiatedChange); + handleBrightnessChanged(values.brightness, userInitiatedChange, + values.powerBrightnessFactor, values.isUserSetBrightness, + values.isDefaultBrightnessConfig); break; } } } + private static class BrightnessChangeValues { + final float brightness; + final float powerBrightnessFactor; + final boolean isUserSetBrightness; + final boolean isDefaultBrightnessConfig; + + BrightnessChangeValues(float brightness, float powerBrightnessFactor, + boolean isUserSetBrightness, boolean isDefaultBrightnessConfig) { + this.brightness = brightness; + this.powerBrightnessFactor = powerBrightnessFactor; + this.isUserSetBrightness = isUserSetBrightness; + this.isDefaultBrightnessConfig = isDefaultBrightnessConfig; + } + } + @VisibleForTesting static class Injector { public void registerSensorListener(Context context, diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 80aec42b719..fa352ab2988 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -795,13 +795,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call brightness = PowerManager.BRIGHTNESS_ON; } - // If the brightness is already set then it's been overriden by something other than the + // If the brightness is already set then it's been overridden by something other than the // user, or is a temporary adjustment. final boolean userInitiatedChange = brightness < 0 && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged); + boolean hadUserBrightnessPoint = false; // Configure auto-brightness. if (mAutomaticBrightnessController != null) { + hadUserBrightnessPoint = mAutomaticBrightnessController.hasUserDataPoints(); mAutomaticBrightnessController.configure(autoBrightnessEnabled, mBrightnessConfiguration, mLastUserSetScreenBrightness / (float) PowerManager.BRIGHTNESS_ON, @@ -930,7 +932,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } if (!brightnessIsTemporary) { - notifyBrightnessChanged(brightness, userInitiatedChange); + notifyBrightnessChanged(brightness, userInitiatedChange, hadUserBrightnessPoint); } } @@ -1469,13 +1471,19 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return true; } - private void notifyBrightnessChanged(int brightness, boolean userInitiated) { + private void notifyBrightnessChanged(int brightness, boolean userInitiated, + boolean hadUserDataPoint) { final float brightnessInNits = convertToNits(brightness); - if (brightnessInNits >= 0.0f) { + if (brightnessInNits >= 0.0f && mAutomaticBrightnessController != null) { // We only want to track changes on devices that can actually map the display backlight // values into a physical brightness unit since the value provided by the API is in // nits and not using the arbitrary backlight units. - mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated); + final float powerFactor = mPowerRequest.lowPowerMode + ? mPowerRequest.screenLowPowerBrightnessFactor + : 1.0f; + mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated, + powerFactor, hadUserDataPoint, + mAutomaticBrightnessController.isDefaultConfig()); } } diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java index edc7d74b47c..da8f8b3eadd 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java @@ -30,7 +30,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.database.ContentObserver; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.display.BrightnessChangeEvent; @@ -195,7 +194,9 @@ public class BrightnessTrackerTest { mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1)); final int systemUpdatedBrightness = 20; - notifyBrightnessChanged(mTracker, systemUpdatedBrightness, false /*userInitiated*/); + notifyBrightnessChanged(mTracker, systemUpdatedBrightness, false /*userInitiated*/, + 0.5f /*powerBrightnessFactor(*/, false /*isUserSetBrightness*/, + false /*isDefaultBrightnessConfig*/); List events = mTracker.getEvents(0, true).getList(); // No events because we filtered out our change. assertEquals(0, events.size()); @@ -285,7 +286,8 @@ public class BrightnessTrackerTest { + "lastNits=\"32.333\" " + "batteryLevel=\"1.0\" nightMode=\"false\" colorTemperature=\"0\"\n" + "lux=\"32.2,31.1\" luxTimestamps=\"" - + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\"/>" + + Long.toString(someTimeAgo) + "," + Long.toString(someTimeAgo) + "\"" + + "defaultConfig=\"true\" powerSaveFactor=\"0.5\" userPoint=\"true\" />" + " mSystemIntSettings = new HashMap<>(); Map mSecureIntSettings = new HashMap<>(); long mCurrentTimeMillis = System.currentTimeMillis(); long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos(); -- GitLab From 81ad972f39a12ff0aa8351b6a43727ffb73c4685 Mon Sep 17 00:00:00 2001 From: Robin Lee Date: Fri, 19 Jan 2018 16:34:31 +0100 Subject: [PATCH 113/416] Feature flag for stopping restricted profiles Defaults to true by default. Some devices may override this to force the restricted profile to stop when not in use to save all the memory it consumes where that is an issue. There is no framework / first-party code setting it, but since this is checked every time a user switches into the profile rolling it out should be relatively straightfoward. Bug: 71626497 Test: make droid Change-Id: I7a718c4fdd2d80131df083908129b715d94e824e --- core/java/android/provider/Settings.java | 12 ++++++++++++ core/res/res/values/config.xml | 2 ++ core/res/res/values/symbols.xml | 2 ++ .../src/android/provider/SettingsBackupTest.java | 1 + 4 files changed, 17 insertions(+) diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2440b489f41..1f0d683192d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10519,6 +10519,18 @@ public final class Settings { */ public static final String NETWORK_WATCHLIST_ENABLED = "network_watchlist_enabled"; + /** + * Flag to keep background restricted profiles running after exiting. If disabled, + * the restricted profile can be put into stopped state as soon as the user leaves it. + * Type: int (0 for false, 1 for true) + * + * Overridden by the system based on device information. If null, the value specified + * by {@code config_keepRestrictedProfilesInBackground} is used. + * + * @hide + */ + public static final String KEEP_PROFILE_IN_BACKGROUND = "keep_profile_in_background"; + /** * Get the key that retrieves a bluetooth headset's priority. * @hide diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 38f890a1e95..ae0a1188912 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3269,4 +3269,6 @@ android.ext.services true + + true diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e8ab0be78b3..1ab33ba7f2a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3241,4 +3241,6 @@ + + diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 733f7a107fa..0083b017033 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -245,6 +245,7 @@ public class SettingsBackupTest { Settings.Global.INTENT_FIREWALL_UPDATE_CONTENT_URL, Settings.Global.INTENT_FIREWALL_UPDATE_METADATA_URL, Settings.Global.JOB_SCHEDULER_CONSTANTS, + Settings.Global.KEEP_PROFILE_IN_BACKGROUND, Settings.Global.LANG_ID_UPDATE_CONTENT_URL, Settings.Global.LANG_ID_UPDATE_METADATA_URL, Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS, -- GitLab From acdb686635fade1bc4bb6bafbd3e721392940f90 Mon Sep 17 00:00:00 2001 From: Tyler Gunn Date: Mon, 29 Jan 2018 14:30:52 -0800 Subject: [PATCH 114/416] Add call recording tone support. Adding carrier configuration option to specify whether the carrier requires the incall recording tone be played. Added phone account extra used in Telephony to communicate this to Telecom. Added permission pregrant for Telecom for MODIFY_AUDIO_ROUTING; this is needed as Telecom listening to the AudioRecordingConfiguration callback from the audio framework. It needs the permission so that it can be informed of the package names of recording apps. Test: Manually enabled for local carrier and confirmed that recording tone plays to remote party when a recording app is started on the device. Bug: 64138141 Change-Id: I1ab521b79cbeeb4ff4dcbf83de7c17c539637bdc --- data/etc/privapp-permissions-platform.xml | 1 + .../java/android/telecom/PhoneAccount.java | 19 +++++++++++++++++++ .../telephony/CarrierConfigManager.java | 10 ++++++++++ 3 files changed, 30 insertions(+) diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 6a3a9739295..bf48a794624 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -212,6 +212,7 @@ applications that come with the platform + diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index fcfc5931ac7..95eb14ada35 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -133,6 +133,25 @@ public final class PhoneAccount implements Parcelable { public static final String EXTRA_LOG_SELF_MANAGED_CALLS = "android.telecom.extra.LOG_SELF_MANAGED_CALLS"; + /** + * Boolean {@link PhoneAccount} extras key (see {@link PhoneAccount#getExtras()}) which + * indicates whether calls for a {@link PhoneAccount} should generate a "call recording tone" + * when the user is recording audio on the device. + *

+ * The call recording tone is played over the telephony audio stream so that the remote party + * has an audible indication that it is possible their call is being recorded using a call + * recording app on the device. + *

+ * This extra only has an effect for calls placed via Telephony (e.g. + * {@link #CAPABILITY_SIM_SUBSCRIPTION}). + *

+ * The call recording tone is a 1400 hz tone which repeats every 15 seconds while recording is + * in progress. + * @hide + */ + public static final String EXTRA_PLAY_CALL_RECORDING_TONE = + "android.telecom.extra.PLAY_CALL_RECORDING_TONE"; + /** * Flag indicating that this {@code PhoneAccount} can act as a connection manager for * other connections. The {@link ConnectionService} associated with this {@code PhoneAccount} diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 94ed2184807..f156390913f 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -145,6 +145,15 @@ public class CarrierConfigManager { */ public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool"; + /** + * Determines if the carrier requires that a tone be played to the remote party when an app is + * recording audio during a call (e.g. using a call recording app). + *

+ * Note: This requires the Telephony config_supports_telephony_audio_device overlay to be true + * in order to work. + * @hide + */ + public static final String KEY_PLAY_CALL_RECORDING_TONE_BOOL = "play_call_recording_tone_bool"; /** * Determines if the carrier requires converting the destination number before sending out an * SMS. Certain networks and numbering plans require different formats. @@ -1771,6 +1780,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_ADDITIONAL_CALL_SETTING_BOOL, true); sDefaults.putBoolean(KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL, false); sDefaults.putBoolean(KEY_ALLOW_LOCAL_DTMF_TONES_BOOL, true); + sDefaults.putBoolean(KEY_PLAY_CALL_RECORDING_TONE_BOOL, false); sDefaults.putBoolean(KEY_APN_EXPAND_BOOL, true); sDefaults.putBoolean(KEY_AUTO_RETRY_ENABLED_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_SETTINGS_ENABLE_BOOL, false); -- GitLab From 2a56f09c51954042f36246fb10bd00111a7846e1 Mon Sep 17 00:00:00 2001 From: Mikhail Naganov Date: Thu, 25 Jan 2018 10:55:22 -0800 Subject: [PATCH 115/416] Docs: Update JavaDocs for Visualizer.OnDataCaptureListener Added more explanations for onFftDataCapture method. Fixed some syntax errors. Bug: 65673222 Test: make docs Change-Id: Ia793e90061de9bf78ba2774102ceccc766c74e27 --- .../android/media/audiofx/Visualizer.java | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/media/java/android/media/audiofx/Visualizer.java b/media/java/android/media/audiofx/Visualizer.java index 0fe7246e85c..f2b4fe09324 100644 --- a/media/java/android/media/audiofx/Visualizer.java +++ b/media/java/android/media/audiofx/Visualizer.java @@ -546,22 +546,39 @@ public class Visualizer { /** * Method called when a new waveform capture is available. *

Data in the waveform buffer is valid only within the scope of the callback. - * Applications which needs access to the waveform data after returning from the callback + * Applications which need access to the waveform data after returning from the callback * should make a copy of the data instead of holding a reference. * @param visualizer Visualizer object on which the listener is registered. * @param waveform array of bytes containing the waveform representation. - * @param samplingRate sampling rate of the audio visualized. + * @param samplingRate sampling rate of the visualized audio. */ void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate); /** * Method called when a new frequency capture is available. *

Data in the fft buffer is valid only within the scope of the callback. - * Applications which needs access to the fft data after returning from the callback + * Applications which need access to the fft data after returning from the callback * should make a copy of the data instead of holding a reference. + * + *

In order to obtain magnitude and phase values the following formulas can + * be used: + *

+         *       for (int i = 0; i < fft.size(); i += 2) {
+         *           float magnitude = (float)Math.hypot(fft[i], fft[i + 1]);
+         *           float phase = (float)Math.atan2(fft[i + 1], fft[i]);
+         *       }
* @param visualizer Visualizer object on which the listener is registered. * @param fft array of bytes containing the frequency representation. - * @param samplingRate sampling rate of the audio visualized. + * The fft array only contains the first half of the actual + * FFT spectrum (frequencies up to Nyquist frequency), exploiting + * the symmetry of the spectrum. For each frequencies bin i: + *
    + *
  • the element at index 2*i in the array contains + * the real part of a complex number,
  • + *
  • the element at index 2*i+1 contains the imaginary + * part of the complex number.
  • + *
+ * @param samplingRate sampling rate of the visualized audio. */ void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate); } -- GitLab From da252ac5c17426f28b8e18a2b8942c6880e4055b Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Mon, 29 Jan 2018 09:42:47 -0800 Subject: [PATCH 116/416] Update argument of Layout methods. Bug: 65024629 Test: minikin_tests Test: bit CtsTextTestCases:* Test: bit CtsWidgetTestCases:android.widget.cts.TextViewTest Change-Id: I659bff2385db44011abbb55e287ed893d50846ec --- libs/hwui/hwui/MinikinUtils.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index 43f46ef758c..a0d000dd217 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -44,7 +44,6 @@ minikin::MinikinPaint MinikinUtils::prepareMinikinPaint(const Paint* paint, minikinPaint.familyVariant = paint->getFamilyVariant(); minikinPaint.fontStyle = resolvedFace->fStyle; minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings(); - minikinPaint.hyphenEdit = minikin::HyphenEdit(paint->getHyphenEdit()); return minikinPaint; } @@ -55,18 +54,23 @@ minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFla minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); minikin::Layout layout; + const minikin::U16StringPiece textBuf(buf, bufSize); + const minikin::Range range(start, start + count); + const minikin::HyphenEdit hyphenEdit = static_cast(paint->getHyphenEdit()); + const minikin::StartHyphenEdit startHyphen = minikin::startHyphenEdit(hyphenEdit); + const minikin::EndHyphenEdit endHyphen = minikin::endHyphenEdit(hyphenEdit); + if (mt == nullptr) { - layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint); + layout.doLayout(textBuf,range, bidiFlags, minikinPaint, startHyphen, endHyphen); return layout; } - if (mt->buildLayout(minikin::U16StringPiece(buf, bufSize), - minikin::Range(start, start + count), - minikinPaint, bidiFlags, mtOffset, &layout)) { + if (mt->buildLayout(textBuf, range, minikinPaint, bidiFlags, mtOffset, startHyphen, endHyphen, + &layout)) { return layout; } - layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint); + layout.doLayout(textBuf, range, bidiFlags, minikinPaint, startHyphen, endHyphen); return layout; } @@ -74,8 +78,14 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface, const uint16_t* buf, size_t start, size_t count, size_t bufSize, float* advances) { minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface); - return minikin::Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinPaint, - advances, nullptr /* extent */); + const minikin::U16StringPiece textBuf(buf, bufSize); + const minikin::Range range(start, start + count); + const minikin::HyphenEdit hyphenEdit = static_cast(paint->getHyphenEdit()); + const minikin::StartHyphenEdit startHyphen = minikin::startHyphenEdit(hyphenEdit); + const minikin::EndHyphenEdit endHyphen = minikin::endHyphenEdit(hyphenEdit); + + return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen, + endHyphen, advances, nullptr /* extent */); } bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) { -- GitLab From 059aa39fab52971fd7a31f45f2965d43896850c5 Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Tue, 30 Jan 2018 12:02:27 -0800 Subject: [PATCH 117/416] Used the better hash method provided by Objects Test: Unit tests Bug: 64132030 Change-Id: Ib0fa616b28df97caf5457fd9069fbd76a80b17cc --- .../android/telephony/data/DataCallResponse.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index ef3a183f780..25f51333350 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -27,6 +27,7 @@ import android.os.Parcelable; import java.net.InetAddress; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Description of the response of a setup data call connection request. @@ -220,17 +221,8 @@ public final class DataCallResponse implements Parcelable { @Override public int hashCode() { - return mStatus * 31 - + mSuggestedRetryTime * 37 - + mCid * 41 - + mActive * 43 - + mType.hashCode() * 47 - + mIfname.hashCode() * 53 - + mAddresses.hashCode() * 59 - + mDnses.hashCode() * 61 - + mGateways.hashCode() * 67 - + mPcscfs.hashCode() * 71 - + mMtu * 73; + return Objects.hash(mStatus, mSuggestedRetryTime, mCid, mActive, mType, mIfname, mAddresses, + mDnses, mGateways, mPcscfs, mMtu); } @Override -- GitLab From ddb8a96fe6a4842fbd8ea6419fe8cf859f632dd8 Mon Sep 17 00:00:00 2001 From: Jason Monk Date: Tue, 30 Jan 2018 13:27:33 -0500 Subject: [PATCH 118/416] Hook up the full slice access checkbox Store the apps that have this access in xml Test: uiservicestests Bug: 68751119 Change-Id: I06a6eab17dbda1a5849fb553b4f27df1c1796403 --- .../server/slice/SliceFullAccessList.java | 131 ++++++++++++++++++ .../server/slice/SliceManagerService.java | 91 ++++++++++-- .../server/slice/SliceFullAccessListTest.java | 103 ++++++++++++++ 3 files changed, 310 insertions(+), 15 deletions(-) create mode 100644 services/core/java/com/android/server/slice/SliceFullAccessList.java create mode 100644 services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java diff --git a/services/core/java/com/android/server/slice/SliceFullAccessList.java b/services/core/java/com/android/server/slice/SliceFullAccessList.java new file mode 100644 index 00000000000..591e809ad3d --- /dev/null +++ b/services/core/java/com/android/server/slice/SliceFullAccessList.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT 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.slice; + +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.UserManager; +import android.util.ArraySet; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.util.List; + +public class SliceFullAccessList { + + static final int DB_VERSION = 1; + private static final String TAG = "SliceFullAccessList"; + + private static final String TAG_LIST = "slice-access-list"; + private static final String TAG_PKG = "pkg"; + private static final String TAG_USER = "user"; + + private final String ATT_USER_ID = "user"; + private final String ATT_VERSION = "version"; + + private final SparseArray> mFullAccessPkgs = new SparseArray<>(); + private final Context mContext; + + public SliceFullAccessList(Context context) { + mContext = context; + } + + public boolean hasFullAccess(String pkg, int userId) { + ArraySet pkgs = mFullAccessPkgs.get(userId, null); + return pkgs != null && pkgs.contains(pkg); + } + + public void grantFullAccess(String pkg, int userId) { + ArraySet pkgs = mFullAccessPkgs.get(userId, null); + if (pkgs == null) { + pkgs = new ArraySet<>(); + mFullAccessPkgs.put(userId, pkgs); + } + pkgs.add(pkg); + } + + public void writeXml(XmlSerializer out) throws IOException { + out.startTag(null, TAG_LIST); + out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION)); + + final int N = mFullAccessPkgs.size(); + for (int i = 0 ; i < N; i++) { + final int userId = mFullAccessPkgs.keyAt(i); + final ArraySet pkgs = mFullAccessPkgs.valueAt(i); + out.startTag(null, TAG_USER); + out.attribute(null, ATT_USER_ID, Integer.toString(userId)); + if (pkgs != null) { + final int M = pkgs.size(); + for (int j = 0; j < M; j++) { + out.startTag(null, TAG_PKG); + out.text(pkgs.valueAt(j)); + out.endTag(null, TAG_PKG); + + } + } + out.endTag(null, TAG_USER); + } + out.endTag(null, TAG_LIST); + } + + public void readXml(XmlPullParser parser) throws XmlPullParserException, IOException { + // upgrade xml + int xmlVersion = XmlUtils.readIntAttribute(parser, ATT_VERSION, 0); + final List activeUsers = UserManager.get(mContext).getUsers(true); + for (UserInfo userInfo : activeUsers) { + upgradeXml(xmlVersion, userInfo.getUserHandle().getIdentifier()); + } + + mFullAccessPkgs.clear(); + // read grants + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + String tag = parser.getName(); + if (type == XmlPullParser.END_TAG + && TAG_LIST.equals(tag)) { + break; + } + if (type == XmlPullParser.START_TAG) { + if (TAG_USER.equals(tag)) { + final int userId = XmlUtils.readIntAttribute(parser, ATT_USER_ID, 0); + ArraySet pkgs = new ArraySet<>(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + String userTag = parser.getName(); + if (type == XmlPullParser.END_TAG + && TAG_USER.equals(userTag)) { + break; + } + if (type == XmlPullParser.START_TAG) { + if (TAG_PKG.equals(userTag)) { + final String pkg = parser.nextText(); + pkgs.add(pkg); + } + } + } + mFullAccessPkgs.put(userId, pkgs); + } + } + } + } + + protected void upgradeXml(final int xmlVersion, final int userId) {} +} diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java index 8e7daeef390..5db0fc0d9e0 100644 --- a/services/core/java/com/android/server/slice/SliceManagerService.java +++ b/services/core/java/com/android/server/slice/SliceManagerService.java @@ -39,6 +39,7 @@ import android.content.pm.ResolveInfo; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; +import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -47,7 +48,10 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.AtomicFile; import android.util.Log; +import android.util.Slog; +import android.util.Xml.Encoding; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -57,6 +61,16 @@ import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.SystemService; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -77,6 +91,8 @@ public class SliceManagerService extends ISliceManager.Stub { private final ArraySet mUserGrants = new ArraySet<>(); private final Handler mHandler; private final ContentObserver mObserver; + private final AtomicFile mSliceAccessFile; + private final SliceFullAccessList mAccessList; public SliceManagerService(Context context) { this(context, createHandler().getLooper()); @@ -101,6 +117,21 @@ public class SliceManagerService extends ISliceManager.Stub { } } }; + final File systemDir = new File(Environment.getDataDirectory(), "system"); + mSliceAccessFile = new AtomicFile(new File(systemDir, "slice_access.xml")); + mAccessList = new SliceFullAccessList(mContext); + + synchronized (mSliceAccessFile) { + if (!mSliceAccessFile.exists()) return; + try { + InputStream input = mSliceAccessFile.openRead(); + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setInput(input, Encoding.UTF_8.name()); + mAccessList.readXml(parser); + } catch (IOException | XmlPullParserException e) { + Slog.d(TAG, "Can't read slice access file", e); + } + } } /// ----- Lifecycle stuff ----- @@ -175,11 +206,11 @@ public class SliceManagerService extends ISliceManager.Stub { == PERMISSION_GRANTED) { return SliceManager.PERMISSION_GRANTED; } - if (hasFullSliceAccess(pkg, uid)) { + if (hasFullSliceAccess(pkg, UserHandle.getUserId(uid))) { return SliceManager.PERMISSION_GRANTED; } synchronized (mLock) { - if (mUserGrants.contains(new SliceGrant(uri, pkg))) { + if (mUserGrants.contains(new SliceGrant(uri, pkg, UserHandle.getUserId(uid)))) { return SliceManager.PERMISSION_USER_GRANTED; } } @@ -192,18 +223,20 @@ public class SliceManagerService extends ISliceManager.Stub { getContext().enforceCallingOrSelfPermission(permission.MANAGE_SLICE_PERMISSIONS, "Slice granting requires MANAGE_SLICE_PERMISSIONS"); if (allSlices) { - // TODO: Manage full access grants. + mAccessList.grantFullAccess(pkg, Binder.getCallingUserHandle().getIdentifier()); + mHandler.post(mSaveAccessList); } else { synchronized (mLock) { - mUserGrants.add(new SliceGrant(uri, pkg)); - } - long ident = Binder.clearCallingIdentity(); - try { - mContext.getContentResolver().notifyChange(uri, null); - } finally { - Binder.restoreCallingIdentity(ident); + mUserGrants.add(new SliceGrant(uri, pkg, + Binder.getCallingUserHandle().getIdentifier())); } } + long ident = Binder.clearCallingIdentity(); + try { + mContext.getContentResolver().notifyChange(uri, null); + } finally { + Binder.restoreCallingIdentity(ident); + } synchronized (mLock) { for (PinnedSliceState p : mPinnedSlicesByUri.values()) { p.recheckPackage(pkg); @@ -260,7 +293,7 @@ public class SliceManagerService extends ISliceManager.Stub { protected int checkAccess(String pkg, Uri uri, int uid, int pid) { int user = UserHandle.getUserId(uid); // Check for default launcher/assistant. - if (!hasFullSliceAccess(pkg, uid)) { + if (!hasFullSliceAccess(pkg, user)) { // Also allow things with uri access. if (getContext().checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != PERMISSION_GRANTED) { @@ -411,8 +444,7 @@ public class SliceManagerService extends ISliceManager.Stub { } private boolean isGrantedFullAccess(String pkg, int userId) { - // TODO: This will be user granted access, if we allow this through a prompt. - return false; + return mAccessList.hasFullAccess(pkg, userId); } private static ServiceThread createHandler() { @@ -422,6 +454,32 @@ public class SliceManagerService extends ISliceManager.Stub { return handlerThread; } + private final Runnable mSaveAccessList = new Runnable() { + @Override + public void run() { + synchronized (mSliceAccessFile) { + final FileOutputStream stream; + try { + stream = mSliceAccessFile.startWrite(); + } catch (IOException e) { + Slog.w(TAG, "Failed to save access file", e); + return; + } + + try { + XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer(); + out.setOutput(stream, Encoding.UTF_8.name()); + mAccessList.writeXml(out); + out.flush(); + mSliceAccessFile.finishWrite(stream); + } catch (IOException | XmlPullParserException e) { + Slog.w(TAG, "Failed to save access file, restoring backup", e); + mSliceAccessFile.failWrite(stream); + } + } + } + }; + public static class Lifecycle extends SystemService { private SliceManagerService mService; @@ -456,10 +514,12 @@ public class SliceManagerService extends ISliceManager.Stub { private class SliceGrant { private final Uri mUri; private final String mPkg; + private final int mUserId; - public SliceGrant(Uri uri, String pkg) { + public SliceGrant(Uri uri, String pkg, int userId) { mUri = uri; mPkg = pkg; + mUserId = userId; } @Override @@ -471,7 +531,8 @@ public class SliceManagerService extends ISliceManager.Stub { public boolean equals(Object obj) { if (!(obj instanceof SliceGrant)) return false; SliceGrant other = (SliceGrant) obj; - return Objects.equals(other.mUri, mUri) && Objects.equals(other.mPkg, mPkg); + return Objects.equals(other.mUri, mUri) && Objects.equals(other.mPkg, mPkg) + && (other.mUserId == mUserId); } } } diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java new file mode 100644 index 00000000000..7c14d083d3c --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT 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.slice; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.support.test.filters.SmallTest; +import android.util.Xml.Encoding; + +import com.android.server.UiServiceTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlSerializer; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +@SmallTest +public class SliceFullAccessListTest extends UiServiceTestCase { + + private static final String TEST_XML = "pkgpkg1pkgpkg2"; + + private SliceFullAccessList mAccessList; + + @Before + public void setup() { + mAccessList = new SliceFullAccessList(mContext); + } + + @Test + public void testNoDefaultAccess() { + assertFalse(mAccessList.hasFullAccess("pkg", 0)); + } + + @Test + public void testGrantAccess() { + mAccessList.grantFullAccess("pkg", 0); + assertTrue(mAccessList.hasFullAccess("pkg", 0)); + } + + @Test + public void testUserSeparation() { + mAccessList.grantFullAccess("pkg", 1); + assertFalse(mAccessList.hasFullAccess("pkg", 0)); + } + + @Test + public void testSerialization() throws XmlPullParserException, IOException { + mAccessList.grantFullAccess("pkg", 0); + mAccessList.grantFullAccess("pkg1", 0); + mAccessList.grantFullAccess("pkg", 1); + mAccessList.grantFullAccess("pkg2", 3); + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer(); + out.setOutput(output, Encoding.UTF_8.name()); + mAccessList.writeXml(out); + out.flush(); + + assertEquals(TEST_XML, output.toString(Encoding.UTF_8.name())); + } + + @Test + public void testDeSerialization() throws XmlPullParserException, IOException { + ByteArrayInputStream input = new ByteArrayInputStream(TEST_XML.getBytes()); + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setInput(input, Encoding.UTF_8.name()); + + mAccessList.readXml(parser); + + assertTrue(mAccessList.hasFullAccess("pkg", 0)); + assertTrue(mAccessList.hasFullAccess("pkg1", 0)); + assertTrue(mAccessList.hasFullAccess("pkg", 1)); + assertTrue(mAccessList.hasFullAccess("pkg2", 3)); + + assertFalse(mAccessList.hasFullAccess("pkg3", 0)); + assertFalse(mAccessList.hasFullAccess("pkg1", 1)); + assertFalse(mAccessList.hasFullAccess("pkg", 3)); + assertFalse(mAccessList.hasFullAccess("pkg", 2)); + } +} \ No newline at end of file -- GitLab From 87b7f8f1b89b6e494fead8a3705f58c1d5493b14 Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Tue, 30 Jan 2018 18:39:28 +0000 Subject: [PATCH 119/416] Re-added support for deprecated BIND_AUTOFILL permission. This permission was renamed during the O previews but it was supported on the final O release, so we need to carry it over. Test: atest CtsAutoFillServiceTestCases Bug: 70682223 Change-Id: I2b3d798fe9c09751138f154e6e69e6af6b60dbb1 --- .../service/autofill/AutofillServiceInfo.java | 22 +++++++++++++++---- core/res/AndroidManifest.xml | 9 ++++++++ proto/src/metrics_constants.proto | 5 +++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java index 5c7388f79a4..4f2f6cb36d8 100644 --- a/core/java/android/service/autofill/AutofillServiceInfo.java +++ b/core/java/android/service/autofill/AutofillServiceInfo.java @@ -25,16 +25,20 @@ import android.content.pm.ServiceInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.metrics.LogMaker; import android.os.RemoteException; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; import com.android.internal.R; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; /** @@ -91,10 +95,20 @@ public final class AutofillServiceInfo { private static TypedArray getMetaDataArray(PackageManager pm, ServiceInfo si) { // Check for permissions. if (!Manifest.permission.BIND_AUTOFILL_SERVICE.equals(si.permission)) { - Log.w(TAG, "AutofillService from '" + si.packageName + "' does not require permission " - + Manifest.permission.BIND_AUTOFILL_SERVICE); - throw new SecurityException("Service does not require permission " - + Manifest.permission.BIND_AUTOFILL_SERVICE); + if (Manifest.permission.BIND_AUTOFILL.equals(si.permission)) { + // Let it go for now... + Log.w(TAG, "AutofillService from '" + si.packageName + "' uses unsupported " + + "permission " + Manifest.permission.BIND_AUTOFILL + ". It works for " + + "now, but might not be supported on future releases"); + new MetricsLogger().write(new LogMaker(MetricsEvent.AUTOFILL_INVALID_PERMISSION) + .setPackageName(si.packageName)); + } else { + Log.w(TAG, "AutofillService from '" + si.packageName + + "' does not require permission " + + Manifest.permission.BIND_AUTOFILL_SERVICE); + throw new SecurityException("Service does not require permission " + + Manifest.permission.BIND_AUTOFILL_SERVICE); + } } // Get the AutoFill metadata, if declared. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 36082ca1b3b..74d6f19a249 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2729,6 +2729,15 @@ + + + + + + + diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index 9f6a946dea2..997fe6dd44b 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -32,80 +32,108 @@ + android:gravity="end" > - + android:layout_weight="1" > + + - + + - - + + + - + + - + + - - + + + + + - + + diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java index 92475da697b..76baee4c836 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java @@ -84,6 +84,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, protected View mEdit; private TouchAnimator mAnimator; + private View mActionsContainer; + public QSFooterImpl(Context context, AttributeSet attrs) { super(context, attrs); } @@ -107,6 +109,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, mMultiUserSwitch = findViewById(R.id.multi_user_switch); mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar); + mActionsContainer = findViewById(R.id.qs_footer_actions_container); + // RenderThread is doing more harm than good when touching the header (to expand quick // settings), so disable it for this view ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true); @@ -158,10 +162,8 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, @Nullable private TouchAnimator createSettingsAlphaAnimator() { return new TouchAnimator.Builder() - .addFloat(mEdit, "alpha", 0, 1) - .addFloat(mMultiUserSwitch, "alpha", 0, 1) .addFloat(mCarrierText, "alpha", 0, 1) - .addFloat(mSettingsButton, "alpha", 0, 1) + .addFloat(mActionsContainer, "alpha", 0, 1) .build(); } @@ -269,6 +271,11 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, @Override public void onClick(View v) { + // Don't do anything until view are unhidden + if (!mExpanded) { + return; + } + if (v == mSettingsButton) { if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) { // If user isn't setup just unlock the device and dump them back at SUW. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 669439d7f6c..d8e10516fe6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -69,7 +69,7 @@ public class QSFragment extends Fragment implements QS { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { - inflater =inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme)); + inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme)); return inflater.inflate(R.layout.qs_panel, container, false); } -- GitLab From 2f0567b00d22edba0b87c0d06766c3cacaa0e041 Mon Sep 17 00:00:00 2001 From: chaviw Date: Mon, 29 Jan 2018 16:22:02 -0800 Subject: [PATCH 126/416] Use self's pendingTransaction object when setting position. When iterating through each child to set its updated position, the transaction object was passed to the children. This could allow multiple transactions to update the same property on the same child. If that occurs, then the merge does not guarantee the same order. This could cause earlier transactions to overwrite later ones. Instead, have each WC update its own transaction object. Change-Id: Ie1bb758e4efd74fa012c7ec945d9c1c586162df4 Fixes: 72198558 Fixes: 72463091 Test: Issues from bugs no longer occur. Test: go/wm-smoke-auto --- .../core/java/com/android/server/wm/TaskStack.java | 8 ++------ .../java/com/android/server/wm/WindowContainer.java | 10 +++------- .../core/java/com/android/server/wm/WindowState.java | 8 ++++++-- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index cf54b67e122..ae5341bd8e4 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -736,15 +736,11 @@ public class TaskStack extends WindowContainer implements } private void updateSurfaceBounds() { - updateSurfaceBounds(getPendingTransaction()); + updateSurfaceSize(getPendingTransaction()); + updateSurfacePosition(); scheduleAnimation(); } - void updateSurfaceBounds(SurfaceControl.Transaction transaction) { - updateSurfaceSize(transaction); - updateSurfacePosition(transaction); - } - private void updateSurfaceSize(SurfaceControl.Transaction transaction) { if (mSurfaceControl == null) { return; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 0c0ce0e17dd..6bd7f22a4a4 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -131,7 +131,7 @@ class WindowContainer extends ConfigurationContainer< @Override public void onConfigurationChanged(Configuration newParentConfig) { super.onConfigurationChanged(newParentConfig); - updateSurfacePosition(getPendingTransaction()); + updateSurfacePosition(); scheduleAnimation(); } @@ -1204,7 +1204,7 @@ class WindowContainer extends ConfigurationContainer< } } - void updateSurfacePosition(SurfaceControl.Transaction transaction) { + void updateSurfacePosition() { if (mSurfaceControl == null) { return; } @@ -1214,12 +1214,8 @@ class WindowContainer extends ConfigurationContainer< return; } - transaction.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); + getPendingTransaction().setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y); mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y); - - for (int i = mChildren.size() - 1; i >= 0; i--) { - mChildren.get(i).updateSurfacePosition(transaction); - } } void getRelativePosition(Point outPos) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 55c982c174a..53a8d82f551 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -4519,7 +4519,7 @@ class WindowState extends WindowContainer implements WindowManagerP if (dimmer != null) { applyDims(dimmer); } - updateSurfacePosition(mPendingTransaction); + updateSurfacePosition(); mWinAnimator.prepareSurfaceLocked(true); super.prepareSurfaces(); @@ -4541,7 +4541,11 @@ class WindowState extends WindowContainer implements WindowManagerP } @Override - void updateSurfacePosition(Transaction t) { + void updateSurfacePosition() { + updateSurfacePosition(getPendingTransaction()); + } + + private void updateSurfacePosition(Transaction t) { if (mSurfaceControl == null) { return; } -- GitLab From a8e8b659d0a872c9221e70b94c094cb6bff0508a Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Wed, 25 Oct 2017 14:25:55 -0400 Subject: [PATCH 127/416] Add Confirmation API. This CL adds new Framework APIs that can be used for the secure confirmations. This includes support for configuring a key such that it can only sign data returned by the confirmation APIs. Bug: 63928580 Test: Manually tested. Change-Id: I94c1fc532376bd555b3dc37fc4709469450cfde6 --- api/current.txt | 36 +++ ...onfirmationAlreadyPresentingException.java | 29 ++ .../security/ConfirmationCallback.java | 54 ++++ .../android/security/ConfirmationDialog.java | 283 ++++++++++++++++++ .../ConfirmationNotAvailableException.java | 30 ++ .../security/keymaster/KeymasterDefs.java | 1 + .../AndroidKeyStoreKeyGeneratorSpi.java | 6 +- .../AndroidKeyStoreKeyPairGeneratorSpi.java | 6 +- .../AndroidKeyStoreSecretKeyFactorySpi.java | 5 +- .../security/keystore/AndroidKeyStoreSpi.java | 6 +- .../keystore/KeyGenParameterSpec.java | 52 +++- .../android/security/keystore/KeyInfo.java | 26 +- .../security/keystore/KeyProtection.java | 52 +++- .../security/keystore/KeymasterUtils.java | 10 +- 14 files changed, 583 insertions(+), 13 deletions(-) create mode 100644 core/java/android/security/ConfirmationAlreadyPresentingException.java create mode 100644 core/java/android/security/ConfirmationCallback.java create mode 100644 core/java/android/security/ConfirmationDialog.java create mode 100644 core/java/android/security/ConfirmationNotAvailableException.java diff --git a/api/current.txt b/api/current.txt index 800412bd2e1..5ae228eb8fa 100644 --- a/api/current.txt +++ b/api/current.txt @@ -38192,6 +38192,37 @@ package android.security { method public java.security.KeyPair getKeyPair(); } + public class ConfirmationAlreadyPresentingException extends java.lang.Exception { + ctor public ConfirmationAlreadyPresentingException(); + ctor public ConfirmationAlreadyPresentingException(java.lang.String); + } + + public abstract class ConfirmationCallback { + ctor public ConfirmationCallback(); + method public void onConfirmedByUser(byte[]); + method public void onDismissedByApplication(); + method public void onDismissedByUser(); + method public void onError(java.lang.Exception); + } + + public class ConfirmationDialog { + method public void cancelPrompt(); + method public static boolean isSupported(); + method public void presentPrompt(java.util.concurrent.Executor, android.security.ConfirmationCallback) throws android.security.ConfirmationAlreadyPresentingException, android.security.ConfirmationNotAvailableException; + } + + public static class ConfirmationDialog.Builder { + ctor public ConfirmationDialog.Builder(); + method public android.security.ConfirmationDialog build(android.content.Context); + method public android.security.ConfirmationDialog.Builder setExtraData(byte[]); + method public android.security.ConfirmationDialog.Builder setPromptText(java.lang.CharSequence); + } + + public class ConfirmationNotAvailableException extends java.lang.Exception { + ctor public ConfirmationNotAvailableException(); + ctor public ConfirmationNotAvailableException(java.lang.String); + } + public final class KeyChain { ctor public KeyChain(); method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String); @@ -38301,6 +38332,7 @@ package android.security.keystore { method public boolean isTrustedUserPresenceRequired(); method public boolean isUserAuthenticationRequired(); method public boolean isUserAuthenticationValidWhileOnBody(); + method public boolean isUserConfirmationRequired(); } public static final class KeyGenParameterSpec.Builder { @@ -38328,6 +38360,7 @@ package android.security.keystore { method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean); method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int); + method public android.security.keystore.KeyGenParameterSpec.Builder setUserConfirmationRequired(boolean); } public class KeyInfo implements java.security.spec.KeySpec { @@ -38349,6 +38382,7 @@ package android.security.keystore { method public boolean isUserAuthenticationRequired(); method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware(); method public boolean isUserAuthenticationValidWhileOnBody(); + method public boolean isUserConfirmationRequired(); } public class KeyNotYetValidException extends java.security.InvalidKeyException { @@ -38416,6 +38450,7 @@ package android.security.keystore { method public boolean isRandomizedEncryptionRequired(); method public boolean isUserAuthenticationRequired(); method public boolean isUserAuthenticationValidWhileOnBody(); + method public boolean isUserConfirmationRequired(); } public static final class KeyProtection.Builder { @@ -38434,6 +38469,7 @@ package android.security.keystore { method public android.security.keystore.KeyProtection.Builder setUserAuthenticationRequired(boolean); method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidWhileOnBody(boolean); method public android.security.keystore.KeyProtection.Builder setUserAuthenticationValidityDurationSeconds(int); + method public android.security.keystore.KeyProtection.Builder setUserConfirmationRequired(boolean); } public class StrongBoxUnavailableException extends java.security.ProviderException { diff --git a/core/java/android/security/ConfirmationAlreadyPresentingException.java b/core/java/android/security/ConfirmationAlreadyPresentingException.java new file mode 100644 index 00000000000..ae4ec5a1bf4 --- /dev/null +++ b/core/java/android/security/ConfirmationAlreadyPresentingException.java @@ -0,0 +1,29 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.security; + +/** + * This exception is thrown when presenting a prompt fails because another prompt is already + * being presented. + */ +public class ConfirmationAlreadyPresentingException extends Exception { + public ConfirmationAlreadyPresentingException() {} + + public ConfirmationAlreadyPresentingException(String message) { + super(message); + } +} diff --git a/core/java/android/security/ConfirmationCallback.java b/core/java/android/security/ConfirmationCallback.java new file mode 100644 index 00000000000..4670bce3a9e --- /dev/null +++ b/core/java/android/security/ConfirmationCallback.java @@ -0,0 +1,54 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.security; + +import android.annotation.NonNull; + +/** + * Callback class used when signaling that a prompt is no longer being presented. + */ +public abstract class ConfirmationCallback { + /** + * Called when the requested prompt was accepted by the user. + * + * The format of 'dataThatWasConfirmed' parameter is a CBOR + * encoded map (type 5) with (at least) the keys prompt and + * extra. The keys are encoded as CBOR text string (type 3). The value of + * promptText is encoded as CBOR text string (type 3), and the value of extraData is encoded as + * CBOR byte string (type 2). Other keys may be added in the future. + * + * @param dataThatWasConfirmed the data that was confirmed, see above for the format. + */ + public void onConfirmedByUser(@NonNull byte[] dataThatWasConfirmed) {} + + /** + * Called when the requested prompt was dismissed (not accepted) by the user. + */ + public void onDismissedByUser() {} + + /** + * Called when the requested prompt was dismissed by the application. + */ + public void onDismissedByApplication() {} + + /** + * Called when the requested prompt was dismissed because of a low-level error. + * + * @param e an exception representing the error. + */ + public void onError(Exception e) {} +} diff --git a/core/java/android/security/ConfirmationDialog.java b/core/java/android/security/ConfirmationDialog.java new file mode 100644 index 00000000000..e9df3705db6 --- /dev/null +++ b/core/java/android/security/ConfirmationDialog.java @@ -0,0 +1,283 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.security; + +import android.annotation.NonNull; +import android.content.Context; +import android.text.TextUtils; +import android.util.Log; + +import java.util.Locale; +import java.util.concurrent.Executor; + +/** + * Class used for displaying confirmation prompts. + * + *

Confirmation prompts are prompts shown to the user to confirm a given text and are + * implemented in a way that a positive response indicates with high confidence that the user has + * seen the given text, even if the Android framework (including the kernel) was + * compromised. Implementing confirmation prompts with these guarantees requires dedicated + * hardware-support and may not always be available. + * + *

Confirmation prompts are typically used with an external entitity - the Relying Party - + * in the following way. The setup steps are as follows: + *

    + *
  • Before first use, the application generates a key-pair with the + * {@link android.security.keystore.KeyGenParameterSpec.Builder#setUserConfirmationRequired + * CONFIRMATION tag} set. Device attestation, + * e.g. {@link java.security.KeyStore#getCertificateChain getCertificateChain()}, is used to + * generate a certificate chain that includes the public key (Kpub in the following) + * of the newly generated key. + *
  • The application sends Kpub and the certificate chain resulting from device + * attestation to the Relying Party. + *
  • The Relying Party validates the certificate chain which involves checking the root + * certificate is what is expected (e.g. a certificate from Google), each certificate signs the + * next one in the chain, ending with Kpub, and that the attestation certificate + * asserts that Kpub has the + * {@link android.security.keystore.KeyGenParameterSpec.Builder#setUserConfirmationRequired + * CONFIRMATION tag} set. + * Additionally the relying party stores Kpub and associates it with the device + * it was received from. + *
+ * + *

The Relying Party is typically an external device (for example connected via + * Bluetooth) or application server. + * + *

Before executing a transaction which requires a high assurance of user content, the + * application does the following: + *

    + *
  • The application gets a cryptographic nonce from the Relying Party and passes this as + * the extraData (via the Builder helper class) to the + * {@link #presentPrompt presentPrompt()} method. The Relying Party stores the nonce locally + * since it'll use it in a later step. + *
  • If the user approves the prompt a Confirmation Response is returned in the + * {@link ConfirmationCallback#onConfirmedByUser onConfirmedByUser(byte[])} callback as the + * dataThatWasConfirmed parameter. This blob contains the text that was shown to the + * user, the extraData parameter, and possibly other data. + *
  • The application signs the Confirmation Response with the previously created key and + * sends the blob and the signature to the Relying Party. + *
  • The Relying Party checks that the signature was made with Kpub and then + * extracts promptText matches what is expected and extraData matches the + * previously created nonce. If all checks passes, the transaction is executed. + *
+ * + *

A common way of implementing the "promptText is what is expected" check in the + * last bullet, is to have the Relying Party generate promptText and store it + * along the nonce in the extraData blob. + */ +public class ConfirmationDialog { + private static final String TAG = "ConfirmationDialog"; + + private CharSequence mPromptText; + private byte[] mExtraData; + private ConfirmationCallback mCallback; + private Executor mExecutor; + + private final KeyStore mKeyStore = KeyStore.getInstance(); + + private void doCallback(int responseCode, byte[] dataThatWasConfirmed, + ConfirmationCallback callback) { + switch (responseCode) { + case KeyStore.CONFIRMATIONUI_OK: + callback.onConfirmedByUser(dataThatWasConfirmed); + break; + + case KeyStore.CONFIRMATIONUI_CANCELED: + callback.onDismissedByUser(); + break; + + case KeyStore.CONFIRMATIONUI_ABORTED: + callback.onDismissedByApplication(); + break; + + case KeyStore.CONFIRMATIONUI_SYSTEM_ERROR: + callback.onError(new Exception("System error returned by ConfirmationUI.")); + break; + + default: + callback.onError(new Exception("Unexpected responseCode=" + responseCode + + " from onConfirmtionPromptCompleted() callback.")); + break; + } + } + + private final android.os.IBinder mCallbackBinder = + new android.security.IConfirmationPromptCallback.Stub() { + @Override + public void onConfirmationPromptCompleted( + int responseCode, final byte[] dataThatWasConfirmed) + throws android.os.RemoteException { + if (mCallback != null) { + ConfirmationCallback callback = mCallback; + Executor executor = mExecutor; + mCallback = null; + mExecutor = null; + if (executor == null) { + doCallback(responseCode, dataThatWasConfirmed, callback); + } else { + executor.execute(new Runnable() { + @Override + public void run() { + doCallback(responseCode, dataThatWasConfirmed, callback); + } + }); + } + } + } + }; + + /** + * A builder that collects arguments, to be shown on the system-provided confirmation dialog. + */ + public static class Builder { + + private CharSequence mPromptText; + private byte[] mExtraData; + + /** + * Creates a builder for the confirmation dialog. + */ + public Builder() { + } + + /** + * Sets the prompt text for the dialog. + * + * @param promptText the text to present in the prompt. + * @return the builder. + */ + public Builder setPromptText(CharSequence promptText) { + mPromptText = promptText; + return this; + } + + /** + * Sets the extra data for the dialog. + * + * @param extraData data to include in the response data. + * @return the builder. + */ + public Builder setExtraData(byte[] extraData) { + mExtraData = extraData; + return this; + } + + /** + * Creates a {@link ConfirmationDialog} with the arguments supplied to this builder. + * + * @param context the application context + * @return a {@link ConfirmationDialog} + * @throws IllegalArgumentException if any of the required fields are not set. + */ + public ConfirmationDialog build(Context context) { + if (TextUtils.isEmpty(mPromptText)) { + throw new IllegalArgumentException("prompt text must be set and non-empty"); + } + if (mExtraData == null) { + throw new IllegalArgumentException("extraData must be set"); + } + return new ConfirmationDialog(mPromptText, mExtraData); + } + } + + private ConfirmationDialog(CharSequence promptText, byte[] extraData) { + mPromptText = promptText; + mExtraData = extraData; + } + + /** + * Requests a confirmation prompt to be presented to the user. + * + * When the prompt is no longer being presented, one of the methods in + * {@link ConfirmationCallback} is called on the supplied callback object. + * + * @param executor the executor identifying the thread that will receive the callback. + * @param callback the callback to use when the dialog is done showing. + * @throws IllegalArgumentException if the prompt text is too long or malfomed. + * @throws ConfirmationAlreadyPresentingException if another prompt is being presented. + * @throws ConfirmationNotAvailableException if confirmation prompts are not supported. + */ + public void presentPrompt(@NonNull Executor executor, @NonNull ConfirmationCallback callback) + throws ConfirmationAlreadyPresentingException, + ConfirmationNotAvailableException { + if (mCallback != null) { + throw new ConfirmationAlreadyPresentingException(); + } + mCallback = callback; + mExecutor = executor; + + int uiOptionsAsFlags = 0; + // TODO: set AccessibilityInverted, AccessibilityMagnified in uiOptionsAsFlags as needed. + String locale = Locale.getDefault().toLanguageTag(); + int responseCode = mKeyStore.presentConfirmationPrompt( + mCallbackBinder, mPromptText.toString(), mExtraData, locale, uiOptionsAsFlags); + switch (responseCode) { + case KeyStore.CONFIRMATIONUI_OK: + return; + + case KeyStore.CONFIRMATIONUI_OPERATION_PENDING: + throw new ConfirmationAlreadyPresentingException(); + + case KeyStore.CONFIRMATIONUI_UNIMPLEMENTED: + throw new ConfirmationNotAvailableException(); + + case KeyStore.CONFIRMATIONUI_UIERROR: + throw new IllegalArgumentException(); + + default: + // Unexpected error code. + Log.w(TAG, + "Unexpected responseCode=" + responseCode + + " from presentConfirmationPrompt() call."); + throw new IllegalArgumentException(); + } + } + + /** + * Cancels a prompt currently being displayed. + * + * On success, the + * {@link ConfirmationCallback#onDismissedByApplication onDismissedByApplication()} method on + * the supplied callback object will be called asynchronously. + * + * @throws IllegalStateException if no prompt is currently being presented. + */ + public void cancelPrompt() { + int responseCode = mKeyStore.cancelConfirmationPrompt(mCallbackBinder); + if (responseCode == KeyStore.CONFIRMATIONUI_OK) { + return; + } else if (responseCode == KeyStore.CONFIRMATIONUI_OPERATION_PENDING) { + throw new IllegalStateException(); + } else { + // Unexpected error code. + Log.w(TAG, + "Unexpected responseCode=" + responseCode + + " from cancelConfirmationPrompt() call."); + throw new IllegalStateException(); + } + } + + /** + * Checks if the device supports confirmation prompts. + * + * @return true if confirmation prompts are supported by the device. + */ + public static boolean isSupported() { + // TODO: read and return system property. + return true; + } +} diff --git a/core/java/android/security/ConfirmationNotAvailableException.java b/core/java/android/security/ConfirmationNotAvailableException.java new file mode 100644 index 00000000000..8d0e672c067 --- /dev/null +++ b/core/java/android/security/ConfirmationNotAvailableException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.security; + +/** + * This exception is thrown when presenting a prompt fails because the the environment lacks + * facilities for showing confirmations. + */ +public class ConfirmationNotAvailableException extends Exception { + public ConfirmationNotAvailableException() { + } + + public ConfirmationNotAvailableException(String message) { + super(message); + } +} diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index 34643703284..1d133350435 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -74,6 +74,7 @@ public final class KeymasterDefs { public static final int KM_TAG_AUTH_TIMEOUT = KM_UINT | 505; public static final int KM_TAG_ALLOW_WHILE_ON_BODY = KM_BOOL | 506; public static final int KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED = KM_BOOL | 507; + public static final int KM_TAG_TRUSTED_CONFIRMATION_REQUIRED = KM_BOOL | 508; public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600; public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601; diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java index f721ed3af7b..09b3b9b523b 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java @@ -248,7 +248,8 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { spec.getUserAuthenticationValidityDurationSeconds(), spec.isUserAuthenticationValidWhileOnBody(), spec.isInvalidatedByBiometricEnrollment(), - GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */); + GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */, + spec.isUserConfirmationRequired()); } catch (IllegalStateException | IllegalArgumentException e) { throw new InvalidAlgorithmParameterException(e); } @@ -289,7 +290,8 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { spec.getUserAuthenticationValidityDurationSeconds(), spec.isUserAuthenticationValidWhileOnBody(), spec.isInvalidatedByBiometricEnrollment(), - GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */); + GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */, + spec.isUserConfirmationRequired()); if (spec.isTrustedUserPresenceRequired()) { args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED); } diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java index d1eb6888bbf..e33e3cd4e92 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -349,7 +349,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mSpec.getUserAuthenticationValidityDurationSeconds(), mSpec.isUserAuthenticationValidWhileOnBody(), mSpec.isInvalidatedByBiometricEnrollment(), - GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */); + GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */, + mSpec.isUserConfirmationRequired()); } catch (IllegalArgumentException | IllegalStateException e) { throw new InvalidAlgorithmParameterException(e); } @@ -545,7 +546,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato mSpec.getUserAuthenticationValidityDurationSeconds(), mSpec.isUserAuthenticationValidWhileOnBody(), mSpec.isInvalidatedByBiometricEnrollment(), - GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */); + GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */, + mSpec.isUserConfirmationRequired()); args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart()); args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, mSpec.getKeyValidityForOriginationEnd()); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java index 9df37f58fd0..7bbc0996458 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java @@ -190,6 +190,8 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { && !keymasterSecureUserIds.contains(getGateKeeperSecureUserId()); } + boolean userConfirmationRequired = keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED); + return new KeyInfo(entryAlias, insideSecureHardware, origin, @@ -207,7 +209,8 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { userAuthenticationRequirementEnforcedBySecureHardware, userAuthenticationValidWhileOnBody, trustedUserPresenceRequred, - invalidatedByBiometricEnrollment); + invalidatedByBiometricEnrollment, + userConfirmationRequired); } private static BigInteger getGateKeeperSecureUserId() throws ProviderException { diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java index 440e0863fbb..05cc74a0bec 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java @@ -502,7 +502,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { spec.getUserAuthenticationValidityDurationSeconds(), spec.isUserAuthenticationValidWhileOnBody(), spec.isInvalidatedByBiometricEnrollment(), - spec.getBoundToSpecificSecureUserId()); + spec.getBoundToSpecificSecureUserId(), + spec.isUserConfirmationRequired()); importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, spec.getKeyValidityStart()); importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME, @@ -704,7 +705,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { params.getUserAuthenticationValidityDurationSeconds(), params.isUserAuthenticationValidWhileOnBody(), params.isInvalidatedByBiometricEnrollment(), - params.getBoundToSpecificSecureUserId()); + params.getBoundToSpecificSecureUserId(), + params.isUserConfirmationRequired()); KeymasterUtils.addMinMacLengthAuthorizationIfNecessary( args, keymasterAlgorithm, diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index a896c72463f..da23c70f58b 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -264,6 +264,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { private final boolean mUserAuthenticationValidWhileOnBody; private final boolean mInvalidatedByBiometricEnrollment; private final boolean mIsStrongBoxBacked; + private final boolean mUserConfirmationRequired; /** * @hide should be built with Builder @@ -293,7 +294,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { boolean uniqueIdIncluded, boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, - boolean isStrongBoxBacked) { + boolean isStrongBoxBacked, + boolean userConfirmationRequired) { if (TextUtils.isEmpty(keyStoreAlias)) { throw new IllegalArgumentException("keyStoreAlias must not be empty"); } @@ -341,6 +343,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; mIsStrongBoxBacked = isStrongBoxBacked; + mUserConfirmationRequired = userConfirmationRequired; } /** @@ -546,6 +549,26 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { return mUserAuthenticationRequired; } + /** + * Returns {@code true} if the key is authorized to be used only for messages confirmed by the + * user. + * + * Confirmation is separate from user authentication (see + * {@link Builder#setUserAuthenticationRequired(boolean)}). Keys can be created that require + * confirmation but not user authentication, or user authentication but not confirmation, or + * both. Confirmation verifies that some user with physical possession of the device has + * approved a displayed message. User authentication verifies that the correct user is present + * and has authenticated. + * + *

This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @see Builder#setUserConfirmationRequired(boolean) + */ + public boolean isUserConfirmationRequired() { + return mUserConfirmationRequired; + } + /** * Gets the duration of time (seconds) for which this key is authorized to be used after the * user is successfully authenticated. This has effect only if user authentication is required @@ -675,6 +698,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { private boolean mUserAuthenticationValidWhileOnBody; private boolean mInvalidatedByBiometricEnrollment = true; private boolean mIsStrongBoxBacked = false; + private boolean mUserConfirmationRequired; /** * Creates a new instance of the {@code Builder}. @@ -1062,6 +1086,29 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { return this; } + /** + * Sets whether this key is authorized to be used only for messages confirmed by the + * user. + * + * Confirmation is separate from user authentication (see + * {@link #setUserAuthenticationRequired(boolean)}). Keys can be created that require + * confirmation but not user authentication, or user authentication but not confirmation, + * or both. Confirmation verifies that some user with physical possession of the device has + * approved a displayed message. User authentication verifies that the correct user is + * present and has authenticated. + * + *

This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @see {@link android.security.ConfirmationPrompter ConfirmationPrompter} class for + * more details about user confirmations. + */ + @NonNull + public Builder setUserConfirmationRequired(boolean required) { + mUserConfirmationRequired = required; + return this; + } + /** * Sets the duration of time (seconds) for which this key is authorized to be used after the * user is successfully authenticated. This has effect if the key requires user @@ -1249,7 +1296,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { mUniqueIdIncluded, mUserAuthenticationValidWhileOnBody, mInvalidatedByBiometricEnrollment, - mIsStrongBoxBacked); + mIsStrongBoxBacked, + mUserConfirmationRequired); } } } diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java index 864f62a6de0..0a75cd5268b 100644 --- a/keystore/java/android/security/keystore/KeyInfo.java +++ b/keystore/java/android/security/keystore/KeyInfo.java @@ -82,6 +82,7 @@ public class KeyInfo implements KeySpec { private final boolean mUserAuthenticationValidWhileOnBody; private final boolean mTrustedUserPresenceRequired; private final boolean mInvalidatedByBiometricEnrollment; + private final boolean mUserConfirmationRequired; /** * @hide @@ -103,7 +104,8 @@ public class KeyInfo implements KeySpec { boolean userAuthenticationRequirementEnforcedBySecureHardware, boolean userAuthenticationValidWhileOnBody, boolean trustedUserPresenceRequired, - boolean invalidatedByBiometricEnrollment) { + boolean invalidatedByBiometricEnrollment, + boolean userConfirmationRequired) { mKeystoreAlias = keystoreKeyAlias; mInsideSecureHardware = insideSecureHardware; mOrigin = origin; @@ -125,6 +127,7 @@ public class KeyInfo implements KeySpec { mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; mTrustedUserPresenceRequired = trustedUserPresenceRequired; mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; + mUserConfirmationRequired = userConfirmationRequired; } /** @@ -259,6 +262,27 @@ public class KeyInfo implements KeySpec { return mUserAuthenticationRequired; } + /** + * Returns {@code true} if the key is authorized to be used only for messages confirmed by the + * user. + * + * Confirmation is separate from user authentication (see + * {@link #isUserAuthenticationRequired()}). Keys can be created that require confirmation but + * not user authentication, or user authentication but not confirmation, or both. Confirmation + * verifies that some user with physical possession of the device has approved a displayed + * message. User authentication verifies that the correct user is present and has + * authenticated. + * + *

This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @see KeyGenParameterSpec.Builder#setUserConfirmationRequired(boolean) + * @see KeyProtection.Builder#setUserConfirmationRequired(boolean) + */ + public boolean isUserConfirmationRequired() { + return mUserConfirmationRequired; + } + /** * Gets the duration of time (seconds) for which this key is authorized to be used after the * user is successfully authenticated. This has effect only if user authentication is required diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index dbacb9c53dd..b5b328192f2 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -228,6 +228,7 @@ public final class KeyProtection implements ProtectionParameter { private final boolean mInvalidatedByBiometricEnrollment; private final long mBoundToSecureUserId; private final boolean mCriticalToDeviceEncryption; + private final boolean mUserConfirmationRequired; private KeyProtection( Date keyValidityStart, @@ -244,7 +245,8 @@ public final class KeyProtection implements ProtectionParameter { boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, long boundToSecureUserId, - boolean criticalToDeviceEncryption) { + boolean criticalToDeviceEncryption, + boolean userConfirmationRequired) { mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart); mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd); mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd); @@ -262,6 +264,7 @@ public final class KeyProtection implements ProtectionParameter { mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; mBoundToSecureUserId = boundToSecureUserId; mCriticalToDeviceEncryption = criticalToDeviceEncryption; + mUserConfirmationRequired = userConfirmationRequired; } /** @@ -395,6 +398,26 @@ public final class KeyProtection implements ProtectionParameter { return mUserAuthenticationRequired; } + /** + * Returns {@code true} if the key is authorized to be used only for messages confirmed by the + * user. + * + * Confirmation is separate from user authentication (see + * {@link #isUserAuthenticationRequired()}). Keys can be created that require confirmation but + * not user authentication, or user authentication but not confirmation, or both. Confirmation + * verifies that some user with physical possession of the device has approved a displayed + * message. User authentication verifies that the correct user is present and has + * authenticated. + * + *

This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @see Builder#setUserConfirmationRequired(boolean) + */ + public boolean isUserConfirmationRequired() { + return mUserConfirmationRequired; + } + /** * Gets the duration of time (seconds) for which this key is authorized to be used after the * user is successfully authenticated. This has effect only if user authentication is required @@ -488,6 +511,7 @@ public final class KeyProtection implements ProtectionParameter { private int mUserAuthenticationValidityDurationSeconds = -1; private boolean mUserAuthenticationValidWhileOnBody; private boolean mInvalidatedByBiometricEnrollment = true; + private boolean mUserConfirmationRequired; private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID; private boolean mCriticalToDeviceEncryption = false; @@ -718,6 +742,29 @@ public final class KeyProtection implements ProtectionParameter { return this; } + /** + * Sets whether this key is authorized to be used only for messages confirmed by the + * user. + * + * Confirmation is separate from user authentication (see + * {@link #setUserAuthenticationRequired(boolean)}). Keys can be created that require + * confirmation but not user authentication, or user authentication but not confirmation, + * or both. Confirmation verifies that some user with physical possession of the device has + * approved a displayed message. User authentication verifies that the correct user is + * present and has authenticated. + * + *

This authorization applies only to secret key and private key operations. Public key + * operations are not restricted. + * + * @see {@link android.security.ConfirmationPrompter ConfirmationPrompter} class for + * more details about user confirmations. + */ + @NonNull + public Builder setUserConfirmationRequired(boolean required) { + mUserConfirmationRequired = required; + return this; + } + /** * Sets the duration of time (seconds) for which this key is authorized to be used after the * user is successfully authenticated. This has effect if the key requires user @@ -866,7 +913,8 @@ public final class KeyProtection implements ProtectionParameter { mUserAuthenticationValidWhileOnBody, mInvalidatedByBiometricEnrollment, mBoundToSecureUserId, - mCriticalToDeviceEncryption); + mCriticalToDeviceEncryption, + mUserConfirmationRequired); } } } diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java index 34c8d1f75f6..4e28601f17a 100644 --- a/keystore/java/android/security/keystore/KeymasterUtils.java +++ b/keystore/java/android/security/keystore/KeymasterUtils.java @@ -16,6 +16,7 @@ package android.security.keystore; +import android.util.Log; import android.hardware.fingerprint.FingerprintManager; import android.security.GateKeeper; import android.security.KeyStore; @@ -93,6 +94,8 @@ public abstract class KeymasterUtils { * overriding the default logic in this method where the key is bound to either the root * SID of the current user, or the fingerprint SID if explicit fingerprint authorization * is requested. + * @param userConfirmationRequired whether user confirmation is required to authorize the use + * of the key. * @throws IllegalStateException if user authentication is required but the system is in a wrong * state (e.g., secure lock screen not set up) for generating or importing keys that * require user authentication. @@ -102,7 +105,12 @@ public abstract class KeymasterUtils { int userAuthenticationValidityDurationSeconds, boolean userAuthenticationValidWhileOnBody, boolean invalidatedByBiometricEnrollment, - long boundToSpecificSecureUserId) { + long boundToSpecificSecureUserId, + boolean userConfirmationRequired) { + if (userConfirmationRequired) { + args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED); + } + if (!userAuthenticationRequired) { args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED); return; -- GitLab From 5564f880db3292327872a07df8e230eee78be14b Mon Sep 17 00:00:00 2001 From: Patrick Baumann Date: Tue, 30 Jan 2018 09:51:26 -0800 Subject: [PATCH 128/416] Removes EphemrealResolverService and related This change removes deprecated classes and constants that were not renamed from ephemeral to instant prior to O. There were no consumers of these APIs as correctly named alternatives existed and were referenced in docs. No known consumers of these APIs exist on user builds. Fixes: 38137176 Fixes: 38121489 Test: manual; builds and instant apps launch Change-Id: I982f8a6edc5668dd42cea65e52a1433ec8d6f8ef --- api/current.txt | 18 +- api/system-removed.txt | 50 ---- .../android/app/EphemeralResolverService.java | 104 -------- .../app/InstantAppResolverService.java | 10 +- core/java/android/content/Intent.java | 30 --- .../content/pm/EphemeralIntentFilter.java | 85 ------- .../content/pm/EphemeralResolveInfo.java | 224 ------------------ .../android/server/pm/InstantAppResolver.java | 28 +-- ...java => InstantAppResolverConnection.java} | 43 ++-- .../server/pm/PackageManagerService.java | 71 ++---- 10 files changed, 72 insertions(+), 591 deletions(-) delete mode 100644 core/java/android/app/EphemeralResolverService.java delete mode 100644 core/java/android/content/pm/EphemeralIntentFilter.java delete mode 100644 core/java/android/content/pm/EphemeralResolveInfo.java rename services/core/java/com/android/server/pm/{EphemeralResolverConnection.java => InstantAppResolverConnection.java} (91%) diff --git a/api/current.txt b/api/current.txt index 5998e7194a1..b4adfe252e4 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11608,15 +11608,15 @@ package android.content.res { public final class AssetManager implements java.lang.AutoCloseable { method public void close(); - method public java.lang.String[] getLocales(); - method public java.lang.String[] list(java.lang.String) throws java.io.IOException; - method public java.io.InputStream open(java.lang.String) throws java.io.IOException; - method public java.io.InputStream open(java.lang.String, int) throws java.io.IOException; - method public android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException; - method public android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException; - method public android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException; - method public android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException; - method public android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException; + method public final java.lang.String[] getLocales(); + method public final java.lang.String[] list(java.lang.String) throws java.io.IOException; + method public final java.io.InputStream open(java.lang.String) throws java.io.IOException; + method public final java.io.InputStream open(java.lang.String, int) throws java.io.IOException; + method public final android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException; + method public final android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException; + method public final android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException; + method public final android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException; + method public final android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException; field public static final int ACCESS_BUFFER = 3; // 0x3 field public static final int ACCESS_RANDOM = 1; // 0x1 field public static final int ACCESS_STREAMING = 2; // 0x2 diff --git a/api/system-removed.txt b/api/system-removed.txt index b63703dc003..48f43e0880d 100644 --- a/api/system-removed.txt +++ b/api/system-removed.txt @@ -1,13 +1,5 @@ package android.app { - public abstract deprecated class EphemeralResolverService extends android.app.InstantAppResolverService { - ctor public EphemeralResolverService(); - method public android.os.Looper getLooper(); - method public abstract deprecated java.util.List onEphemeralResolveInfoList(int[], int); - method public android.content.pm.EphemeralResolveInfo onGetEphemeralIntentFilter(java.lang.String); - method public java.util.List onGetEphemeralResolveInfo(int[]); - } - public class Notification implements android.os.Parcelable { method public static java.lang.Class getNotificationStyleClass(java.lang.String); } @@ -31,10 +23,7 @@ package android.content { public class Intent implements java.lang.Cloneable android.os.Parcelable { field public static final deprecated java.lang.String ACTION_DEVICE_INITIALIZATION_WIZARD = "android.intent.action.DEVICE_INITIALIZATION_WIZARD"; - field public static final deprecated java.lang.String ACTION_EPHEMERAL_RESOLVER_SETTINGS = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS"; - field public static final deprecated java.lang.String ACTION_INSTALL_EPHEMERAL_PACKAGE = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE"; field public static final deprecated java.lang.String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR"; - field public static final deprecated java.lang.String ACTION_RESOLVE_EPHEMERAL_PACKAGE = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE"; field public static final deprecated java.lang.String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE"; field public static final deprecated java.lang.String EXTRA_CDMA_DEFAULT_ROAMING_INDICATOR = "cdmaDefaultRoamingIndicator"; field public static final deprecated java.lang.String EXTRA_CDMA_ROAMING_INDICATOR = "cdmaRoamingIndicator"; @@ -62,45 +51,6 @@ package android.content { } -package android.content.pm { - - public final deprecated class EphemeralIntentFilter implements android.os.Parcelable { - ctor public EphemeralIntentFilter(java.lang.String, java.util.List); - method public int describeContents(); - method public java.util.List getFilters(); - method public java.lang.String getSplitName(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - - public final deprecated class EphemeralResolveInfo implements android.os.Parcelable { - ctor public deprecated EphemeralResolveInfo(android.net.Uri, java.lang.String, java.util.List); - ctor public deprecated EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List); - ctor public EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List, int); - ctor public EphemeralResolveInfo(java.lang.String, java.lang.String, java.util.List); - method public int describeContents(); - method public byte[] getDigestBytes(); - method public int getDigestPrefix(); - method public deprecated java.util.List getFilters(); - method public java.util.List getIntentFilters(); - method public java.lang.String getPackageName(); - method public int getVersionCode(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - field public static final java.lang.String SHA_ALGORITHM = "SHA-256"; - } - - public static final class EphemeralResolveInfo.EphemeralDigest implements android.os.Parcelable { - ctor public EphemeralResolveInfo.EphemeralDigest(java.lang.String); - method public int describeContents(); - method public byte[][] getDigestBytes(); - method public int[] getDigestPrefix(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - -} - package android.media.tv { public final class TvInputManager { diff --git a/core/java/android/app/EphemeralResolverService.java b/core/java/android/app/EphemeralResolverService.java deleted file mode 100644 index d1c244136ec..00000000000 --- a/core/java/android/app/EphemeralResolverService.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app; - -import android.annotation.SystemApi; -import android.content.pm.EphemeralResolveInfo; -import android.content.pm.InstantAppResolveInfo; -import android.os.Build; -import android.os.Looper; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Base class for implementing the resolver service. - * @hide - * @removed - * @deprecated use InstantAppResolverService instead - */ -@Deprecated -@SystemApi -public abstract class EphemeralResolverService extends InstantAppResolverService { - private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; - private static final String TAG = "PackageManager"; - - /** - * Called to retrieve resolve info for ephemeral applications. - * - * @param digestPrefix The hash prefix of the ephemeral's domain. - * @param prefixMask A mask that was applied to each digest prefix. This should - * be used when comparing against the digest prefixes as all bits might - * not be set. - * @deprecated use {@link #onGetEphemeralResolveInfo(int[])} instead - */ - @Deprecated - public abstract List onEphemeralResolveInfoList( - int digestPrefix[], int prefix); - - /** - * Called to retrieve resolve info for ephemeral applications. - * - * @param digestPrefix The hash prefix of the ephemeral's domain. - */ - public List onGetEphemeralResolveInfo(int digestPrefix[]) { - return onEphemeralResolveInfoList(digestPrefix, 0xFFFFF000); - } - - /** - * Called to retrieve intent filters for ephemeral applications. - * - * @param hostName The name of the host to get intent filters for. - */ - public EphemeralResolveInfo onGetEphemeralIntentFilter(String hostName) { - throw new IllegalStateException("Must define"); - } - - @Override - public Looper getLooper() { - return super.getLooper(); - } - - void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, - InstantAppResolutionCallback callback) { - if (DEBUG_EPHEMERAL) { - Log.d(TAG, "Legacy resolver; getInstantAppResolveInfo;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - final List response = onGetEphemeralResolveInfo(digestPrefix); - final int responseSize = response == null ? 0 : response.size(); - final List resultList = new ArrayList<>(responseSize); - for (int i = 0; i < responseSize; i++) { - resultList.add(response.get(i).getInstantAppResolveInfo()); - } - callback.onInstantAppResolveInfo(resultList); - } - - void _onGetInstantAppIntentFilter(int[] digestPrefix, String token, - String hostName, InstantAppResolutionCallback callback) { - if (DEBUG_EPHEMERAL) { - Log.d(TAG, "Legacy resolver; getInstantAppIntentFilter;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - final EphemeralResolveInfo response = onGetEphemeralIntentFilter(hostName); - final List resultList = new ArrayList<>(1); - resultList.add(response.getInstantAppResolveInfo()); - callback.onInstantAppResolveInfo(resultList); - } -} diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java index 89aff36f883..76a36820ed8 100644 --- a/core/java/android/app/InstantAppResolverService.java +++ b/core/java/android/app/InstantAppResolverService.java @@ -43,7 +43,7 @@ import java.util.List; */ @SystemApi public abstract class InstantAppResolverService extends Service { - private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; private static final String TAG = "PackageManager"; /** @hide */ @@ -133,7 +133,7 @@ public abstract class InstantAppResolverService extends Service { @Override public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix, String token, int sequence, IRemoteCallback callback) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "[" + token + "] Phase1 called; posting"); } final SomeArgs args = SomeArgs.obtain(); @@ -148,7 +148,7 @@ public abstract class InstantAppResolverService extends Service { @Override public void getInstantAppIntentFilterList(Intent sanitizedIntent, int[] digestPrefix, String token, IRemoteCallback callback) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "[" + token + "] Phase2 called; posting"); } final SomeArgs args = SomeArgs.obtain(); @@ -203,7 +203,7 @@ public abstract class InstantAppResolverService extends Service { final String token = (String) args.arg3; final Intent intent = (Intent) args.arg4; final int sequence = message.arg1; - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "[" + token + "] Phase1 request;" + " prefix: " + Arrays.toString(digestPrefix)); } @@ -217,7 +217,7 @@ public abstract class InstantAppResolverService extends Service { final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; final Intent intent = (Intent) args.arg4; - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "[" + token + "] Phase2 request;" + " prefix: " + Arrays.toString(digestPrefix)); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index b3c8737a283..908465efa0c 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1553,16 +1553,6 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_INSTALL_FAILURE = "android.intent.action.INSTALL_FAILURE"; - /** - * @hide - * @removed - * @deprecated Do not use. This will go away. - * Replace with {@link #ACTION_INSTALL_INSTANT_APP_PACKAGE}. - */ - @SystemApi - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE - = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE"; /** * Activity Action: Launch instant application installer. *

@@ -1576,16 +1566,6 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_INSTALL_INSTANT_APP_PACKAGE = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE"; - /** - * @hide - * @removed - * @deprecated Do not use. This will go away. - * Replace with {@link #ACTION_RESOLVE_INSTANT_APP_PACKAGE}. - */ - @SystemApi - @SdkConstant(SdkConstantType.SERVICE_ACTION) - public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE - = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE"; /** * Service Action: Resolve instant application. *

@@ -1600,16 +1580,6 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE"; - /** - * @hide - * @removed - * @deprecated Do not use. This will go away. - * Replace with {@link #ACTION_INSTANT_APP_RESOLVER_SETTINGS}. - */ - @SystemApi - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_EPHEMERAL_RESOLVER_SETTINGS - = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS"; /** * Activity Action: Launch instant app settings. * diff --git a/core/java/android/content/pm/EphemeralIntentFilter.java b/core/java/android/content/pm/EphemeralIntentFilter.java deleted file mode 100644 index 1dbbf816ed9..00000000000 --- a/core/java/android/content/pm/EphemeralIntentFilter.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.pm; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.content.IntentFilter; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; - -/** - * Information about an ephemeral application intent filter. - * @hide - * @removed - */ -@Deprecated -@SystemApi -public final class EphemeralIntentFilter implements Parcelable { - private final InstantAppIntentFilter mInstantAppIntentFilter; - - public EphemeralIntentFilter(@Nullable String splitName, @NonNull List filters) { - mInstantAppIntentFilter = new InstantAppIntentFilter(splitName, filters); - } - - EphemeralIntentFilter(@NonNull InstantAppIntentFilter intentFilter) { - mInstantAppIntentFilter = intentFilter; - } - - EphemeralIntentFilter(Parcel in) { - mInstantAppIntentFilter = in.readParcelable(null /*loader*/); - } - - public String getSplitName() { - return mInstantAppIntentFilter.getSplitName(); - } - - public List getFilters() { - return mInstantAppIntentFilter.getFilters(); - } - - /** @hide */ - InstantAppIntentFilter getInstantAppIntentFilter() { - return mInstantAppIntentFilter; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mInstantAppIntentFilter, flags); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public EphemeralIntentFilter createFromParcel(Parcel in) { - return new EphemeralIntentFilter(in); - } - @Override - public EphemeralIntentFilter[] newArray(int size) { - return new EphemeralIntentFilter[size]; - } - }; -} diff --git a/core/java/android/content/pm/EphemeralResolveInfo.java b/core/java/android/content/pm/EphemeralResolveInfo.java deleted file mode 100644 index 12131a3ebc9..00000000000 --- a/core/java/android/content/pm/EphemeralResolveInfo.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.pm; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.content.IntentFilter; -import android.content.pm.InstantAppResolveInfo.InstantAppDigest; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -/** - * Information about an ephemeral application. - * @hide - * @removed - */ -@Deprecated -@SystemApi -public final class EphemeralResolveInfo implements Parcelable { - /** Algorithm that will be used to generate the domain digest */ - public static final String SHA_ALGORITHM = "SHA-256"; - - private final InstantAppResolveInfo mInstantAppResolveInfo; - @Deprecated - private final List mLegacyFilters; - - @Deprecated - public EphemeralResolveInfo(@NonNull Uri uri, @NonNull String packageName, - @NonNull List filters) { - if (uri == null || packageName == null || filters == null || filters.isEmpty()) { - throw new IllegalArgumentException(); - } - final List ephemeralFilters = new ArrayList<>(1); - ephemeralFilters.add(new EphemeralIntentFilter(packageName, filters)); - mInstantAppResolveInfo = new InstantAppResolveInfo(uri.getHost(), packageName, - createInstantAppIntentFilterList(ephemeralFilters)); - mLegacyFilters = new ArrayList(filters.size()); - mLegacyFilters.addAll(filters); - } - - @Deprecated - public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName, - @Nullable List filters) { - this(digest, packageName, filters, -1 /*versionCode*/); - } - - public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName, - @Nullable List filters, int versionCode) { - mInstantAppResolveInfo = new InstantAppResolveInfo( - digest.getInstantAppDigest(), packageName, - createInstantAppIntentFilterList(filters), versionCode); - mLegacyFilters = null; - } - - public EphemeralResolveInfo(@NonNull String hostName, @Nullable String packageName, - @Nullable List filters) { - this(new EphemeralDigest(hostName), packageName, filters); - } - - EphemeralResolveInfo(Parcel in) { - mInstantAppResolveInfo = in.readParcelable(null /*loader*/); - mLegacyFilters = new ArrayList(); - in.readList(mLegacyFilters, null /*loader*/); - } - - /** @hide */ - public InstantAppResolveInfo getInstantAppResolveInfo() { - return mInstantAppResolveInfo; - } - - private static List createInstantAppIntentFilterList( - List filters) { - if (filters == null) { - return null; - } - final int filterCount = filters.size(); - final List returnList = new ArrayList<>(filterCount); - for (int i = 0; i < filterCount; i++) { - returnList.add(filters.get(i).getInstantAppIntentFilter()); - } - return returnList; - } - - public byte[] getDigestBytes() { - return mInstantAppResolveInfo.getDigestBytes(); - } - - public int getDigestPrefix() { - return mInstantAppResolveInfo.getDigestPrefix(); - } - - public String getPackageName() { - return mInstantAppResolveInfo.getPackageName(); - } - - public List getIntentFilters() { - final List filters = mInstantAppResolveInfo.getIntentFilters(); - final int filterCount = filters.size(); - final List returnList = new ArrayList<>(filterCount); - for (int i = 0; i < filterCount; i++) { - returnList.add(new EphemeralIntentFilter(filters.get(i))); - } - return returnList; - } - - public int getVersionCode() { - return mInstantAppResolveInfo.getVersionCode(); - } - - @Deprecated - public List getFilters() { - return mLegacyFilters; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mInstantAppResolveInfo, flags); - out.writeList(mLegacyFilters); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public EphemeralResolveInfo createFromParcel(Parcel in) { - return new EphemeralResolveInfo(in); - } - @Override - public EphemeralResolveInfo[] newArray(int size) { - return new EphemeralResolveInfo[size]; - } - }; - - /** - * Helper class to generate and store each of the digests and prefixes - * sent to the Ephemeral Resolver. - *

- * Since intent filters may want to handle multiple hosts within a - * domain [eg “*.google.com”], the resolver is presented with multiple - * hash prefixes. For example, "a.b.c.d.e" generates digests for - * "d.e", "c.d.e", "b.c.d.e" and "a.b.c.d.e". - * - * @hide - */ - @SystemApi - public static final class EphemeralDigest implements Parcelable { - private final InstantAppDigest mInstantAppDigest; - - public EphemeralDigest(@NonNull String hostName) { - this(hostName, -1 /*maxDigests*/); - } - - /** @hide */ - public EphemeralDigest(@NonNull String hostName, int maxDigests) { - mInstantAppDigest = new InstantAppDigest(hostName, maxDigests); - } - - EphemeralDigest(Parcel in) { - mInstantAppDigest = in.readParcelable(null /*loader*/); - } - - /** @hide */ - InstantAppDigest getInstantAppDigest() { - return mInstantAppDigest; - } - - public byte[][] getDigestBytes() { - return mInstantAppDigest.getDigestBytes(); - } - - public int[] getDigestPrefix() { - return mInstantAppDigest.getDigestPrefix(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mInstantAppDigest, flags); - } - - @SuppressWarnings("hiding") - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public EphemeralDigest createFromParcel(Parcel in) { - return new EphemeralDigest(in); - } - @Override - public EphemeralDigest[] newArray(int size) { - return new EphemeralDigest[size]; - } - }; - } -} diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index 55212cc6b3d..00fdb9d687d 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -51,8 +51,8 @@ import android.util.Slog; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; -import com.android.server.pm.EphemeralResolverConnection.ConnectionException; -import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback; +import com.android.server.pm.InstantAppResolverConnection.ConnectionException; +import com.android.server.pm.InstantAppResolverConnection.PhaseTwoCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -66,7 +66,7 @@ import java.util.UUID; /** @hide */ public abstract class InstantAppResolver { - private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; private static final String TAG = "PackageManager"; private static final int RESOLUTION_SUCCESS = 0; @@ -117,10 +117,10 @@ public abstract class InstantAppResolver { } public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne( - EphemeralResolverConnection connection, InstantAppRequest requestObj) { + InstantAppResolverConnection connection, InstantAppRequest requestObj) { final long startTime = System.currentTimeMillis(); final String token = UUID.randomUUID().toString(); - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Log.d(TAG, "[" + token + "] Phase1; resolving"); } final Intent origIntent = requestObj.origIntent; @@ -152,7 +152,7 @@ public abstract class InstantAppResolver { logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token, resolutionStatus); } - if (DEBUG_EPHEMERAL && resolveInfo == null) { + if (DEBUG_INSTANT && resolveInfo == null) { if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) { Log.d(TAG, "[" + token + "] Phase1; bind timed out"); } else if (resolutionStatus == RESOLUTION_CALL_TIMEOUT) { @@ -173,11 +173,11 @@ public abstract class InstantAppResolver { } public static void doInstantAppResolutionPhaseTwo(Context context, - EphemeralResolverConnection connection, InstantAppRequest requestObj, + InstantAppResolverConnection connection, InstantAppRequest requestObj, ActivityInfo instantAppInstaller, Handler callbackHandler) { final long startTime = System.currentTimeMillis(); final String token = requestObj.responseObj.token; - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Log.d(TAG, "[" + token + "] Phase2; resolving"); } final Intent origIntent = requestObj.origIntent; @@ -234,7 +234,7 @@ public abstract class InstantAppResolver { } logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token, resolutionStatus); - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) { Log.d(TAG, "[" + token + "] Phase2; bind timed out"); } else { @@ -444,13 +444,13 @@ public abstract class InstantAppResolver { return null; } // No filters; we need to start phase two - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Log.d(TAG, "No app filters; go to phase 2"); } return Collections.emptyList(); } - final PackageManagerService.EphemeralIntentResolver instantAppResolver = - new PackageManagerService.EphemeralIntentResolver(); + final PackageManagerService.InstantAppIntentResolver instantAppResolver = + new PackageManagerService.InstantAppIntentResolver(); for (int j = instantAppFilters.size() - 1; j >= 0; --j) { final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); final List splitFilters = instantAppFilter.getFilters(); @@ -481,11 +481,11 @@ public abstract class InstantAppResolver { instantAppResolver.queryIntent( origIntent, resolvedType, false /*defaultOnly*/, userId); if (!matchedResolveInfoList.isEmpty()) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList); } return matchedResolveInfoList; - } else if (DEBUG_EPHEMERAL) { + } else if (DEBUG_INSTANT) { Log.d(TAG, "[" + token + "] No matches found" + " package: " + instantAppInfo.getPackageName() + ", versionCode: " + instantAppInfo.getVersionCode()); diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java similarity index 91% rename from services/core/java/com/android/server/pm/EphemeralResolverConnection.java rename to services/core/java/com/android/server/pm/InstantAppResolverConnection.java index 6d6c960eed7..a9ee41162ae 100644 --- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java +++ b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java @@ -37,34 +37,29 @@ import android.util.Slog; import android.util.TimedRemoteCaller; import com.android.internal.annotations.GuardedBy; -import com.android.internal.os.TransferPipe; -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.TimeoutException; /** - * Represents a remote ephemeral resolver. It is responsible for binding to the remote + * Represents a remote instant app resolver. It is responsible for binding to the remote * service and handling all interactions in a timely manner. * @hide */ -final class EphemeralResolverConnection implements DeathRecipient { +final class InstantAppResolverConnection implements DeathRecipient { private static final String TAG = "PackageManager"; // This is running in a critical section and the timeout must be sufficiently low private static final long BIND_SERVICE_TIMEOUT_MS = Build.IS_ENG ? 500 : 300; private static final long CALL_SERVICE_TIMEOUT_MS = Build.IS_ENG ? 200 : 100; - private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; private final Object mLock = new Object(); - private final GetEphemeralResolveInfoCaller mGetEphemeralResolveInfoCaller = - new GetEphemeralResolveInfoCaller(); + private final GetInstantAppResolveInfoCaller mGetInstantAppResolveInfoCaller = + new GetInstantAppResolveInfoCaller(); private final ServiceConnection mServiceConnection = new MyServiceConnection(); private final Context mContext; /** Intent used to bind to the service */ @@ -79,7 +74,7 @@ final class EphemeralResolverConnection implements DeathRecipient { @GuardedBy("mLock") private IInstantAppResolver mRemoteInstance; - public EphemeralResolverConnection( + public InstantAppResolverConnection( Context context, ComponentName componentName, String action) { mContext = context; mIntent = new Intent(action).setComponent(componentName); @@ -98,8 +93,8 @@ final class EphemeralResolverConnection implements DeathRecipient { throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED); } try { - return mGetEphemeralResolveInfoCaller - .getEphemeralResolveInfoList(target, sanitizedIntent, hashPrefix, token); + return mGetInstantAppResolveInfoCaller + .getInstantAppResolveInfoList(target, sanitizedIntent, hashPrefix, token); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_CALL); } catch (RemoteException ignore) { @@ -171,7 +166,7 @@ final class EphemeralResolverConnection implements DeathRecipient { if (mBindState == STATE_PENDING) { // there is a pending bind, let's see if we can use it. - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.i(TAG, "[" + token + "] Previous bind timed out; waiting for connection"); } try { @@ -188,7 +183,7 @@ final class EphemeralResolverConnection implements DeathRecipient { if (mBindState == STATE_BINDING) { // someone was binding when we called bind(), or they raced ahead while we were // waiting in the PENDING case; wait for their result instead. Last chance! - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.i(TAG, "[" + token + "] Another thread is binding; waiting for connection"); } waitForBindLocked(token); @@ -206,12 +201,12 @@ final class EphemeralResolverConnection implements DeathRecipient { IInstantAppResolver instance = null; try { if (doUnbind) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.i(TAG, "[" + token + "] Previous connection never established; rebinding"); } mContext.unbindService(mServiceConnection); } - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "[" + token + "] Binding to instant app resolver"); } final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; @@ -247,7 +242,7 @@ final class EphemeralResolverConnection implements DeathRecipient { @Override public void binderDied() { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Binder to instant app resolver died"); } synchronized (mLock) { @@ -286,7 +281,7 @@ final class EphemeralResolverConnection implements DeathRecipient { private final class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Connected to instant app resolver"); } synchronized (mLock) { @@ -295,7 +290,7 @@ final class EphemeralResolverConnection implements DeathRecipient { mBindState = STATE_IDLE; } try { - service.linkToDeath(EphemeralResolverConnection.this, 0 /*flags*/); + service.linkToDeath(InstantAppResolverConnection.this, 0 /*flags*/); } catch (RemoteException e) { handleBinderDiedLocked(); } @@ -305,7 +300,7 @@ final class EphemeralResolverConnection implements DeathRecipient { @Override public void onServiceDisconnected(ComponentName name) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Disconnected from instant app resolver"); } synchronized (mLock) { @@ -314,11 +309,11 @@ final class EphemeralResolverConnection implements DeathRecipient { } } - private static final class GetEphemeralResolveInfoCaller + private static final class GetInstantAppResolveInfoCaller extends TimedRemoteCaller> { private final IRemoteCallback mCallback; - public GetEphemeralResolveInfoCaller() { + public GetInstantAppResolveInfoCaller() { super(CALL_SERVICE_TIMEOUT_MS); mCallback = new IRemoteCallback.Stub() { @Override @@ -333,7 +328,7 @@ final class EphemeralResolverConnection implements DeathRecipient { }; } - public List getEphemeralResolveInfoList( + public List getInstantAppResolveInfoList( IInstantAppResolver target, Intent sanitizedIntent, int hashPrefix[], String token) throws RemoteException, TimeoutException { final int sequence = onBeforeRemoteCall(); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6402b5d2e4b..61569ae8c02 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -424,7 +424,7 @@ public class PackageManagerService extends IPackageManager.Stub public static final boolean DEBUG_DEXOPT = false; private static final boolean DEBUG_ABI_SELECTION = false; - private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; private static final boolean DEBUG_TRIAGED_MISSING = false; private static final boolean DEBUG_APP_DATA = false; @@ -981,7 +981,7 @@ public class PackageManagerService extends IPackageManager.Stub private int mIntentFilterVerificationToken = 0; /** The service connection to the ephemeral resolver */ - final EphemeralResolverConnection mInstantAppResolverConnection; + final InstantAppResolverConnection mInstantAppResolverConnection; /** Component used to show resolver settings for Instant Apps */ final ComponentName mInstantAppResolverSettingsComponent; @@ -3149,10 +3149,10 @@ Slog.e("TODD", final Pair instantAppResolverComponent = getInstantAppResolverLPr(); if (instantAppResolverComponent != null) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Set ephemeral resolver: " + instantAppResolverComponent); } - mInstantAppResolverConnection = new EphemeralResolverConnection( + mInstantAppResolverConnection = new InstantAppResolverConnection( mContext, instantAppResolverComponent.first, instantAppResolverComponent.second); mInstantAppResolverSettingsComponent = @@ -3532,7 +3532,7 @@ Slog.e("TODD", final String[] packageArray = mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage); if (packageArray.length == 0 && !Build.IS_DEBUGGABLE) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Ephemeral resolver NOT found; empty package list"); } return null; @@ -3547,19 +3547,9 @@ Slog.e("TODD", final Intent resolverIntent = new Intent(actionName); List resolvers = queryIntentServicesInternal(resolverIntent, null, resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/); - // temporarily look for the old action - if (resolvers.size() == 0) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "Ephemeral resolver not found with new action; try old one"); - } - actionName = Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE; - resolverIntent.setAction(actionName); - resolvers = queryIntentServicesInternal(resolverIntent, null, - resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/); - } final int N = resolvers.size(); if (N == 0) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Ephemeral resolver NOT found; no matching intent filters"); } return null; @@ -3575,20 +3565,20 @@ Slog.e("TODD", final String packageName = info.serviceInfo.packageName; if (!possiblePackages.contains(packageName) && !Build.IS_DEBUGGABLE) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Ephemeral resolver not in allowed package list;" + " pkg: " + packageName + ", info:" + info); } continue; } - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Ephemeral resolver found;" + " pkg: " + packageName + ", info:" + info); } return new Pair<>(new ComponentName(packageName, info.serviceInfo.name), actionName); } - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Ephemeral resolver NOT found"); } return null; @@ -3598,11 +3588,9 @@ Slog.e("TODD", String[] orderedActions = Build.IS_ENG ? new String[]{ Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE + "_TEST", - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, - Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE} + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE} : new String[]{ - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, - Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE}; + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE}; final int resolveFlags = MATCH_DIRECT_BOOT_AWARE @@ -3618,7 +3606,7 @@ Slog.e("TODD", matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, resolveFlags, UserHandle.USER_SYSTEM); if (matches.isEmpty()) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Instant App installer not found with " + action); } } else { @@ -3656,15 +3644,6 @@ Slog.e("TODD", final int resolveFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; List matches = queryIntentActivitiesInternal(intent, null, resolveFlags, UserHandle.USER_SYSTEM); - // temporarily look for the old action - if (matches.isEmpty()) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "Ephemeral resolver settings not found with new action; try old one"); - } - intent.setAction(Intent.ACTION_EPHEMERAL_RESOLVER_SETTINGS); - matches = queryIntentActivitiesInternal(intent, null, resolveFlags, - UserHandle.USER_SYSTEM); - } if (matches.isEmpty()) { return null; } @@ -6007,7 +5986,7 @@ Slog.e("TODD", final int status = (int) (packedStatus >> 32); if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName + ", status: " + status); } @@ -6015,7 +5994,7 @@ Slog.e("TODD", } } if (ps.getInstantApp(userId)) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "DENY instant app installed;" + " pkg: " + packageName); } @@ -6651,7 +6630,7 @@ Slog.e("TODD", // there's a local instant application installed, but, the user has // chosen to never use it; skip resolution and don't acknowledge // an instant application is even available - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Instant app marked to never run; pkg: " + packageName); } blockResolution = true; @@ -6659,7 +6638,7 @@ Slog.e("TODD", } else { // we have a locally installed instant application; skip resolution // but acknowledge there's an instant application available - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Found installed instant app; pkg: " + packageName); } localInstantApp = info; @@ -6717,7 +6696,7 @@ Slog.e("TODD", // make sure this resolver is the default ephemeralInstaller.isDefault = true; ephemeralInstaller.auxiliaryInfo = auxiliaryResponse; - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } @@ -7604,7 +7583,7 @@ Slog.e("TODD", info.serviceInfo.splitName)) { // requested service is defined in a split that hasn't been installed yet. // add the installer to the resolve list - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } final ResolveInfo installerInfo = new ResolveInfo( @@ -7726,7 +7705,7 @@ Slog.e("TODD", info.providerInfo.splitName)) { // requested provider is defined in a split that hasn't been installed yet. // add the installer to the resolve list - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } final ResolveInfo installerInfo = new ResolveInfo( @@ -11768,14 +11747,14 @@ Slog.e("TODD", private void setUpInstantAppInstallerActivityLP(ActivityInfo installerActivity) { if (installerActivity == null) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Clear ephemeral installer activity"); } mInstantAppInstallerActivity = null; return; } - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Set ephemeral installer activity: " + installerActivity.getComponentName()); } @@ -13235,7 +13214,7 @@ Slog.e("TODD", private int mFlags; } - static final class EphemeralIntentResolver + static final class InstantAppIntentResolver extends IntentResolver { /** @@ -13605,7 +13584,7 @@ Slog.e("TODD", IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, String installerPackageName, int installerUid, UserHandle user, PackageParser.SigningDetails signingDetails) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { Slog.d(TAG, "Ephemeral install of " + packageName); } @@ -15089,7 +15068,7 @@ Slog.e("TODD", pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags, packageAbiOverride); - if (DEBUG_EPHEMERAL && ephemeral) { + if (DEBUG_INSTANT && ephemeral) { Slog.v(TAG, "pkgLite for install: " + pkgLite); } @@ -15156,7 +15135,7 @@ Slog.e("TODD", installFlags |= PackageManager.INSTALL_EXTERNAL; installFlags &= ~PackageManager.INSTALL_INTERNAL; } else if (loc == PackageHelper.RECOMMEND_INSTALL_EPHEMERAL) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "...setting INSTALL_EPHEMERAL install flag"); } installFlags |= PackageManager.INSTALL_INSTANT_APP; -- GitLab From 24e9be8bc738bf4e36977a792df5c1606e83a6b8 Mon Sep 17 00:00:00 2001 From: Dmitry Dementyev Date: Mon, 29 Jan 2018 15:16:40 -0800 Subject: [PATCH 129/416] Add PersistentKeyChainSnapshot serialization/deserialization methods. Unlike Parcelables, Byte array produced by the class can be safely stored in the database. Bug: 71804644 Test: adb shell am instrument -w -e package com.android.server.locksettings.recoverablekeystore com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner Change-Id: I826a0cc4d7dc33ff1a062374a4fc8471db8e2f34 --- .../storage/PersistentKeyChainSnapshot.java | 298 ++++++++++++++++ .../PersistentKeyChainSnapshotTest.java | 335 ++++++++++++++++++ 2 files changed, 633 insertions(+) create mode 100644 services/core/java/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshot.java create mode 100644 services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshotTest.java diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshot.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshot.java new file mode 100644 index 00000000000..52381b8f87d --- /dev/null +++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshot.java @@ -0,0 +1,298 @@ +/* + * 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.locksettings.recoverablekeystore.storage; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.security.keystore.recovery.KeyChainProtectionParams; +import android.security.keystore.recovery.KeyChainSnapshot; +import android.security.keystore.recovery.KeyDerivationParams; +import android.security.keystore.recovery.WrappedApplicationKey; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides helper methods serialize and deserialize {@link KeyChainSnapshot}. + * + *

It is necessary since {@link android.os.Parcelable} is not designed for persistent storage. + * + *

For every list, length is stored before the elements. + * + */ +public class PersistentKeyChainSnapshot { + private static final int VERSION = 1; + private static final int NULL_LIST_LENGTH = -1; + + private DataInputStream mInput; + private DataOutputStream mOut; + private ByteArrayOutputStream mOutStream; + + @VisibleForTesting + PersistentKeyChainSnapshot() { + } + + @VisibleForTesting + void initReader(byte[] input) { + mInput = new DataInputStream(new ByteArrayInputStream(input)); + } + + @VisibleForTesting + void initWriter() { + mOutStream = new ByteArrayOutputStream(); + mOut = new DataOutputStream(mOutStream); + } + + @VisibleForTesting + byte[] getOutput() { + return mOutStream.toByteArray(); + } + + /** + * Converts {@link KeyChainSnapshot} to its binary representation. + * + * @param snapshot The snapshot. + * + * @throws IOException if serialization failed. + */ + public static byte[] serialize(@NonNull KeyChainSnapshot snapshot) throws IOException { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + writer.writeInt(VERSION); + writer.writeKeyChainSnapshot(snapshot); + return writer.getOutput(); + } + + /** + * deserializes {@link KeyChainSnapshot}. + * + * @input input - byte array produced by {@link serialize} method. + * @throws IOException if parsing failed. + */ + public static @NonNull KeyChainSnapshot deserialize(@NonNull byte[] input) + throws IOException { + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(input); + try { + int version = reader.readInt(); + if (version != VERSION) { + throw new IOException("Unsupported version " + version); + } + return reader.readKeyChainSnapshot(); + } catch (IOException e) { + throw new IOException("Malformed KeyChainSnapshot", e); + } + } + + /** + * Must be in sync with {@link KeyChainSnapshot.writeToParcel} + */ + @VisibleForTesting + void writeKeyChainSnapshot(KeyChainSnapshot snapshot) throws IOException { + writeInt(snapshot.getSnapshotVersion()); + writeProtectionParamsList(snapshot.getKeyChainProtectionParams()); + writeBytes(snapshot.getEncryptedRecoveryKeyBlob()); + writeKeysList(snapshot.getWrappedApplicationKeys()); + + writeInt(snapshot.getMaxAttempts()); + writeLong(snapshot.getCounterId()); + writeBytes(snapshot.getServerParams()); + writeBytes(snapshot.getTrustedHardwarePublicKey()); + } + + @VisibleForTesting + KeyChainSnapshot readKeyChainSnapshot() throws IOException { + int snapshotVersion = readInt(); + List protectionParams = readProtectionParamsList(); + byte[] encryptedRecoveryKey = readBytes(); + List keysList = readKeysList(); + + int maxAttempts = readInt(); + long conterId = readLong(); + byte[] serverParams = readBytes(); + byte[] trustedHardwarePublicKey = readBytes(); + + return new KeyChainSnapshot.Builder() + .setSnapshotVersion(snapshotVersion) + .setKeyChainProtectionParams(protectionParams) + .setEncryptedRecoveryKeyBlob(encryptedRecoveryKey) + .setWrappedApplicationKeys(keysList) + .setMaxAttempts(maxAttempts) + .setCounterId(conterId) + .setServerParams(serverParams) + .setTrustedHardwarePublicKey(trustedHardwarePublicKey) + .build(); + } + + @VisibleForTesting + void writeProtectionParamsList( + @NonNull List ProtectionParamsList) throws IOException { + writeInt(ProtectionParamsList.size()); + for (KeyChainProtectionParams protectionParams : ProtectionParamsList) { + writeProtectionParams(protectionParams); + } + } + + @VisibleForTesting + List readProtectionParamsList() throws IOException { + int length = readInt(); + List result = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + result.add(readProtectionParams()); + } + return result; + } + + /** + * Must be in sync with {@link KeyChainProtectionParams.writeToParcel} + */ + @VisibleForTesting + void writeProtectionParams(@NonNull KeyChainProtectionParams protectionParams) + throws IOException { + if (!ArrayUtils.isEmpty(protectionParams.getSecret())) { + // Extra security check. + throw new RuntimeException("User generated secret should not be stored"); + } + writeInt(protectionParams.getUserSecretType()); + writeInt(protectionParams.getLockScreenUiFormat()); + writeKeyDerivationParams(protectionParams.getKeyDerivationParams()); + writeBytes(protectionParams.getSecret()); + } + + @VisibleForTesting + KeyChainProtectionParams readProtectionParams() throws IOException { + int userSecretType = readInt(); + int lockScreenUiFormat = readInt(); + KeyDerivationParams derivationParams = readKeyDerivationParams(); + byte[] secret = readBytes(); + return new KeyChainProtectionParams.Builder() + .setUserSecretType(userSecretType) + .setLockScreenUiFormat(lockScreenUiFormat) + .setKeyDerivationParams(derivationParams) + .setSecret(secret) + .build(); + } + + /** + * Must be in sync with {@link KeyDerivationParams.writeToParcel} + */ + @VisibleForTesting + void writeKeyDerivationParams(@NonNull KeyDerivationParams Params) throws IOException { + writeInt(Params.getAlgorithm()); + writeBytes(Params.getSalt()); + } + + @VisibleForTesting + KeyDerivationParams readKeyDerivationParams() throws IOException { + int algorithm = readInt(); + byte[] salt = readBytes(); + return KeyDerivationParams.createSha256Params(salt); + } + + @VisibleForTesting + void writeKeysList(@NonNull List applicationKeys) throws IOException { + writeInt(applicationKeys.size()); + for (WrappedApplicationKey keyEntry : applicationKeys) { + writeKeyEntry(keyEntry); + } + } + + @VisibleForTesting + List readKeysList() throws IOException { + int length = readInt(); + List result = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + result.add(readKeyEntry()); + } + return result; + } + + /** + * Must be in sync with {@link WrappedApplicationKey.writeToParcel} + */ + @VisibleForTesting + void writeKeyEntry(@NonNull WrappedApplicationKey keyEntry) throws IOException { + mOut.writeUTF(keyEntry.getAlias()); + writeBytes(keyEntry.getEncryptedKeyMaterial()); + writeBytes(keyEntry.getAccount()); + } + + @VisibleForTesting + WrappedApplicationKey readKeyEntry() throws IOException { + String alias = mInput.readUTF(); + byte[] keyMaterial = readBytes(); + byte[] account = readBytes(); + return new WrappedApplicationKey.Builder() + .setAlias(alias) + .setEncryptedKeyMaterial(keyMaterial) + .setAccount(account) + .build(); + } + + @VisibleForTesting + void writeInt(int value) throws IOException { + mOut.writeInt(value); + } + + @VisibleForTesting + int readInt() throws IOException { + return mInput.readInt(); + } + + @VisibleForTesting + void writeLong(long value) throws IOException { + mOut.writeLong(value); + } + + @VisibleForTesting + long readLong() throws IOException { + return mInput.readLong(); + } + + @VisibleForTesting + void writeBytes(@Nullable byte[] value) throws IOException { + if (value == null) { + writeInt(NULL_LIST_LENGTH); + return; + } + writeInt(value.length); + mOut.write(value, 0, value.length); + } + + /** + * Reads @code{byte[]} from current position. Converts {@code null} to an empty array. + */ + @VisibleForTesting + @NonNull byte[] readBytes() throws IOException { + int length = readInt(); + if (length == NULL_LIST_LENGTH) { + return new byte[]{}; + } + byte[] result = new byte[length]; + mInput.read(result, 0, result.length); + return result; + } +} + diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshotTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshotTest.java new file mode 100644 index 00000000000..aad5295abd7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/PersistentKeyChainSnapshotTest.java @@ -0,0 +1,335 @@ +/* + * 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.locksettings.recoverablekeystore.storage; + +import static com.google.common.truth.Truth.assertThat; +import static org.testng.Assert.assertThrows; + +import android.security.keystore.recovery.KeyDerivationParams; +import android.security.keystore.recovery.WrappedApplicationKey; +import android.security.keystore.recovery.KeyChainSnapshot; +import android.security.keystore.recovery.KeyChainProtectionParams; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class PersistentKeyChainSnapshotTest { + + private static final String ALIAS = "some_key"; + private static final String ALIAS2 = "another_key"; + private static final byte[] RECOVERY_KEY_MATERIAL = "recovery_key_data" + .getBytes(StandardCharsets.UTF_8); + private static final byte[] KEY_MATERIAL = "app_key_data".getBytes(StandardCharsets.UTF_8); + private static final byte[] PUBLIC_KEY = "public_key_data".getBytes(StandardCharsets.UTF_8); + private static final byte[] ACCOUNT = "test_account".getBytes(StandardCharsets.UTF_8); + private static final byte[] SALT = "salt".getBytes(StandardCharsets.UTF_8); + private static final int SNAPSHOT_VERSION = 2; + private static final int MAX_ATTEMPTS = 10; + private static final long COUNTER_ID = 123456789L; + private static final byte[] SERVER_PARAMS = "server_params".getBytes(StandardCharsets.UTF_8); + private static final byte[] ZERO_BYTES = new byte[0]; + private static final byte[] ONE_BYTE = new byte[]{(byte) 11}; + private static final byte[] TWO_BYTES = new byte[]{(byte) 222,(byte) 222}; + + @Test + public void testWriteInt() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + writer.writeInt(Integer.MIN_VALUE); + writer.writeInt(Integer.MAX_VALUE); + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + assertThat(reader.readInt()).isEqualTo(Integer.MIN_VALUE); + assertThat(reader.readInt()).isEqualTo(Integer.MAX_VALUE); + + assertThrows( + IOException.class, + () -> reader.readInt()); + } + + @Test + public void testWriteLong() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + writer.writeLong(Long.MIN_VALUE); + writer.writeLong(Long.MAX_VALUE); + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + assertThat(reader.readLong()).isEqualTo(Long.MIN_VALUE); + assertThat(reader.readLong()).isEqualTo(Long.MAX_VALUE); + + assertThrows( + IOException.class, + () -> reader.readLong()); + } + + @Test + public void testWriteBytes() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + writer.writeBytes(ZERO_BYTES); + writer.writeBytes(ONE_BYTE); + writer.writeBytes(TWO_BYTES); + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + assertThat(reader.readBytes()).isEqualTo(ZERO_BYTES); + assertThat(reader.readBytes()).isEqualTo(ONE_BYTE); + assertThat(reader.readBytes()).isEqualTo(TWO_BYTES); + + assertThrows( + IOException.class, + () -> reader.readBytes()); + } + + @Test + public void testReadBytes_returnsNullArrayAsEmpty() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + writer.writeBytes(null); + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + assertThat(reader.readBytes()).isEqualTo(new byte[]{}); // null -> empty array + } + + @Test + public void testWriteKeyEntry() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + WrappedApplicationKey entry = new WrappedApplicationKey.Builder() + .setAlias(ALIAS) + .setEncryptedKeyMaterial(KEY_MATERIAL) + .setAccount(ACCOUNT) + .build(); + writer.writeKeyEntry(entry); + + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + + WrappedApplicationKey copy = reader.readKeyEntry(); + assertThat(copy.getAlias()).isEqualTo(ALIAS); + assertThat(copy.getEncryptedKeyMaterial()).isEqualTo(KEY_MATERIAL); + assertThat(copy.getAccount()).isEqualTo(ACCOUNT); + + assertThrows( + IOException.class, + () -> reader.readKeyEntry()); + } + + public void testWriteProtectionParams() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + KeyDerivationParams derivationParams = KeyDerivationParams.createSha256Params(SALT); + KeyChainProtectionParams protectionParams = new KeyChainProtectionParams.Builder() + .setUserSecretType(1) + .setLockScreenUiFormat(2) + .setKeyDerivationParams(derivationParams) + .build(); + writer.writeProtectionParams(protectionParams); + + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + + KeyChainProtectionParams copy = reader.readProtectionParams(); + assertThat(copy.getUserSecretType()).isEqualTo(1); + assertThat(copy.getLockScreenUiFormat()).isEqualTo(2); + assertThat(copy.getKeyDerivationParams().getSalt()).isEqualTo(SALT); + + assertThrows( + IOException.class, + () -> reader.readProtectionParams()); + } + + public void testKeyChainSnapshot() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + + KeyDerivationParams derivationParams = KeyDerivationParams.createSha256Params(SALT); + + ArrayList protectionParamsList = new ArrayList<>(); + protectionParamsList.add(new KeyChainProtectionParams.Builder() + .setUserSecretType(1) + .setLockScreenUiFormat(2) + .setKeyDerivationParams(derivationParams) + .build()); + + ArrayList appKeysList = new ArrayList<>(); + appKeysList.add(new WrappedApplicationKey.Builder() + .setAlias(ALIAS) + .setEncryptedKeyMaterial(KEY_MATERIAL) + .setAccount(ACCOUNT) + .build()); + + KeyChainSnapshot snapshot = new KeyChainSnapshot.Builder() + .setSnapshotVersion(SNAPSHOT_VERSION) + .setKeyChainProtectionParams(protectionParamsList) + .setEncryptedRecoveryKeyBlob(KEY_MATERIAL) + .setWrappedApplicationKeys(appKeysList) + .setMaxAttempts(MAX_ATTEMPTS) + .setCounterId(COUNTER_ID) + .setServerParams(SERVER_PARAMS) + .setTrustedHardwarePublicKey(PUBLIC_KEY) + .build(); + + writer.writeKeyChainSnapshot(snapshot); + + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + + KeyChainSnapshot copy = reader.readKeyChainSnapshot(); + assertThat(copy.getSnapshotVersion()).isEqualTo(SNAPSHOT_VERSION); + assertThat(copy.getKeyChainProtectionParams()).hasSize(2); + assertThat(copy.getKeyChainProtectionParams().get(0).getUserSecretType()).isEqualTo(1); + assertThat(copy.getKeyChainProtectionParams().get(1).getUserSecretType()).isEqualTo(2); + assertThat(copy.getEncryptedRecoveryKeyBlob()).isEqualTo(RECOVERY_KEY_MATERIAL); + assertThat(copy.getWrappedApplicationKeys()).hasSize(2); + assertThat(copy.getWrappedApplicationKeys().get(0).getAlias()).isEqualTo(ALIAS); + assertThat(copy.getWrappedApplicationKeys().get(1).getAlias()).isEqualTo(ALIAS2); + assertThat(copy.getMaxAttempts()).isEqualTo(MAX_ATTEMPTS); + assertThat(copy.getCounterId()).isEqualTo(COUNTER_ID); + assertThat(copy.getServerParams()).isEqualTo(SERVER_PARAMS); + assertThat(copy.getTrustedHardwarePublicKey()).isEqualTo(PUBLIC_KEY); + + assertThrows( + IOException.class, + () -> reader.readKeyChainSnapshot()); + + verifyDeserialize(snapshot); + } + + public void testKeyChainSnapshot_withManyKeysAndProtectionParams() throws Exception { + PersistentKeyChainSnapshot writer = new PersistentKeyChainSnapshot(); + writer.initWriter(); + + KeyDerivationParams derivationParams = KeyDerivationParams.createSha256Params(SALT); + + ArrayList protectionParamsList = new ArrayList<>(); + protectionParamsList.add(new KeyChainProtectionParams.Builder() + .setUserSecretType(1) + .setLockScreenUiFormat(2) + .setKeyDerivationParams(derivationParams) + .build()); + protectionParamsList.add(new KeyChainProtectionParams.Builder() + .setUserSecretType(2) + .setLockScreenUiFormat(3) + .setKeyDerivationParams(derivationParams) + .build()); + ArrayList appKeysList = new ArrayList<>(); + appKeysList.add(new WrappedApplicationKey.Builder() + .setAlias(ALIAS) + .setEncryptedKeyMaterial(KEY_MATERIAL) + .setAccount(ACCOUNT) + .build()); + appKeysList.add(new WrappedApplicationKey.Builder() + .setAlias(ALIAS2) + .setEncryptedKeyMaterial(KEY_MATERIAL) + .setAccount(ACCOUNT) + .build()); + + + KeyChainSnapshot snapshot = new KeyChainSnapshot.Builder() + .setSnapshotVersion(SNAPSHOT_VERSION) + .setKeyChainProtectionParams(protectionParamsList) + .setEncryptedRecoveryKeyBlob(KEY_MATERIAL) + .setWrappedApplicationKeys(appKeysList) + .setMaxAttempts(MAX_ATTEMPTS) + .setCounterId(COUNTER_ID) + .setServerParams(SERVER_PARAMS) + .setTrustedHardwarePublicKey(PUBLIC_KEY) + .build(); + + writer.writeKeyChainSnapshot(snapshot); + + byte[] result = writer.getOutput(); + + PersistentKeyChainSnapshot reader = new PersistentKeyChainSnapshot(); + reader.initReader(result); + + KeyChainSnapshot copy = reader.readKeyChainSnapshot(); + assertThat(copy.getSnapshotVersion()).isEqualTo(SNAPSHOT_VERSION); + assertThat(copy.getKeyChainProtectionParams().get(0).getUserSecretType()).isEqualTo(1); + assertThat(copy.getEncryptedRecoveryKeyBlob()).isEqualTo(RECOVERY_KEY_MATERIAL); + assertThat(copy.getWrappedApplicationKeys().get(0).getAlias()).isEqualTo(ALIAS); + assertThat(copy.getMaxAttempts()).isEqualTo(MAX_ATTEMPTS); + assertThat(copy.getCounterId()).isEqualTo(COUNTER_ID); + assertThat(copy.getServerParams()).isEqualTo(SERVER_PARAMS); + assertThat(copy.getTrustedHardwarePublicKey()).isEqualTo(PUBLIC_KEY); + + assertThrows( + IOException.class, + () -> reader.readKeyChainSnapshot()); + + verifyDeserialize(snapshot); + } + + private void verifyDeserialize(KeyChainSnapshot snapshot) throws Exception { + byte[] serialized = PersistentKeyChainSnapshot.serialize(snapshot); + KeyChainSnapshot copy = PersistentKeyChainSnapshot.deserialize(serialized); + assertThat(copy.getSnapshotVersion()) + .isEqualTo(snapshot.getSnapshotVersion()); + assertThat(copy.getKeyChainProtectionParams().size()) + .isEqualTo(copy.getKeyChainProtectionParams().size()); + assertThat(copy.getEncryptedRecoveryKeyBlob()) + .isEqualTo(snapshot.getEncryptedRecoveryKeyBlob()); + assertThat(copy.getWrappedApplicationKeys().size()) + .isEqualTo(snapshot.getWrappedApplicationKeys().size()); + assertThat(copy.getMaxAttempts()).isEqualTo(snapshot.getMaxAttempts()); + assertThat(copy.getCounterId()).isEqualTo(snapshot.getCounterId()); + assertThat(copy.getServerParams()).isEqualTo(snapshot.getServerParams()); + assertThat(copy.getTrustedHardwarePublicKey()) + .isEqualTo(snapshot.getTrustedHardwarePublicKey()); + } + + + public void testDeserialize_failsForNewerVersion() throws Exception { + byte[] newVersion = new byte[]{(byte) 2, (byte) 0, (byte) 0, (byte) 0}; + assertThrows( + IOException.class, + () -> PersistentKeyChainSnapshot.deserialize(newVersion)); + } + + public void testDeserialize_failsForEmptyData() throws Exception { + byte[] empty = new byte[]{}; + assertThrows( + IOException.class, + () -> PersistentKeyChainSnapshot.deserialize(empty)); + } + +} + -- GitLab From 99d66f061340f603cf8a859cadb56ae21a11dbda Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Tue, 30 Jan 2018 15:01:26 -0800 Subject: [PATCH 130/416] Update Typeface test implementation not to copy minikin::Font Test: hwui_unit_tests Bug: 65024629 Change-Id: I974c11ed4d258934fb4f2bec95bede33b2261629 --- libs/hwui/hwui/Typeface.cpp | 9 +++++---- libs/hwui/tests/unit/TypefaceTests.cpp | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp index ebc14c8b675..091b5267881 100644 --- a/libs/hwui/hwui/Typeface.cpp +++ b/libs/hwui/hwui/Typeface.cpp @@ -182,10 +182,11 @@ void Typeface::setRobotoTypefaceForTest() { std::shared_ptr font = std::make_shared( std::move(typeface), data, st.st_size, 0, std::vector()); - std::shared_ptr family = std::make_shared( - std::vector({minikin::Font(std::move(font), minikin::FontStyle())})); - std::shared_ptr collection = - std::make_shared(std::move(family)); + std::vector fonts; + fonts.push_back(minikin::Font(std::move(font), minikin::FontStyle())); + + std::shared_ptr collection = std::make_shared( + std::make_shared(std::move(fonts))); Typeface* hwTypeface = new Typeface(); hwTypeface->fFontCollection = collection; diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp index 66d6f527e60..2232c25de34 100644 --- a/libs/hwui/tests/unit/TypefaceTests.cpp +++ b/libs/hwui/tests/unit/TypefaceTests.cpp @@ -56,8 +56,9 @@ std::shared_ptr buildFamily(const char* fileName) { LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName); std::shared_ptr font = std::make_shared( std::move(typeface), data, st.st_size, 0, std::vector()); - return std::make_shared( - std::vector({minikin::Font(std::move(font), minikin::FontStyle())})); + std::vector fonts; + fonts.push_back(minikin::Font(std::move(font), minikin::FontStyle())); + return std::make_shared(std::move(fonts)); } std::vector> makeSingleFamlyVector(const char* fileName) { -- GitLab From d58536800f5157184ee3a294248f96934ddd65b3 Mon Sep 17 00:00:00 2001 From: Wenting Xiong Date: Fri, 12 Aug 2016 18:28:37 +0800 Subject: [PATCH 131/416] Add carrier config option to show call barring UI in CallSettings Some operators require to add Call Barring setting UI to CallSetting, so add a new CarrierConfig value to customize this. Bug: 30845125 Test: Manual Change-Id: I5d80508afd8216f04f443c5a9e0dd83d5247788f --- .../java/android/telephony/CarrierConfigManager.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index f59871b00c2..7289d9e4f4c 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -61,6 +61,14 @@ public class CarrierConfigManager { public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool"; + /** + * Boolean indicating if the "Call barring" item is visible in the Call Settings menu. + * true means visible. false means gone. + * @hide + */ + public static final String KEY_CALL_BARRING_VISIBILITY_BOOL = + "call_barring_visibility_bool"; + /** * Flag indicating whether the Phone app should ignore EVENT_SIM_NETWORK_LOCKED * events from the Sim. @@ -1794,6 +1802,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_HIDE_SIM_LOCK_SETTINGS_BOOL, false); sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONED_BOOL, false); + sDefaults.putBoolean(KEY_CALL_BARRING_VISIBILITY_BOOL, false); sDefaults.putBoolean(KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL, false); sDefaults.putBoolean(KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL, false); sDefaults.putBoolean(KEY_OPERATOR_SELECTION_EXPAND_BOOL, true); -- GitLab From 244ef2b030e9f0c8d6780be11f8e18003dba2777 Mon Sep 17 00:00:00 2001 From: Jeffrey Vander Stoep Date: Tue, 30 Jan 2018 23:26:17 +0000 Subject: [PATCH 132/416] Revert "pm: Apps with shared UID must also share selinux domain" This reverts commit 60747d28230c5a78e30fc8836946a8a8806ab738. Reason for revert: b/72708181 Bug: 72708181 Change-Id: I694f12d769d9ba60201ecb68d3c3b8090fed6593 --- .../server/pm/PackageManagerService.java | 17 +++-------------- .../java/com/android/server/pm/SELinuxMMAC.java | 8 +++----- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6402b5d2e4b..fdb157e7e92 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -9988,7 +9988,8 @@ Slog.e("TODD", // priv-apps. synchronized (mPackages) { PackageSetting platformPkgSetting = mSettings.mPackages.get("android"); - if ((compareSignatures(platformPkgSetting.signatures.mSigningDetails.signatures, + if (!pkg.packageName.equals("android") + && (compareSignatures(platformPkgSetting.signatures.mSigningDetails.signatures, pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH)) { scanFlags |= SCAN_AS_PRIVILEGED; } @@ -10439,19 +10440,7 @@ Slog.e("TODD", pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; } - // SELinux sandboxes become more restrictive as targetSdkVersion increases. - // To ensure that apps with sharedUserId are placed in the same selinux domain - // without breaking any assumptions about access, put them into the least - // restrictive targetSdkVersion=25 domain. - // TODO(b/72290969): Base this on the actual targetSdkVersion(s) of the apps within the - // sharedUserSetting, instead of defaulting to the least restrictive domain. - final int targetSdk = (sharedUserSetting != null) ? 25 - : pkg.applicationInfo.targetSdkVersion; - // TODO(b/71593002): isPrivileged for sharedUser and appInfo should never be out of sync. - // They currently can be if the sharedUser apps are signed with the platform key. - final boolean isPrivileged = (sharedUserSetting != null) ? sharedUserSetting.isPrivileged() - : pkg.applicationInfo.isPrivilegedApp(); - SELinuxMMAC.assignSeInfoValue(pkg, isPrivileged, targetSdk); + SELinuxMMAC.assignSeInfoValue(pkg); pkg.mExtras = pkgSetting; pkg.applicationInfo.processName = fixProcessName( diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index 805734bcd9d..2552643a6a2 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -287,8 +287,7 @@ public final class SELinuxMMAC { * * @param pkg object representing the package to be labeled. */ - public static void assignSeInfoValue(PackageParser.Package pkg, boolean isPrivileged, - int targetSdkVersion) { + public static void assignSeInfoValue(PackageParser.Package pkg) { synchronized (sPolicies) { if (!sPolicyRead) { if (DEBUG_POLICY) { @@ -308,11 +307,10 @@ public final class SELinuxMMAC { if (pkg.applicationInfo.targetSandboxVersion == 2) pkg.applicationInfo.seInfo += SANDBOX_V2_STR; - if (isPrivileged) { + if (pkg.applicationInfo.isPrivilegedApp()) pkg.applicationInfo.seInfo += PRIVILEGED_APP_STR; - } - pkg.applicationInfo.seInfo += TARGETSDKVERSION_STR + targetSdkVersion; + pkg.applicationInfo.seInfo += TARGETSDKVERSION_STR + pkg.applicationInfo.targetSdkVersion; if (DEBUG_POLICY_INSTALL) { Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " + -- GitLab From 5ac59b7c46aca3c518595774f6e34d0c9671d914 Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Tue, 30 Jan 2018 15:31:36 -0800 Subject: [PATCH 133/416] Remove feature flag for suggestion ui v2. Bug: 70573674 Test: rebuild Change-Id: I876ca6b126e55cca3a0f60020c0990261bd93bdc --- core/java/android/util/FeatureFlagUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 57d23ced786..410cdc6a9bf 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -42,7 +42,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_battery_v2", "true"); DEFAULT_FLAGS.put("settings_battery_display_app_list", "false"); DEFAULT_FLAGS.put("settings_zone_picker_v2", "true"); - DEFAULT_FLAGS.put("settings_suggestion_ui_v2", "false"); DEFAULT_FLAGS.put("settings_about_phone_v2", "false"); DEFAULT_FLAGS.put("settings_bluetooth_while_driving", "false"); } -- GitLab From 58ac218a52377b4c59a6eb66e1e9fd8769edbf6f Mon Sep 17 00:00:00 2001 From: Michael Wachenschwanz Date: Tue, 30 Jan 2018 15:11:19 -0800 Subject: [PATCH 134/416] Dump per uid Binder Proxy Count before ProxyMap assert To help identify which apps may be leaking binder proxies Bug: 71353150 Test: manual Change-Id: Ib377056e3cef7088c6b05a03921d0b7a4f89d422 --- core/java/android/os/Binder.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index eb264d6d308..4aadc5b49e7 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -21,7 +21,9 @@ import android.annotation.Nullable; import android.util.ExceptionUtils; import android.util.Log; import android.util.Slog; +import android.util.SparseIntArray; +import com.android.internal.os.BinderInternal; import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FunctionalUtils.ThrowingRunnable; import com.android.internal.util.FunctionalUtils.ThrowingSupplier; @@ -934,6 +936,7 @@ final class BinderProxy implements IBinder { final int totalUnclearedSize = unclearedSize(); if (totalUnclearedSize >= CRASH_AT_SIZE) { dumpProxyInterfaceCounts(); + dumpPerUidProxyCounts(); Runtime.getRuntime().gc(); throw new AssertionError("Binder ProxyMap has too many entries: " + totalSize + " (total), " + totalUnclearedSize + " (uncleared), " @@ -987,6 +990,20 @@ final class BinderProxy implements IBinder { } } + /** + * Dump per uid binder proxy counts to the logcat. + */ + private void dumpPerUidProxyCounts() { + SparseIntArray counts = BinderInternal.nGetBinderProxyPerUidCounts(); + if (counts.size() == 0) return; + Log.d(Binder.TAG, "Per Uid Binder Proxy Counts:"); + for (int i = 0; i < counts.size(); i++) { + final int uid = counts.keyAt(i); + final int binderCount = counts.valueAt(i); + Log.d(Binder.TAG, "UID : " + uid + " count = " + binderCount); + } + } + // Corresponding ArrayLists in the following two arrays always have the same size. // They contain no empty entries. However WeakReferences in the values ArrayLists // may have been cleared. -- GitLab From 3bf7fd2d394434b5e820027183be20b47d4c4f83 Mon Sep 17 00:00:00 2001 From: Sudheer Shanka Date: Tue, 30 Jan 2018 15:20:18 -0800 Subject: [PATCH 135/416] Update the logic for getting metered data disabled pkgs in DPMS. Bug: 63700027 Test: atest com.android.cts.devicepolicy.MixedProfileOwnerTest#testSetMeteredDataDisabled Test: atest com.android.cts.devicepolicy.MixedProfileOwnerTest#testSetMeteredDataDisabled Test: atest com.android.server.devicepolicy.DevicePolicyManagerTest Change-Id: I207f7d43d3aade7e91ae98f32e9b775b16b2153e --- .../devicepolicy/DevicePolicyManagerService.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index dae760576b4..48dae3b7014 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -11437,16 +11437,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private Set getMeteredDisabledPackagesLocked(int userId) { - final DevicePolicyData policy = getUserData(userId); + final ComponentName who = getOwnerComponent(userId); final Set restrictedPkgs = new ArraySet<>(); - for (int i = policy.mAdminList.size() - 1; i >= 0; --i) { - final ActiveAdmin admin = policy.mAdminList.get(i); - if (!isActiveAdminWithPolicyForUserLocked(admin, - DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, userId)) { - // Not a profile or device owner, ignore - continue; - } - if (admin.meteredDisabledPackages != null) { + if (who != null) { + final ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userId); + if (admin != null && admin.meteredDisabledPackages != null) { restrictedPkgs.addAll(admin.meteredDisabledPackages); } } -- GitLab From 6d6619a44e3f2439bfddaca411a301b2c3324059 Mon Sep 17 00:00:00 2001 From: Yao Chen Date: Tue, 30 Jan 2018 15:50:54 -0800 Subject: [PATCH 136/416] Make statsd dogfood app to be a system app. And update the baseline config. Bug: 72710440 Test: manual Change-Id: Icc37d1d688d3788e9c9e98a594b9532889ffeea9 --- cmds/statsd/tools/dogfood/Android.mk | 1 + cmds/statsd/tools/dogfood/AndroidManifest.xml | 1 + .../dogfood/res/raw/statsd_baseline_config | Bin 4785 -> 15841 bytes 3 files changed, 2 insertions(+) diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/statsd/tools/dogfood/Android.mk index 32a85b13602..a65095f4188 100644 --- a/cmds/statsd/tools/dogfood/Android.mk +++ b/cmds/statsd/tools/dogfood/Android.mk @@ -27,6 +27,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := platformprotoslite \ LOCAL_PROTOC_OPTIMIZE_TYPE := lite LOCAL_PRIVILEGED_MODULE := true LOCAL_DEX_PREOPT := false +LOCAL_CERTIFICATE := platform LOCAL_PROGUARD_ENABLED := disabled include $(BUILD_PACKAGE) diff --git a/cmds/statsd/tools/dogfood/AndroidManifest.xml b/cmds/statsd/tools/dogfood/AndroidManifest.xml index 7bfde405019..cd76c9d379b 100644 --- a/cmds/statsd/tools/dogfood/AndroidManifest.xml +++ b/cmds/statsd/tools/dogfood/AndroidManifest.xml @@ -18,6 +18,7 @@ --> diff --git a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config index c1c3914838558079f14c3c35018c56a13575889d..d05006124994831625369971ac34b4c18e304730 100644 GIT binary patch literal 15841 zcmdvc0~z^a;&=8u;cZGmuufL3QU~QeRt;XV|`{qq8uGF56-=IuIcM+MuEdi z_dHl}a{B*3AyJOI51yX5-|*zDgTS$y7h5i0Xlr@FC?v{pasQc3hc?b!ks~nwNW+_7 z9oN?WVH6VK*!Jtp=B}5I+SvpiKUjX_#OWHinKPe0k?h`8Rpvp!vTvSfb`E^|XUS{H9>+j^&cVmy+2 zKfe2NseQ?owkjOPzMQmX$MZeymsjC3)|+GE<1O1J{OLdXoKfJ{$Bn0-E^b@lf}GH} zIF!LD2a?33k&_r3R0@{3JUL!3f3tnptEtDNk<3C$Tc~D9A*CvGqr5n7F1q%sx$Q%v z9FkE;=?T>+Y2=iIY1ZSvH=cfOT=hp2$tG#9RDx;N)M=krfB3TG-g{)T5D5g`ETp7? z#jIUd)=zCcH_1w1_O37cZ++f3H(R3Ta_^l*`x?)jVpQVd;J}?GAX11lvHS9;%Wpd$ zT;oDA>-f4uv*(|h|Kt(6S*U3O)hI8HYZv~!-`oAYu@cFs|4sYm9iBXC`B!wK(9#6D zSw9;8pPKyi$LSS}NQTY$J7eOao(l&&F}#S9CQ!}t=J>ky{)Xw3-t4=MY}T8J4==r& zfAj1^OoySQ2~@+pI3DbH{prcl-*-EZ4g2!4d;k6&_m8k+I1D9Cpqq7JanFagHCu1W zBl)p&w$V(7d;8+g}vZkP`+(2h1>(gaMI47aG zQK0MP#}6l(kM69Kn9}*+HLm zO8Icy*m8Ey!eT2D$`9J(MkKpR$QwH`zY>JBL{j+0Ag zOuYT4qx%zzY49c!nqjDk8PzNwj)g0q+*|Qw+mF2{W+B>1XojIBW^~iqzaIa$aNn&H z|4>XrBv}m85K=;H&>(>M(}!dFyVZ~1-`e|o4T@=qfW|NlA%(@X!yC^w{%!jIWi5(n zh&aVC4IzbLnm0%Hr&AkN?|%ED2020zsRzR_gcQ1AN|qc?UO&6l_-W2^TY)nxT9-~a zyyo$FMk#iViGET{9IOiP2D=tFL;A_*zt2pa z0rKu-Oz(n)kiGl-T*HIj*9&@@vFSziE{5KjGq(P?w&}>5uh{gWdKW|Qnrm~GEjJ?9gc~gn>N3{`EywlBc^IjMAU;-Yjb?M|8wc~)khv!V=6}S23WBU$K~Hg zKVDk&W%(g2Mk8eeuxf3N6`MB9`?R@vMG&UZNYM#ZeDBTS9d|eWyH<#)m>td0Ivi&^ zXFa>Kw(rOREbibyQ*F%Acx3m+SNnE+*}y0;W+*% zmtKnqy!kLe- zU?I3xqezas&yJn`w4mwNL`H$#pD!z5DO-w2#yWkKAl_j zu zMdZdBX(nGgwCaBEk7s92A)Aco*D4~n5lJ!Glw;DFx4+MR{eEmMvdQaC{CwH)bJE6e z4JI6kYSYf|_uG07Y)e3jdZdv#B9be1lfyZ_--3DK56EsNDY0TVIh14iyZ5&@?by+_71?Cc zk{zQFO8&55G791Nz5e&_P22u{T#jrkNofw7scj#B&YXW@#cFY+@Fgw1VKeno$N7f& z>pQz#kW3{lsbQKL$}wxn>6Vu>_J3FzPJ9{iF;~DjwLCHVHz63 zac<4$zYnMEdT|fgtt2HcOjDIP9!!`!>*U7Y|2HC=iYNyeEtrgyIquE7_rCf2Mp<&HGP$D#EIU$%4s9i(^^S=aq*}UG6c)s)o^s zjYAdG^#-dwdSTAq2kVY>F=189grru9qy5FiO>H+XAJWFE7UVuJj@i5K^gaBs^2%jK zfyRzwE!S6VTf7=&5DTrR51x;KHR?1#ZUc*0Fd2Dpth@N6>C&v0yM@R`Aq`=n_VdB| zu$tt>@#^oPBdt9%PaZ;X7196}S}z~BS)Lq^4zJ!Z`__kRpHa+03|~njw;rGqPFS7g z$?<>V(-jNepIqyW>_Mc#D{17`0zRWWIR37DeQ)W@?~`MYjY5oEp|zHA`_GHx;D)Pj zmUaDYU60~Fq=756W;t%Nni)A}oO(R<|EUQ#Z!rpNfA(wsnY~Si7BV7@M4_3*3`&=< zz*0hUG`b{GONxyHMG~Bjz#s9O27x>-5JhPulk! zQ5RUX;Q#aAOI|+hz?Uyj#)dT*jgTe;ESQXZIQq^{f3;-U-Swu(#^KBx_)YWUIJ~3j z?A(KMj@@EJHV$|Gz;B)p$D#TA`@TJWaZD50IGkAoziECP|L$J;zx?LHjWbYOhdZC( zH_w-2^V*BoUoP$XzYN7ZoSB7?ansJ->HfU_bc-CaXL08j{Khpia`c~l)wt#Fy@TIS zoCoh9E1~5X6!XyY47wyroB-{~WF@4v0YrGX7R zUJj}(l{hZU-Fa%!lY4E-0(*NmFZlIy)<$n+!!4MMJURCDwEo_>>hiyHj7W-*X6w-E zG?cM$aJYg+z;)Wa32T2J?Rwq!4%sN2`4+4XRvx06bnE!pzq7xb{SuGlCfqp|w@L0C z52xJOuzb&*_ti)y;moYK4f5pZ`8DZHr3q{H^uF(SglrVf zEQ-&hjy;DC_B5^Paz*kT?%avnBn6HSx907-_Ts^783Fjr12|5=3qLHFjFdQ9-@mvy z|LJE?LP1P*pr}#gICgSH`=fd5J0%4Wa}Fpt)2obEyn6CA3wIVN4W`+U;tH7iSzLI;Op9gZ_cTUvU4UAeb_5hs{X&~#A0ejYx&_r{71=WUVg2Nzju&d#{|{?>#OFOe1DF0!OR zrlS-zU=eVU_4w?*JDbj)UiSmpD4azWSRYoCJUPzH-1&FWylzFCHvd9ElVLILj>D26=KcpLlk$VaL|xTakT-yUfCElsm`u{`SWUPOd${g5)Ng zMHX&@JU9++JwN;9(jAXOkqp9JWZ^bRiDUW3{XN_Fy?rSu(DLbSb7$-MnZ+1MWnJfl zqb>i>wQpxcQiMCHNMIxtun0J*G;Ub+^!BI!GiM+hg)^yu^`Rscun5?sYwtUbfB*1q z5*L!2aHkZoKCC9WbIg7AXKL5J4c)>>CgDsexDE2)c-Zsm^n%}a?(!fRggc?&Hp!i1 z>z}nfyH52k6+|)#XF|bkkO#;4XV2!}`#9r-I+8)S6AEsVlsTTPTE64;huvqdF$zpx zcYoc#-t+sV(Nc;B$D@iIGsiA{IyHZ}nS?W);5NvEW9jvkuXkR%dNc{iAl&H$ zw@JzzdwyztCKWqy~375ywa;U=eUS zIq>TH(M#92{18Gi31>P1YeR`$un4$5{IX!hkKVOscQzoq8+SqhYs6}l2gliCf4{9; zeE#WaWTSAV6nrM_-`BNZ@~OQKS&@8)JEh<@$(>`@$u-L+AA5eM0LdhrDFwGd9vo8- z@0$Pb-TEJ9NCx3fDL75C;^%k-UzaV!#Gz#+#&PAwr1jHZP41b*D8$JjEyT*fq|w4| zCC2gL|A~eJZ!dgUi4b4IWF^M&X6NTckJc}`bp|1Rhucbw+=)mw)Z)~jbFlRCCahm+};y!Cj4w+h6_(%wi4yoHv7fbYaiBq&xH%W06AvG zv0VqgZawjSC&KV4TvlQnkKeC6wRgs>h3gRFdswYRIhGz?Kj&@hqow6=V}5{?Kizxc z(D_f3?|nun@8AR3dhErWWiyw)xz8xX!67Zg!oj4$U?s}&_twtsd%tzB_>T}yu@d9h z{`uwClPjlvK?;EloK~V7eP8D`u3b3cqbuCfGaOc;9P>}TIy+;<{EMt`;RS3~q8xY6 zTzuHudFb&&km2G`J4HC=U0VM8>5*S6IE6SkAfk*`{2WLt@|if~toS(K^ZP`t@roOE?;n=n^DS~ zgGm4p{sQoz6+ol}0Yu~rz~kFWjN{Pei?cWHx!QXe5#BdgtSmYH-+Mc|f8*5G5mLGw zOaj~Bp)Y_4aREg76M&}`P;x!geDCYm6YG&u!U;wzQI7Ky59~g-qj70GJe0qH($k}t zvwPnD`7rk#D3m3kp)AVL+%a{{r?+o5OhX7WTJdop_Ea!&$Xf|;Ag1(%m^o(nSqX3; z)&~eNbIkIy;^RQaQvU%yXxS-E&BxFqM-B&U%ey5%rVE$N|IxCXYaGAFB|(4 zqH01yA}vZDy3>;b0bkr#CAJj)N0g{`_b=oej3^3OqGgNpKuH zf2-wj@7IMUU_I~zWhKS2=hUG&$NqkO$S=gl!37Qw1|b#>77a!#DUOzgX{UEi-F_z$ zO^(@0nq%9hXG{7!Kb<|#h$hKorOW}}-XbN=!6dNt`Hnq*|6lCY6hLUQQs(&a_Tv8U zTi3L&WCX_-+#v#RYptX>;Cj()VX)HWnD>71lm4?emM>f@kf44phTZwSM<%Kvn6ognf7&SndYvsdPNB8glJNpzUwLyd# ztVB5AM!*$=QUu&MxG0Mi9|vL=6BCDml?VrXeZ3F|2Zs;~2Q#?DJ=Ame^@j!hi|&Ff zf(V0>HDZqu69=ai9|vNS6BCE5l_st`QAWt4V@NDXmuJ?1nSqfqZla(mP$BWP3y}q#h_(X(iHY*X1 zra#BNPrGp8K!gwn2ShQb#^^uWxwyS~*W8N;#jIAA9KADl^~QT{U(LPH-}UZDB3u+yG&QfD-*LQe z|3X$F4i08Wwy+ZA_nA`asX{Ff z;n;hpuWw2F$*oaB92^i)kkjGY(1bWR)S+rcIW~0keeS*RZrcWs5fEWUCmrO4Hu4;Z zvP%G7s7WxuhTEJ}I1ueJDRBfYc-qKoqekE=3p!b_8O?63L}19Ec?W=&BG-)kR+Bq=3|V5I~gR zNLDLzz&!vq6EP5qWV12{eAxiHB4mpZL$N{%NDU4FMD>7TF}z0rHWQ&4$zl}_#NrBc zRS284k(SWOBU&^9h^hz0UPSjuN{j<(%ofRBWe&vp6EsE0)*^;*h2%I86_^05Vnec4 zg#*#Q1N#J_7s*}~4#aXAbXCX}BL>EW0S&eo-05-B;ed~m2q_>n^8^qLL9l9YXWB`H1D+*?BsdVYoB*QMlwfeu z;eZe53Mp_PnrQ-vh8);T@Q9z2CI@`RN=TLi(N05b`7nZR^l;MVK#V>M$sw9%0{>x+ zDzLfWF=i)I4)`KbAx)%q0n)BW2F!(fPI?@OiESZ84nzfu*cpX9i|wSu@wsL1z8iDy z&#M&@LsVP>@Onss!AXe&9y{Q43fF-Yx4Imk;nRXb3W)j#u~WtfX@by6mjgaSDWu2& zuWpbw=@=nRKsxDgAl5Ai$s<)c0`NKs9CYA`ekUCc_%N%G0tceT5kS;QVAbG}I8dy> n*7*p@BdQMpMEwF*3|_3`q{D$&Pa`CcRDmFEbOu!p;N=(q$bn#I literal 4785 zcmd<$aB>WG_3`v`71HAp4e$wdcXxG(4-Rn*agBHOaP)I`brHfLY``Vv?BN*X?&;@_ zrU93@4wp!v4%XIfjI|21UmE zxQ4m65P~Tc$|Z*33}^pPzYrl|3~_%yDG8`$p26|{Zf^1Ze(_K*Dlu^| zDlu^|Yq4-VSS}FXKg>Jsnd;~F3A?C2K{F#9SC@D%eAeVr=j4a{r=7!TSB_<9gsDZXze8G`^&Pe8P zfcZpfcjOWXcJ&MP4?;443o1gS5$0UH?g7C_`q@ETe3~InS90Xy4fk~OjCYC*aSe_S zi4c-Q<_0eLu6S*)oVZY1^$L8R#kT#BH8pt23%uz zkZONFAubL!AvP{14kiUgD}63SXz>IoQenklJWMn+*hz?sLjkOTQGv-y9$rpEDm@qf zC|4ma4q2!IW-BGMN-NaQ+s{87WGhS!gOw;3taLzf41<*{7eBOEhL{a@D@+%Yl>`@3 znIy!;!3wsMS%JYyk4pjOUWiU;VGj3&qcf5x7_DS*R>e#l@>XhG5?Irxznhy77Y8%g zRwe}oD^*Y_j#NGS`^5)`1_!wMxgZ4yo0S}w0IILR#=yMAh~ew7ASCUqR;pYC3Pd4h zj+g9KCR}RxgCN{9$khi_sUl*N#mbaR9;4WUG-Ki&odZKXgItA#IJjUw2GcAG%vJ_m zO871F^Y;U#3RtoM#gGJsMYwHXw3@)kC5zVzseBG5A;PgvMCv9o-0+1JC}bUjv4s^h z!=P2^LR=gi;J^eWUTIvdE+!5+D^22>SWFz;R#Ipg4-$_qLQEX8prndDNrUnwEJZU~ ziE+W34MJQT>~JTBaA9pk*>W%m;f_{9Da%TQV1t#3Lljh2fD2o2VE}Uyqg5b0SA~QI z`MJ8p`};|O+=Eeqh@)mcL>@&|85U$E&cz8WlEHStiWf#JbuLL1LqKI7maNWVB@Qnf zks7f~9Gq5CTs(-17;L&KIB-Elur#y*1g^LtWeiM`!AcreCxeMY0aCJ{BrQ;M!crEa zl^z#n?BGtS3|9QeZEGeDX{%5!5sX|5YSMuM8n@9nBOYqERW3Z3U~%W>CKbWKB!pQ| z!g>IZVjUFlLU`f=wPOUWd7Q#=wBf-Or;sHVu2#Gd&T2q{0o3?%is8c6ni6s)s&vF( zyh$)PIdS3aq)4f8FbeTP(U)1IJuZ-2{De%g78gQT(-OomBPI@JCkrk#FR5cmC-5`^OFDiM3{Lu7D2*v0 zd6b+C%hYaeApe4zbxtl^*cyXEx}ZV@Ekg;Swi^ABOwr^*Y6l8QbFsq;DtIXhv(PJ; zOU^aI*)=%WGt4y}TTv{8ztt_p#_@ukW3RZv0wxVEgN6kk4{bE@3gnUm_rI~4N4T90 zH}CSZga$cO{WC$8MZgv$sM z0!Da%jA-bZ-m{oFNGdr$S1&Oyr6@l$MX$Idv7|UfFC{-cEk8dch|AH_B|bPZIK Date: Mon, 29 Jan 2018 15:36:35 -0800 Subject: [PATCH 137/416] Fix AOD scrim excluded area Excluded scrim area wasn't being updated properly, we should also update it whenever we enter or leave "dark mode." Fixes: 72456250 Test: Expand notification shade, lock device Test: Receive notification in AOD Change-Id: I85fdd77fa70c5ee55a6af3cbe997e9c5bc47b322 --- .../stack/NotificationStackScrollLayout.java | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index ad8a0eb98ea..330d564162d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -365,7 +365,7 @@ public class NotificationStackScrollLayout extends ViewGroup private boolean mGroupExpandedForMeasure; private boolean mScrollable; private View mForcedScroll; - private float mDarkAmount = 1.0f; + private float mDarkAmount = 0f; private static final Property DARK_AMOUNT = new FloatProperty("darkAmount") { @Override @@ -523,9 +523,9 @@ public class NotificationStackScrollLayout extends ViewGroup setClipBounds(null); } else { float animProgress = Interpolators.FAST_OUT_SLOW_IN - .getInterpolation(mDarkAmount); + .getInterpolation(1f - mDarkAmount); float sidePaddingsProgress = Interpolators.FAST_OUT_SLOW_IN - .getInterpolation(mDarkAmount * 2); + .getInterpolation((1f - mDarkAmount) * 2); mTmpRect.set((int) MathUtils.lerp(darkLeft, lockScreenLeft, sidePaddingsProgress), (int) MathUtils.lerp(darkTop, lockScreenTop, animProgress), (int) MathUtils.lerp(darkRight, lockScreenRight, sidePaddingsProgress), @@ -548,7 +548,7 @@ public class NotificationStackScrollLayout extends ViewGroup } else { float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount); - alpha *= mDarkAmount; + alpha *= 1f - mDarkAmount; // We need to manually blend in the background color int scrimColor = mScrimController.getBackgroundColor(); color = ColorUtils.blendARGB(scrimColor, mBgColor, alpha); @@ -2304,8 +2304,9 @@ public class NotificationStackScrollLayout extends ViewGroup return; } + final boolean awake = mDarkAmount != 0 || mAmbientState.isDark(); mScrimController.setExcludedBackgroundArea( - mFadingOut || mParentNotFullyVisible || mDarkAmount != 1 || mIsClipped ? null + mFadingOut || mParentNotFullyVisible || awake || mIsClipped ? null : mCurrentBounds); invalidate(); } @@ -3858,17 +3859,12 @@ public class NotificationStackScrollLayout extends ViewGroup mDarkNeedsAnimation = true; mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation); mNeedsAnimation = true; - setDarkAmount(0.0f); - } else if (!dark) { - setDarkAmount(1.0f); - } - requestChildrenUpdate(); - if (dark) { - mScrimController.setExcludedBackgroundArea(null); } else { + setDarkAmount(dark ? 1f : 0f); updateBackground(); } - + requestChildrenUpdate(); + applyCurrentBackgroundBounds(); updateWillNotDraw(); updateContentHeight(); notifyHeightChangeListener(mShelf); @@ -3894,7 +3890,7 @@ public class NotificationStackScrollLayout extends ViewGroup } private void startBackgroundFadeIn() { - ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, DARK_AMOUNT, 0f, 1f); + ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, DARK_AMOUNT, mDarkAmount, 0f); fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP); fadeAnimator.setInterpolator(Interpolators.ALPHA_IN); fadeAnimator.start(); -- GitLab From 391161f110b916520be2455a06bdc93de548082c Mon Sep 17 00:00:00 2001 From: Andrii Kulian Date: Mon, 29 Jan 2018 10:50:02 -0800 Subject: [PATCH 138/416] Switch order of onSaveInstanceState and onStop This CL switches order of onSaveInstanceState and onStop callbacks for app that target P+. Now activity state will be saved after onStop. Bug: 68763258 Test: Manual Change-Id: If0410cbfe7920bfff9e3b9fd8879646c5622cb33 --- core/java/android/app/Activity.java | 13 +++++++++++-- core/java/android/app/ActivityThread.java | 23 ++++++++++++++++------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index cd029c06b91..6a9d8f77f4f 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -424,6 +424,12 @@ import java.util.List; * safely called after {@link #onPause()} and allows and application to safely * wait until {@link #onStop()} to save persistent state.

* + *

For applications targeting platforms starting with + * {@link android.os.Build.VERSION_CODES#P} {@link #onSaveInstanceState(Bundle)} + * will always be called after {@link #onStop}, so an application may safely + * perform fragment transactions in {@link #onStop} and will be able to save + * persistent state later.

+ * *

For those methods that are not marked as being killable, the activity's * process will not be killed by the system starting from the time the method * is called and continuing after it returns. Thus an activity is in the killable @@ -1576,8 +1582,11 @@ public class Activity extends ContextThemeWrapper * call through to the default implementation, otherwise be prepared to save * all of the state of each view yourself. * - *

If called, this method will occur before {@link #onStop}. There are - * no guarantees about whether it will occur before or after {@link #onPause}. + *

If called, this method will occur after {@link #onStop} for applications + * targeting platforms starting with {@link android.os.Build.VERSION_CODES#P}. + * For applications targeting earlier platform versions this method will occur + * before {@link #onStop} and there are no guarantees about whether it will + * occur before or after {@link #onPause}. * * @param outState Bundle in which to place your saved state. * diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2b548b1fd14..09540464443 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -487,12 +487,14 @@ public final class ActivityThread extends ClientTransactionHandler { } } - public boolean isPreHoneycomb() { - if (activity != null) { - return activity.getApplicationInfo().targetSdkVersion - < android.os.Build.VERSION_CODES.HONEYCOMB; - } - return false; + private boolean isPreHoneycomb() { + return activity != null && activity.getApplicationInfo().targetSdkVersion + < android.os.Build.VERSION_CODES.HONEYCOMB; + } + + private boolean isPreP() { + return activity != null && activity.getApplicationInfo().targetSdkVersion + < android.os.Build.VERSION_CODES.P; } public boolean isPersistable() { @@ -4164,9 +4166,12 @@ public final class ActivityThread extends ClientTransactionHandler { * {@link Activity#onSaveInstanceState(Bundle)} is also executed in the same call. */ private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) { + // Before P onSaveInstanceState was called before onStop, starting with P it's + // called after. Before Honeycomb state was always saved before onPause. final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null && !r.isPreHoneycomb(); - if (shouldSaveState) { + final boolean isPreP = r.isPreP(); + if (shouldSaveState && isPreP) { callActivityOnSaveInstanceState(r); } @@ -4185,6 +4190,10 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_STOP); EventLog.writeEvent(LOG_AM_ON_STOP_CALLED, UserHandle.myUserId(), r.activity.getComponentName().getClassName(), reason); + + if (shouldSaveState && !isPreP) { + callActivityOnSaveInstanceState(r); + } } private void updateVisibility(ActivityClientRecord r, boolean show) { -- GitLab From a656b8bf9fddf18c506a4f59989d55b9eb6abaa8 Mon Sep 17 00:00:00 2001 From: Dan Cashman Date: Fri, 26 Jan 2018 13:53:59 -0800 Subject: [PATCH 139/416] APK Signature Scheme v3: add version number to proof-of-rotation. The proof-of-rotation record contains a list of signing certificates and corresponding flags. New flags may be defined in future platform versions, but APKs targeting P would have no knowledge of them. Add a version code to enable future platform versions to identify which flags were deliberately set. Ignore the version code for this platform version, though, since all flags are known. Bug: 64686581 Test: Builds, boots. Change-Id: I765f50918f7f337100aff3ed15999b45369fc9d1 --- .../apk/ApkSignatureSchemeV3Verifier.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java index a4c590f1b45..9436b296e9e 100644 --- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java +++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java @@ -438,8 +438,8 @@ public class ApkSignatureSchemeV3Verifier { List flagsList = new ArrayList<>(); // Proof-of-rotation struct: - // is basically a singly linked list of nodes, called levels here, each of which have the - // following structure: + // A uint32 version code followed by basically a singly linked list of nodes, called levels + // here, each of which have the following structure: // * length-prefix for the entire level // - length-prefixed signed data (if previous level exists) // * length-prefixed X509 Certificate @@ -450,9 +450,13 @@ public class ApkSignatureSchemeV3Verifier { // - length-prefixed signature over the signed data in this level. The signature here // is verified using the certificate from the previous level. // The linking is provided by the certificate of each level signing the one of the next. - while (porBuf.hasRemaining()) { - levelCount++; - try { + + try { + + // get the version code, but don't do anything with it: creator knew about all our flags + porBuf.getInt(); + while (porBuf.hasRemaining()) { + levelCount++; ByteBuffer level = getLengthPrefixedSlice(porBuf); ByteBuffer signedData = getLengthPrefixedSlice(level); int flags = level.getInt(); @@ -491,17 +495,17 @@ public class ApkSignatureSchemeV3Verifier { lastSigAlgorithm = sigAlgorithm; certs.add(lastCert); flagsList.add(flags); - } catch (IOException | BufferUnderflowException e) { - throw new IOException("Failed to parse Proof-of-rotation record", e); - } catch (NoSuchAlgorithmException | InvalidKeyException - | InvalidAlgorithmParameterException | SignatureException e) { - throw new SecurityException( - "Failed to verify signature over signed data for certificate #" - + levelCount + " when verifying Proof-of-rotation record", e); - } catch (CertificateException e) { - throw new SecurityException("Failed to decode certificate #" + levelCount - + " when verifying Proof-of-rotation record", e); } + } catch (IOException | BufferUnderflowException e) { + throw new IOException("Failed to parse Proof-of-rotation record", e); + } catch (NoSuchAlgorithmException | InvalidKeyException + | InvalidAlgorithmParameterException | SignatureException e) { + throw new SecurityException( + "Failed to verify signature over signed data for certificate #" + + levelCount + " when verifying Proof-of-rotation record", e); + } catch (CertificateException e) { + throw new SecurityException("Failed to decode certificate #" + levelCount + + " when verifying Proof-of-rotation record", e); } return new VerifiedProofOfRotation(certs, flagsList); } -- GitLab From 1dbe6d02849cb4af87bbd26b7537e11badead3b1 Mon Sep 17 00:00:00 2001 From: Dan Cashman Date: Tue, 23 Jan 2018 11:18:28 -0800 Subject: [PATCH 140/416] Add key rotation. Change certificate checks to also consider the possibility of signing certificate rotation by checking the SigningDetails#pastSigningCertificates field. In particular, add a SigningDetails#checkCapability method which reports whether or not the older SigningDetails is an ancestor of the current one, and queries whether or not the old one has been granted capabilities, such as being a sharedUser. Bug: 64686581 Test: Builds, boots, browser and camera work, all with v3 signing. Change-Id: I4199ff3f2d9ae959325b117b28e666ae31889800 --- .../android/content/pm/PackageParser.java | 292 +++++++++++++++++- core/java/android/content/pm/Signature.java | 23 ++ .../android/server/pm/InstantAppRegistry.java | 25 +- .../server/pm/PackageManagerService.java | 123 +++++--- .../server/pm/PackageManagerServiceUtils.java | 138 ++++----- .../android/server/pm/PackageSettingBase.java | 5 + .../com/android/server/pm/SELinuxMMAC.java | 6 +- .../DefaultPermissionGrantPolicy.java | 6 +- .../permission/PermissionManagerService.java | 24 +- 9 files changed, 493 insertions(+), 149 deletions(-) diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 3d26af1f15b..2da893771d9 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -78,8 +78,10 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Base64; +import android.util.ByteStringUtils; import android.util.DisplayMetrics; import android.util.Log; +import android.util.PackageUtils; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -5683,7 +5685,10 @@ public class PackageParser { return true; } - /** A container for signing-related data of an application package. */ + /** + * A container for signing-related data of an application package. + * @hide + */ public static final class SigningDetails implements Parcelable { @IntDef({SigningDetails.SignatureSchemeVersion.UNKNOWN, @@ -5705,15 +5710,54 @@ public class PackageParser { public final ArraySet publicKeys; /** - * Collection of {@code Signature} objects, each of which is formed from a former signing - * certificate of this APK before it was changed by signing certificate rotation. + * APK Signature Scheme v3 includes support for adding a proof-of-rotation record that + * contains two pieces of information: + * 1) the past signing certificates + * 2) the flags that APK wants to assign to each of the past signing certificates. + * + * This collection of {@code Signature} objects, each of which is formed from a former + * signing certificate of this APK before it was changed by signing certificate rotation, + * represents the first piece of information. It is the APK saying to the rest of the + * world: "hey if you trust the old cert, you can trust me!" This is useful, if for + * instance, the platform would like to determine whether or not to allow this APK to do + * something it would've allowed it to do under the old cert (like upgrade). */ @Nullable public final Signature[] pastSigningCertificates; + /** special value used to see if cert is in package - not exposed to callers */ + private static final int PAST_CERT_EXISTS = 0; + + @IntDef( + flag = true, + value = {CertCapabilities.INSTALLED_DATA, + CertCapabilities.SHARED_USER_ID, + CertCapabilities.PERMISSION }) + public @interface CertCapabilities { + + /** accept data from already installed pkg with this cert */ + int INSTALLED_DATA = 1; + + /** accept sharedUserId with pkg with this cert */ + int SHARED_USER_ID = 2; + + /** grant SIGNATURE permissions to pkgs with this cert */ + int PERMISSION = 4; + } + /** - * Flags for the {@code pastSigningCertificates} collection, which indicate the capabilities - * the including APK wishes to grant to its past signing certificates. + * APK Signature Scheme v3 includes support for adding a proof-of-rotation record that + * contains two pieces of information: + * 1) the past signing certificates + * 2) the flags that APK wants to assign to each of the past signing certificates. + * + * These flags, which have a one-to-one relationship for the {@code pastSigningCertificates} + * collection, represent the second piece of information and are viewed as capabilities. + * They are an APK's way of telling the platform: "this is how I want to trust my old certs, + * please enforce that." This is useful for situation where this app itself is using its + * signing certificate as an authorization mechanism, like whether or not to allow another + * app to have its SIGNATURE permission. An app could specify whether to allow other apps + * signed by its old cert 'X' to still get a signature permission it defines, for example. */ @Nullable public final int[] pastSigningCertificatesFlags; @@ -5784,6 +5828,244 @@ public class PackageParser { return pastSigningCertificates != null && pastSigningCertificates.length > 0; } + /** + * Determines if the provided {@code oldDetails} is an ancestor of or the same as this one. + * If the {@code oldDetails} signing certificate appears in our pastSigningCertificates, + * then that means it has authorized a signing certificate rotation, which eventually leads + * to our certificate, and thus can be trusted. If this method evaluates to true, this + * SigningDetails object should be trusted if the previous one is. + */ + public boolean hasAncestorOrSelf(SigningDetails oldDetails) { + if (this == UNKNOWN || oldDetails == UNKNOWN) { + return false; + } + if (oldDetails.signatures.length > 1) { + + // multiple-signer packages cannot rotate signing certs, so we just compare current + // signers for an exact match + return signaturesMatchExactly(oldDetails); + } else { + + // we may have signing certificate rotation history, check to see if the oldDetails + // was one of our old signing certificates + return hasCertificate(oldDetails.signatures[0]); + } + } + + /** + * Similar to {@code hasAncestorOrSelf}. Returns true only if this {@code SigningDetails} + * is a descendant of {@code oldDetails}, not if they're the same. This is used to + * determine if this object is newer than the provided one. + */ + public boolean hasAncestor(SigningDetails oldDetails) { + if (this == UNKNOWN || oldDetails == UNKNOWN) { + return false; + } + if (this.hasPastSigningCertificates() && oldDetails.signatures.length == 1) { + + // the last entry in pastSigningCertificates is the current signer, ignore it + for (int i = 0; i < pastSigningCertificates.length - 1; i++) { + if (pastSigningCertificates[i].equals(oldDetails.signatures[i])) { + return true; + } + } + } + return false; + } + + /** + * Determines if the provided {@code oldDetails} is an ancestor of this one, and whether or + * not this one grants it the provided capability, represented by the {@code flags} + * parameter. In the event of signing certificate rotation, a package may still interact + * with entities signed by its old signing certificate and not want to break previously + * functioning behavior. The {@code flags} value determines which capabilities the app + * signed by the newer signing certificate would like to continue to give to its previous + * signing certificate(s). + */ + public boolean checkCapability(SigningDetails oldDetails, @CertCapabilities int flags) { + if (this == UNKNOWN || oldDetails == UNKNOWN) { + return false; + } + if (oldDetails.signatures.length > 1) { + + // multiple-signer packages cannot rotate signing certs, so we must have an exact + // match, which also means all capabilities are granted + return signaturesMatchExactly(oldDetails); + } else { + + // we may have signing certificate rotation history, check to see if the oldDetails + // was one of our old signing certificates, and if we grant it the capability it's + // requesting + return hasCertificate(oldDetails.signatures[0], flags); + } + } + + /** + * A special case of {@code checkCapability} which re-encodes both sets of signing + * certificates to counteract a previous re-encoding. + */ + public boolean checkCapabilityRecover(SigningDetails oldDetails, + @CertCapabilities int flags) throws CertificateException { + if (oldDetails == UNKNOWN || this == UNKNOWN) { + return false; + } + if (hasPastSigningCertificates() && oldDetails.signatures.length == 1) { + + // signing certificates may have rotated, check entire history for effective match + for (int i = 0; i < pastSigningCertificates.length; i++) { + if (Signature.areEffectiveMatch( + oldDetails.signatures[0], + pastSigningCertificates[i]) + && pastSigningCertificatesFlags[i] == flags) { + return true; + } + } + } else { + return Signature.areEffectiveMatch(oldDetails.signatures, signatures); + } + return false; + } + + /** + * Determine if {@code signature} is in this SigningDetails' signing certificate history, + * including the current signer. Automatically returns false if this object has multiple + * signing certificates, since rotation is only supported for single-signers; this is + * enforced by {@code hasCertificateInternal}. + */ + public boolean hasCertificate(Signature signature) { + return hasCertificateInternal(signature, PAST_CERT_EXISTS); + } + + /** + * Determine if {@code signature} is in this SigningDetails' signing certificate history, + * including the current signer, and whether or not it has the given permission. + * Certificates which match our current signer automatically get all capabilities. + * Automatically returns false if this object has multiple signing certificates, since + * rotation is only supported for single-signers. + */ + public boolean hasCertificate(Signature signature, @CertCapabilities int flags) { + return hasCertificateInternal(signature, flags); + } + + /** Convenient wrapper for calling {@code hasCertificate} with certificate's raw bytes. */ + public boolean hasCertificate(byte[] certificate) { + Signature signature = new Signature(certificate); + return hasCertificate(signature); + } + + private boolean hasCertificateInternal(Signature signature, int flags) { + if (this == UNKNOWN) { + return false; + } + + // only single-signed apps can have pastSigningCertificates + if (hasPastSigningCertificates()) { + + // check all past certs, except for the current one, which automatically gets all + // capabilities, since it is the same as the current signature + for (int i = 0; i < pastSigningCertificates.length - 1; i++) { + if (pastSigningCertificates[i].equals(signature)) { + if (flags == PAST_CERT_EXISTS + || (flags & pastSigningCertificatesFlags[i]) == flags) { + return true; + } + } + } + } + + // not in previous certs signing history, just check the current signer and make sure + // we are singly-signed + return signatures.length == 1 && signatures[0].equals(signature); + } + + /** + * Determines if the provided {@code sha256String} is an ancestor of this one, and whether + * or not this one grants it the provided capability, represented by the {@code flags} + * parameter. In the event of signing certificate rotation, a package may still interact + * with entities signed by its old signing certificate and not want to break previously + * functioning behavior. The {@code flags} value determines which capabilities the app + * signed by the newer signing certificate would like to continue to give to its previous + * signing certificate(s). + * + * @param sha256String A hex-encoded representation of a sha256 digest. In the case of an + * app with multiple signers, this represents the hex-encoded sha256 + * digest of the combined hex-encoded sha256 digests of each individual + * signing certificate according to {@link + * PackageUtils#computeSignaturesSha256Digest(Signature[])} + */ + public boolean checkCapability(String sha256String, @CertCapabilities int flags) { + if (this == UNKNOWN) { + return false; + } + + // first see if the hash represents a single-signer in our signing history + byte[] sha256Bytes = ByteStringUtils.fromHexToByteArray(sha256String); + if (hasSha256Certificate(sha256Bytes, flags)) { + return true; + } + + // Not in signing history, either represents multiple signatures or not a match. + // Multiple signers can't rotate, so no need to check flags, just see if the SHAs match. + // We already check the single-signer case above as part of hasSha256Certificate, so no + // need to verify we have multiple signers, just run the old check + // just consider current signing certs + final String[] mSignaturesSha256Digests = + PackageUtils.computeSignaturesSha256Digests(signatures); + final String mSignaturesSha256Digest = + PackageUtils.computeSignaturesSha256Digest(mSignaturesSha256Digests); + return mSignaturesSha256Digest.equals(sha256String); + } + + /** + * Determine if the {@code sha256Certificate} is in this SigningDetails' signing certificate + * history, including the current signer. Automatically returns false if this object has + * multiple signing certificates, since rotation is only supported for single-signers. + */ + public boolean hasSha256Certificate(byte[] sha256Certificate) { + return hasSha256CertificateInternal(sha256Certificate, PAST_CERT_EXISTS); + } + + /** + * Determine if the {@code sha256Certificate} certificate hash corresponds to a signing + * certificate in this SigningDetails' signing certificate history, including the current + * signer, and whether or not it has the given permission. Certificates which match our + * current signer automatically get all capabilities. Automatically returns false if this + * object has multiple signing certificates, since rotation is only supported for + * single-signers. + */ + public boolean hasSha256Certificate(byte[] sha256Certificate, @CertCapabilities int flags) { + return hasSha256CertificateInternal(sha256Certificate, flags); + } + + private boolean hasSha256CertificateInternal(byte[] sha256Certificate, int flags) { + if (this == UNKNOWN) { + return false; + } + if (hasPastSigningCertificates()) { + + // check all past certs, except for the last one, which automatically gets all + // capabilities, since it is the same as the current signature, and is checked below + for (int i = 0; i < pastSigningCertificates.length - 1; i++) { + byte[] digest = PackageUtils.computeSha256DigestBytes( + pastSigningCertificates[i].toByteArray()); + if (Arrays.equals(sha256Certificate, digest)) { + if (flags == PAST_CERT_EXISTS + || (flags & pastSigningCertificatesFlags[i]) == flags) { + return true; + } + } + } + } + + // not in previous certs signing history, just check the current signer + if (signatures.length == 1) { + byte[] digest = + PackageUtils.computeSha256DigestBytes(signatures[0].toByteArray()); + return Arrays.equals(sha256Certificate, digest); + } + return false; + } + /** Returns true if the signatures in this and other match exactly. */ public boolean signaturesMatchExactly(SigningDetails other) { return Signature.areExactMatch(this.signatures, other.signatures); diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java index fdc54aedac9..a2a14eddd59 100644 --- a/core/java/android/content/pm/Signature.java +++ b/core/java/android/content/pm/Signature.java @@ -284,6 +284,29 @@ public class Signature implements Parcelable { return areExactMatch(aPrime, bPrime); } + /** + * Test if given {@link Signature} objects are effectively equal. In rare + * cases, certificates can have slightly malformed encoding which causes + * exact-byte checks to fail. + *

+ * To identify effective equality, we bounce the certificates through an + * decode/encode pass before doing the exact-byte check. To reduce attack + * surface area, we only allow a byte size delta of a few bytes. + * + * @throws CertificateException if the before/after length differs + * substantially, usually a signal of something fishy going on. + * @hide + */ + public static boolean areEffectiveMatch(Signature a, Signature b) + throws CertificateException { + final CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + final Signature aPrime = bounce(cf, a); + final Signature bPrime = bounce(cf, b); + + return aPrime.equals(bPrime); + } + /** * Bounce the given {@link Signature} through a decode/encode cycle. * diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java index af20cd77e62..30088dd9b12 100644 --- a/services/core/java/com/android/server/pm/InstantAppRegistry.java +++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java @@ -44,6 +44,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.Xml; + import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; @@ -51,6 +52,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -295,25 +297,26 @@ class InstantAppRegistry { continue; } + String cookieName = currentCookieFile.getName(); + String currentCookieSha256 = + cookieName.substring(INSTANT_APP_COOKIE_FILE_PREFIX.length(), + cookieName.length() - INSTANT_APP_COOKIE_FILE_SIFFIX.length()); + // Before we used only the first signature to compute the SHA 256 but some // apps could be singed by multiple certs and the cert order is undefined. // We prefer the modern computation procedure where all certs are taken // into account but also allow the value from the old computation to avoid // data loss. - final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests( - pkg.mSigningDetails.signatures); - final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest( - signaturesSha256Digests); - - // We prefer a match based on all signatures - if (currentCookieFile.equals(computeInstantCookieFile(pkg.packageName, - signaturesSha256Digest, userId))) { + if (pkg.mSigningDetails.checkCapability(currentCookieSha256, + PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) { return; } - // For backwards compatibility we accept match based on first signature - if (pkg.mSigningDetails.signatures.length > 1 && currentCookieFile.equals(computeInstantCookieFile( - pkg.packageName, signaturesSha256Digests[0], userId))) { + // For backwards compatibility we accept match based on first signature only in the case + // of multiply-signed packagse + final String[] signaturesSha256Digests = + PackageUtils.computeSignaturesSha256Digests(pkg.mSigningDetails.signatures); + if (signaturesSha256Digests[0].equals(currentCookieSha256)) { return; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index fdb157e7e92..941b4e67e6e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -108,8 +108,6 @@ import static com.android.server.pm.PackageManagerServiceUtils.dumpCriticalInfo; import static com.android.server.pm.PackageManagerServiceUtils.getCompressedFiles; import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime; import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; -import static com.android.server.pm.PackageManagerServiceUtils.signingDetailsHasCertificate; -import static com.android.server.pm.PackageManagerServiceUtils.signingDetailsHasSha256Certificate; import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures; import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE; import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS; @@ -246,6 +244,7 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Base64; +import android.util.ByteStringUtils; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.ExceptionUtils; @@ -5546,9 +5545,9 @@ Slog.e("TODD", } switch (type) { case CERT_INPUT_RAW_X509: - return signingDetailsHasCertificate(certificate, p.mSigningDetails); + return p.mSigningDetails.hasCertificate(certificate); case CERT_INPUT_SHA256: - return signingDetailsHasSha256Certificate(certificate, p.mSigningDetails); + return p.mSigningDetails.hasSha256Certificate(certificate); default: return false; } @@ -5587,9 +5586,9 @@ Slog.e("TODD", } switch (type) { case CERT_INPUT_RAW_X509: - return signingDetailsHasCertificate(certificate, signingDetails); + return signingDetails.hasCertificate(certificate); case CERT_INPUT_SHA256: - return signingDetailsHasSha256Certificate(certificate, signingDetails); + return signingDetails.hasSha256Certificate(certificate); default: return false; } @@ -8757,10 +8756,9 @@ Slog.e("TODD", // the application installed on /data. if (scanSystemPartition && !isSystemPkgUpdated && pkgAlreadyExists && !pkgSetting.isSystem()) { - // if the signatures don't match, wipe the installed application and its data - if (compareSignatures(pkgSetting.signatures.mSigningDetails.signatures, - pkg.mSigningDetails.signatures) - != PackageManager.SIGNATURE_MATCH) { + + if (!pkg.mSigningDetails.checkCapability(pkgSetting.signatures.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) { logCriticalInfo(Log.WARN, "System package signature mismatch;" + " name: " + pkgSetting.name); @@ -9725,34 +9723,51 @@ Slog.e("TODD", } final String[] expectedCertDigests = requiredCertDigests[i]; - // For apps targeting O MR1 we require explicit enumeration of all certs. - final String[] libCertDigests = (targetSdk > Build.VERSION_CODES.O) - ? PackageUtils.computeSignaturesSha256Digests( - libPkg.mSigningDetails.signatures) - : PackageUtils.computeSignaturesSha256Digests( - new Signature[]{libPkg.mSigningDetails.signatures[0]}); - - // Take a shortcut if sizes don't match. Note that if an app doesn't - // target O we don't parse the "additional-certificate" tags similarly - // how we only consider all certs only for apps targeting O (see above). - // Therefore, the size check is safe to make. - if (expectedCertDigests.length != libCertDigests.length) { - throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires differently signed" + - " static shared library; failing!"); - } - // Use a predictable order as signature order may vary - Arrays.sort(libCertDigests); - Arrays.sort(expectedCertDigests); - final int certCount = libCertDigests.length; - for (int j = 0; j < certCount; j++) { - if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) { + if (expectedCertDigests.length > 1) { + + // For apps targeting O MR1 we require explicit enumeration of all certs. + final String[] libCertDigests = (targetSdk > Build.VERSION_CODES.O) + ? PackageUtils.computeSignaturesSha256Digests( + libPkg.mSigningDetails.signatures) + : PackageUtils.computeSignaturesSha256Digests( + new Signature[]{libPkg.mSigningDetails.signatures[0]}); + + // Take a shortcut if sizes don't match. Note that if an app doesn't + // target O we don't parse the "additional-certificate" tags similarly + // how we only consider all certs only for apps targeting O (see above). + // Therefore, the size check is safe to make. + if (expectedCertDigests.length != libCertDigests.length) { throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, "Package " + packageName + " requires differently signed" + " static shared library; failing!"); } + + // Use a predictable order as signature order may vary + Arrays.sort(libCertDigests); + Arrays.sort(expectedCertDigests); + + final int certCount = libCertDigests.length; + for (int j = 0; j < certCount; j++) { + if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) { + throw new PackageManagerException( + INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires differently signed" + + " static shared library; failing!"); + } + } + } else { + + // lib signing cert could have rotated beyond the one expected, check to see + // if the new one has been blessed by the old + if (!libPkg.mSigningDetails.hasSha256Certificate( + ByteStringUtils.fromHexToByteArray(expectedCertDigests[0]))) { + throw new PackageManagerException( + INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires differently signed" + + " static shared library; failing!"); + } } } @@ -10169,6 +10184,15 @@ Slog.e("TODD", // We just determined the app is signed correctly, so bring // over the latest parsed certs. pkgSetting.signatures.mSigningDetails = pkg.mSigningDetails; + + + // if this is is a sharedUser, check to see if the new package is signed by a newer + // signing certificate than the existing one, and if so, copy over the new details + if (signatureCheckPs.sharedUser != null + && pkg.mSigningDetails.hasAncestor( + signatureCheckPs.sharedUser.signatures.mSigningDetails)) { + signatureCheckPs.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails; + } } catch (PackageManagerException e) { if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { throw e; @@ -10195,6 +10219,13 @@ Slog.e("TODD", String msg = "System package " + pkg.packageName + " signature changed; retaining data."; reportSettingsProblem(Log.WARN, msg); + } catch (IllegalArgumentException e) { + + // should never happen: certs matched when checking, but not when comparing + // old to new for sharedUser + throw new PackageManagerException(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, + "Signing certificates comparison made on incomparable signing details" + + " but somehow passed verifySignatures!"); } } @@ -16061,10 +16092,10 @@ Slog.e("TODD", return; } } else { + // default to original signature matching - if (compareSignatures(oldPackage.mSigningDetails.signatures, - pkg.mSigningDetails.signatures) - != PackageManager.SIGNATURE_MATCH) { + if (!pkg.mSigningDetails.checkCapability(oldPackage.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) { res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "New package has a different signature: " + pkgName); return; @@ -17058,9 +17089,25 @@ Slog.e("TODD", sourcePackageSetting, scanFlags))) { sigsOk = ksms.checkUpgradeKeySetLocked(sourcePackageSetting, pkg); } else { - sigsOk = compareSignatures( - sourcePackageSetting.signatures.mSigningDetails.signatures, - pkg.mSigningDetails.signatures) == PackageManager.SIGNATURE_MATCH; + + // in the event of signing certificate rotation, we need to see if the + // package's certificate has rotated from the current one, or if it is an + // older certificate with which the current is ok with sharing permissions + if (sourcePackageSetting.signatures.mSigningDetails.checkCapability( + pkg.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.PERMISSION)) { + sigsOk = true; + } else if (pkg.mSigningDetails.checkCapability( + sourcePackageSetting.signatures.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.PERMISSION)) { + + // the scanned package checks out, has signing certificate rotation + // history, and is newer; bring it over + sourcePackageSetting.signatures.mSigningDetails = pkg.mSigningDetails; + sigsOk = true; + } else { + sigsOk = false; + } } if (!sigsOk) { // If the owning package is the system itself, we log but allow diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 76c199b88a5..c02331da4ed 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -531,18 +531,29 @@ public class PackageManagerServiceUtils { // migrate the old signatures to the new scheme packageSignatures.mSigningDetails = parsedSignatures; return true; + } else if (parsedSignatures.hasPastSigningCertificates()) { + + // well this sucks: the parsed package has probably rotated signing certificates, but + // we don't have enough information to determine if the new signing certificate was + // blessed by the old one + logCriticalInfo(Log.INFO, "Existing package " + packageName + " has flattened signing " + + "certificate chain. Unable to install newer version with rotated signing " + + "certificate."); } return false; } - private static boolean matchSignaturesRecover(String packageName, - Signature[] existingSignatures, Signature[] parsedSignatures) { + private static boolean matchSignaturesRecover( + String packageName, + PackageParser.SigningDetails existingSignatures, + PackageParser.SigningDetails parsedSignatures, + @PackageParser.SigningDetails.CertCapabilities int flags) { String msg = null; try { - if (Signature.areEffectiveMatch(existingSignatures, parsedSignatures)) { - logCriticalInfo(Log.INFO, - "Recovered effectively matching certificates for " + packageName); - return true; + if (parsedSignatures.checkCapabilityRecover(existingSignatures, flags)) { + logCriticalInfo(Log.INFO, "Recovered effectively matching certificates for " + + packageName); + return true; } } catch (CertificateException e) { msg = e.getMessage(); @@ -563,9 +574,11 @@ public class PackageManagerServiceUtils { PackageSetting disabledPkgSetting) { try { PackageParser.collectCertificates(disabledPkgSetting.pkg, true /* skipVerify */); - if (compareSignatures(pkgSetting.signatures.mSigningDetails.signatures, - disabledPkgSetting.signatures.mSigningDetails.signatures) - != PackageManager.SIGNATURE_MATCH) { + if (pkgSetting.signatures.mSigningDetails.checkCapability( + disabledPkgSetting.signatures.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) { + return true; + } else { logCriticalInfo(Log.ERROR, "Updated system app mismatches cert on /system: " + pkgSetting.name); return false; @@ -575,69 +588,6 @@ public class PackageManagerServiceUtils { e.getMessage()); return false; } - return true; - } - - /** - * Checks the signing certificates to see if the provided certificate is a member. Invalid for - * {@code SigningDetails} with multiple signing certificates. - * @param certificate certificate to check for membership - * @param signingDetails signing certificates record whose members are to be searched - * @return true if {@code certificate} is in {@code signingDetails} - */ - public static boolean signingDetailsHasCertificate( - byte[] certificate, PackageParser.SigningDetails signingDetails) { - if (signingDetails == PackageParser.SigningDetails.UNKNOWN) { - return false; - } - Signature signature = new Signature(certificate); - if (signingDetails.hasPastSigningCertificates()) { - for (int i = 0; i < signingDetails.pastSigningCertificates.length; i++) { - if (signingDetails.pastSigningCertificates[i].equals(signature)) { - return true; - } - } - } else { - // no signing history, just check the current signer - if (signingDetails.signatures.length == 1 - && signingDetails.signatures[0].equals(signature)) { - return true; - } - } - return false; - } - - /** - * Checks the signing certificates to see if the provided certificate is a member. Invalid for - * {@code SigningDetails} with multiple signing certificaes. - * @param sha256Certificate certificate to check for membership - * @param signingDetails signing certificates record whose members are to be searched - * @return true if {@code certificate} is in {@code signingDetails} - */ - public static boolean signingDetailsHasSha256Certificate( - byte[] sha256Certificate, PackageParser.SigningDetails signingDetails ) { - if (signingDetails == PackageParser.SigningDetails.UNKNOWN) { - return false; - } - if (signingDetails.hasPastSigningCertificates()) { - for (int i = 0; i < signingDetails.pastSigningCertificates.length; i++) { - byte[] digest = PackageUtils.computeSha256DigestBytes( - signingDetails.pastSigningCertificates[i].toByteArray()); - if (Arrays.equals(sha256Certificate, digest)) { - return true; - } - } - } else { - // no signing history, just check the current signer - if (signingDetails.signatures.length == 1) { - byte[] digest = PackageUtils.computeSha256DigestBytes( - signingDetails.signatures[0].toByteArray()); - if (Arrays.equals(sha256Certificate, digest)) { - return true; - } - } - } - return false; } /** Returns true if APK Verity is enabled. */ @@ -662,10 +612,11 @@ public class PackageManagerServiceUtils { final String packageName = pkgSetting.name; boolean compatMatch = false; if (pkgSetting.signatures.mSigningDetails.signatures != null) { + // Already existing package. Make sure signatures match - boolean match = compareSignatures(pkgSetting.signatures.mSigningDetails.signatures, - parsedSignatures.signatures) - == PackageManager.SIGNATURE_MATCH; + boolean match = parsedSignatures.checkCapability( + pkgSetting.signatures.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA); if (!match && compareCompat) { match = matchSignaturesCompat(packageName, pkgSetting.signatures, parsedSignatures); @@ -673,8 +624,10 @@ public class PackageManagerServiceUtils { } if (!match && compareRecover) { match = matchSignaturesRecover( - packageName, pkgSetting.signatures.mSigningDetails.signatures, - parsedSignatures.signatures); + packageName, + pkgSetting.signatures.mSigningDetails, + parsedSignatures, + PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA); } if (!match && isApkVerificationForced(disabledPkgSetting)) { @@ -689,20 +642,35 @@ public class PackageManagerServiceUtils { } // Check for shared user signatures if (pkgSetting.sharedUser != null - && pkgSetting.sharedUser.signatures.mSigningDetails.signatures != null) { - // Already existing package. Make sure signatures match + && pkgSetting.sharedUser.signatures.mSigningDetails + != PackageParser.SigningDetails.UNKNOWN) { + + // Already existing package. Make sure signatures match. In case of signing certificate + // rotation, the packages with newer certs need to be ok with being sharedUserId with + // the older ones. We check to see if either the new package is signed by an older cert + // with which the current sharedUser is ok, or if it is signed by a newer one, and is ok + // with being sharedUser with the existing signing cert. boolean match = - compareSignatures( - pkgSetting.sharedUser.signatures.mSigningDetails.signatures, - parsedSignatures.signatures) == PackageManager.SIGNATURE_MATCH; + parsedSignatures.checkCapability( + pkgSetting.sharedUser.signatures.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID) + || pkgSetting.sharedUser.signatures.mSigningDetails.checkCapability( + parsedSignatures, + PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID); if (!match && compareCompat) { match = matchSignaturesCompat( packageName, pkgSetting.sharedUser.signatures, parsedSignatures); } if (!match && compareRecover) { - match = matchSignaturesRecover(packageName, - pkgSetting.sharedUser.signatures.mSigningDetails.signatures, - parsedSignatures.signatures); + match = + matchSignaturesRecover(packageName, + pkgSetting.sharedUser.signatures.mSigningDetails, + parsedSignatures, + PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID) + || matchSignaturesRecover(packageName, + parsedSignatures, + pkgSetting.sharedUser.signatures.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID); compatMatch |= match; } if (!match) { diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 18356c570d0..f14a684b386 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import android.content.pm.ApplicationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageParser; import android.content.pm.PackageUserState; import android.content.pm.Signature; import android.service.pm.PackageProto; @@ -236,6 +237,10 @@ public abstract class PackageSettingBase extends SettingBase { return signatures.mSigningDetails.signatures; } + public PackageParser.SigningDetails getSigningDetails() { + return signatures.mSigningDetails; + } + /** * Makes a shallow copy of the given package settings. * diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index 2552643a6a2..79f702b10d8 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -454,7 +454,11 @@ final class Policy { Signature[] certs = mCerts.toArray(new Signature[0]); if (pkg.mSigningDetails != SigningDetails.UNKNOWN && !Signature.areExactMatch(certs, pkg.mSigningDetails.signatures)) { - return null; + + // certs aren't exact match, but the package may have rotated from the known system cert + if (certs.length > 1 || !pkg.mSigningDetails.hasCertificate(certs[0])) { + return null; + } } // Check for inner package name matches given that the diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index e2123c25dd8..63087669b2f 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -1166,9 +1166,9 @@ public final class DefaultPermissionGrantPolicy { final String systemPackageName = mServiceInternal.getKnownPackageName( PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM); final PackageParser.Package systemPackage = getPackage(systemPackageName); - return compareSignatures(systemPackage.mSigningDetails.signatures, - pkg.mSigningDetails.signatures) - == PackageManager.SIGNATURE_MATCH; + return pkg.mSigningDetails.hasAncestorOrSelf(systemPackage.mSigningDetails) + || systemPackage.mSigningDetails.checkCapability(pkg.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.PERMISSION); } private void grantDefaultPermissionExceptions(int userId) { diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index cb3b1073a59..08f8bbd20af 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1022,12 +1022,24 @@ Slog.e(TAG, "TODD: Packages: " + Arrays.toString(packages)); PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM); final PackageParser.Package systemPackage = mPackageManagerInt.getPackage(systemPackageName); - boolean allowed = (PackageManagerServiceUtils.compareSignatures( - bp.getSourceSignatures(), pkg.mSigningDetails.signatures) - == PackageManager.SIGNATURE_MATCH) - || (PackageManagerServiceUtils.compareSignatures( - systemPackage.mSigningDetails.signatures, pkg.mSigningDetails.signatures) - == PackageManager.SIGNATURE_MATCH); + + // check if the package is allow to use this signature permission. A package is allowed to + // use a signature permission if: + // - it has the same set of signing certificates as the source package + // - or its signing certificate was rotated from the source package's certificate + // - or its signing certificate is a previous signing certificate of the defining + // package, and the defining package still trusts the old certificate for permissions + // - or it shares the above relationships with the system package + boolean allowed = + pkg.mSigningDetails.hasAncestorOrSelf( + bp.getSourcePackageSetting().getSigningDetails()) + || bp.getSourcePackageSetting().getSigningDetails().checkCapability( + pkg.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.PERMISSION) + || pkg.mSigningDetails.hasAncestorOrSelf(systemPackage.mSigningDetails) + || systemPackage.mSigningDetails.checkCapability( + pkg.mSigningDetails, + PackageParser.SigningDetails.CertCapabilities.PERMISSION); if (!allowed && (privilegedPermission || oemPermission)) { if (pkg.isSystem()) { // For updated system applications, a privileged/oem permission -- GitLab From b8ef541cbc61ce11bd68106ff431752de86f761f Mon Sep 17 00:00:00 2001 From: Benedict Wong Date: Wed, 24 Jan 2018 15:31:39 -0800 Subject: [PATCH 141/416] Fix minor bugs with tunnel mode implementation This change makes sure tunnel mode transforms are properly activated upon construction, and corrects bugs with how policy selectors were being generated for tunnel mode policies. Specifically, the source/destination could not be empty strings, even for cases where an empty selector was desired. Bug: 72457770 Test: GTS tests run Change-Id: I9a9f64c34b07883a02a5c996614f958486d214fc --- core/java/android/net/IpSecTransform.java | 2 +- .../java/com/android/server/IpSecService.java | 57 +++++++++++-------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java index 9ccdbe2b1bd..0829b4a3e9f 100644 --- a/core/java/android/net/IpSecTransform.java +++ b/core/java/android/net/IpSecTransform.java @@ -462,7 +462,7 @@ public final class IpSecTransform implements AutoCloseable { mConfig.setMode(MODE_TUNNEL); mConfig.setSourceAddress(sourceAddress.getHostAddress()); mConfig.setSpiResourceId(spi.getResourceId()); - return new IpSecTransform(mContext, mConfig); + return new IpSecTransform(mContext, mConfig).activate(); } /** diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java index fe4ac6d771b..a07a982abc5 100644 --- a/services/core/java/com/android/server/IpSecService.java +++ b/services/core/java/com/android/server/IpSecService.java @@ -87,6 +87,7 @@ public class IpSecService extends IIpSecService.Stub { private static final String NETD_SERVICE_NAME = "netd"; private static final int[] DIRECTIONS = new int[] {IpSecManager.DIRECTION_OUT, IpSecManager.DIRECTION_IN}; + private static final String[] WILDCARD_ADDRESSES = new String[]{"0.0.0.0", "::"}; private static final int NETD_FETCH_TIMEOUT_MS = 5000; // ms private static final int MAX_PORT_BIND_ATTEMPTS = 10; @@ -413,12 +414,16 @@ public class IpSecService extends IIpSecService.Stub { .append(mTransformQuotaTracker) .append(", mSocketQuotaTracker=") .append(mSocketQuotaTracker) + .append(", mTunnelQuotaTracker=") + .append(mTunnelQuotaTracker) .append(", mSpiRecords=") .append(mSpiRecords) .append(", mTransformRecords=") .append(mTransformRecords) .append(", mEncapSocketRecords=") .append(mEncapSocketRecords) + .append(", mTunnelInterfaceRecords=") + .append(mTunnelInterfaceRecords) .append("}") .toString(); } @@ -815,12 +820,14 @@ public class IpSecService extends IIpSecService.Stub { try { mSrvConfig.getNetdInstance().removeVirtualTunnelInterface(mInterfaceName); - for (int direction : DIRECTIONS) { - int mark = (direction == IpSecManager.DIRECTION_IN) ? mIkey : mOkey; - mSrvConfig - .getNetdInstance() - .ipSecDeleteSecurityPolicy( - 0, direction, mLocalAddress, mRemoteAddress, mark, 0xffffffff); + for(String wildcardAddr : WILDCARD_ADDRESSES) { + for (int direction : DIRECTIONS) { + int mark = (direction == IpSecManager.DIRECTION_IN) ? mIkey : mOkey; + mSrvConfig + .getNetdInstance() + .ipSecDeleteSecurityPolicy( + 0, direction, wildcardAddr, wildcardAddr, mark, 0xffffffff); + } } } catch (ServiceSpecificException e) { // FIXME: get the error code and throw is at an IOException from Errno Exception @@ -1261,19 +1268,21 @@ public class IpSecService extends IIpSecService.Stub { .getNetdInstance() .addVirtualTunnelInterface(intfName, localAddr, remoteAddr, ikey, okey); - for (int direction : DIRECTIONS) { - int mark = (direction == IpSecManager.DIRECTION_OUT) ? okey : ikey; + for(String wildcardAddr : WILDCARD_ADDRESSES) { + for (int direction : DIRECTIONS) { + int mark = (direction == IpSecManager.DIRECTION_OUT) ? okey : ikey; - mSrvConfig - .getNetdInstance() - .ipSecAddSecurityPolicy( + mSrvConfig + .getNetdInstance() + .ipSecAddSecurityPolicy( 0, // Use 0 for reqId direction, - "", - "", + wildcardAddr, + wildcardAddr, 0, mark, 0xffffffff); + } } userRecord.mTunnelInterfaceRecords.put( @@ -1646,16 +1655,18 @@ public class IpSecService extends IIpSecService.Stub { c.setNetwork(tunnelInterfaceInfo.getUnderlyingNetwork()); // If outbound, also add SPI to the policy. - mSrvConfig - .getNetdInstance() - .ipSecUpdateSecurityPolicy( - 0, // Use 0 for reqId - direction, - "", - "", - transformInfo.getSpiRecord().getSpi(), - mark, - 0xffffffff); + for(String wildcardAddr : WILDCARD_ADDRESSES) { + mSrvConfig + .getNetdInstance() + .ipSecUpdateSecurityPolicy( + 0, // Use 0 for reqId + direction, + wildcardAddr, + wildcardAddr, + transformInfo.getSpiRecord().getSpi(), + mark, + 0xffffffff); + } } // Update SA with tunnel mark (ikey or okey based on direction) -- GitLab From 39b3d4cac119b011544294f1c98fc6f40a9753e1 Mon Sep 17 00:00:00 2001 From: Vladislav Kaznacheev Date: Tue, 30 Jan 2018 17:32:04 -0800 Subject: [PATCH 142/416] Fix layout for menu item checkbox and submenu arrow. Use RelativeLayout for title and shortcut, the way it used to be for a long time before ag/2765657. Move the comments in the layout file to correct places. Bug: 72712956 Test: manual Change-Id: I3539cf1cbba67f33bd3cc9c6b81d7e946ad70bc9 --- .../res/res/layout/popup_menu_item_layout.xml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/core/res/res/layout/popup_menu_item_layout.xml b/core/res/res/layout/popup_menu_item_layout.xml index 3b89f0d0cde..1be915e809e 100644 --- a/core/res/res/layout/popup_menu_item_layout.xml +++ b/core/res/res/layout/popup_menu_item_layout.xml @@ -28,18 +28,18 @@ android:layout_marginBottom="4dip" android:background="@drawable/list_divider_material" /> - - - - + + + - + - - + + + -- GitLab From 75edab1e74b1a34af90b12c4707f0abb0a3d7cbc Mon Sep 17 00:00:00 2001 From: Suprabh Shukla Date: Mon, 29 Jan 2018 14:09:06 -0800 Subject: [PATCH 143/416] Throttling alarms for a package per its standby bucket Packages can now get to run alarms only if a certain amount of time has elapsed since they last go to do so. The time interval is decided based on the current app standby bucket the package is in. So, frequently used apps get to run alarms more frequently than the rarely used ones Test: atest CtsAlarmManagerTestCases or atest CtsAlarmManagerTestCases:AppStandbyTests Bug: 72660630 Change-Id: Ib89c81e8166eab4c985152e01178da61f8a880f7 --- .../android/server/AlarmManagerService.java | 228 ++++++++++++++++-- .../server/job/JobSchedulerService.java | 7 +- 2 files changed, 208 insertions(+), 27 deletions(-) diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 342b48ee7f7..30dfee86403 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -16,6 +16,7 @@ package com.android.server; +import android.annotation.UserIdInt; import android.app.Activity; import android.app.ActivityManager; import android.app.AlarmManager; @@ -26,6 +27,8 @@ import android.app.IAlarmListener; import android.app.IAlarmManager; import android.app.IUidObserver; import android.app.PendingIntent; +import android.app.usage.UsageStatsManager; +import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -57,6 +60,7 @@ import android.text.format.DateFormat; import android.util.ArrayMap; import android.util.KeyValueListParser; import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -75,6 +79,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.Locale; import java.util.Random; @@ -120,6 +125,7 @@ class AlarmManagerService extends SystemService { static final boolean DEBUG_LISTENER_CALLBACK = localLOGV || false; static final boolean DEBUG_WAKELOCK = localLOGV || false; static final boolean DEBUG_BG_LIMIT = localLOGV || false; + static final boolean DEBUG_STANDBY = localLOGV || false; static final boolean RECORD_ALARMS_IN_HISTORY = true; static final boolean RECORD_DEVICE_IDLE_ALARMS = false; static final int ALARM_EVENT = 1; @@ -140,6 +146,7 @@ class AlarmManagerService extends SystemService { AppOpsManager mAppOps; DeviceIdleController.LocalService mLocalDeviceIdleController; + private UsageStatsManagerInternal mUsageStatsManagerInternal; final Object mLock = new Object(); @@ -215,6 +222,14 @@ class AlarmManagerService extends SystemService { } final ArrayList mAllowWhileIdleDispatches = new ArrayList(); + interface Stats { + int REBATCH_ALL_ALARMS = 0; + } + + private final StatLogger mStatLogger = new StatLogger(new String[] { + "REBATCH_ALL_ALARMS", + }); + /** * Broadcast options to use for FLAG_ALLOW_WHILE_IDLE. */ @@ -233,6 +248,8 @@ class AlarmManagerService extends SystemService { new SparseArray<>(); private final ForceAppStandbyTracker mForceAppStandbyTracker; + private boolean mAppStandbyParole; + private ArrayMap, Long> mLastAlarmDeliveredForPackage = new ArrayMap<>(); /** * All times are in milliseconds. These constants are kept synchronized with the system @@ -249,13 +266,28 @@ class AlarmManagerService extends SystemService { = "allow_while_idle_whitelist_duration"; private static final String KEY_LISTENER_TIMEOUT = "listener_timeout"; + // Keys for specifying throttling delay based on app standby bucketing + private final String[] KEYS_APP_STANDBY_DELAY = { + "standby_active_delay", + "standby_working_delay", + "standby_frequent_delay", + "standby_rare_delay", + "standby_never_delay", + }; + private static final long DEFAULT_MIN_FUTURITY = 5 * 1000; private static final long DEFAULT_MIN_INTERVAL = 60 * 1000; private static final long DEFAULT_ALLOW_WHILE_IDLE_SHORT_TIME = DEFAULT_MIN_FUTURITY; private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000; private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000; - private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000; + private final long[] DEFAULT_APP_STANDBY_DELAYS = { + 0, // Active + 6 * 60_000, // Working + 30 * 60_000, // Frequent + 2 * 60 * 60_000, // Rare + 10 * 24 * 60 * 60_000 // Never + }; // Minimum futurity of a new alarm public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY; @@ -276,6 +308,8 @@ class AlarmManagerService extends SystemService { // Direct alarm listener callback timeout public long LISTENER_TIMEOUT = DEFAULT_LISTENER_TIMEOUT; + public long[] APP_STANDBY_MIN_DELAYS = new long[DEFAULT_APP_STANDBY_DELAYS.length]; + private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); private long mLastAllowWhileIdleWhitelistDuration = -1; @@ -328,7 +362,12 @@ class AlarmManagerService extends SystemService { DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION); LISTENER_TIMEOUT = mParser.getLong(KEY_LISTENER_TIMEOUT, DEFAULT_LISTENER_TIMEOUT); - + APP_STANDBY_MIN_DELAYS[0] = mParser.getDurationMillis(KEYS_APP_STANDBY_DELAY[0], + DEFAULT_APP_STANDBY_DELAYS[0]); + for (int i = 1; i < KEYS_APP_STANDBY_DELAY.length; i++) { + APP_STANDBY_MIN_DELAYS[i] = mParser.getDurationMillis(KEYS_APP_STANDBY_DELAY[i], + Math.max(APP_STANDBY_MIN_DELAYS[i-1], DEFAULT_APP_STANDBY_DELAYS[i])); + } updateAllowWhileIdleWhitelistDurationLocked(); } } @@ -359,6 +398,12 @@ class AlarmManagerService extends SystemService { pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION); pw.print("="); TimeUtils.formatDuration(ALLOW_WHILE_IDLE_WHITELIST_DURATION, pw); pw.println(); + + for (int i = 0; i < KEYS_APP_STANDBY_DELAY.length; i++) { + pw.print(" "); pw.print(KEYS_APP_STANDBY_DELAY[i]); pw.print("="); + TimeUtils.formatDuration(APP_STANDBY_MIN_DELAYS[i], pw); + pw.println(); + } } void dumpProto(ProtoOutputStream proto, long fieldId) { @@ -618,9 +663,7 @@ class AlarmManagerService extends SystemService { } PriorityClass packagePrio = a.priorityClass; - String alarmPackage = (a.operation != null) - ? a.operation.getCreatorPackage() - : a.packageName; + String alarmPackage = a.sourcePackage; if (packagePrio == null) packagePrio = mPriorities.get(alarmPackage); if (packagePrio == null) { packagePrio = a.priorityClass = new PriorityClass(); // lowest prio & stale sequence @@ -751,6 +794,7 @@ class AlarmManagerService extends SystemService { } void rebatchAllAlarmsLocked(boolean doValidate) { + long start = mStatLogger.getTime(); final int oldCount = getAlarmCount(mAlarmBatches) + ArrayUtils.size(mPendingWhileIdleAlarms); final boolean oldHasTick = haveBatchesTimeTickAlarm(mAlarmBatches) @@ -790,6 +834,7 @@ class AlarmManagerService extends SystemService { rescheduleKernelAlarmsLocked(); updateNextAlarmClockLocked(); + mStatLogger.logDurationStat(Stats.REBATCH_ALL_ALARMS, start); } void reAddAlarmLocked(Alarm a, long nowElapsed, boolean doValidate) { @@ -905,7 +950,7 @@ class AlarmManagerService extends SystemService { // Recurring alarms may have passed several alarm intervals while the // alarm was kept pending. Send the appropriate trigger count. if (alarm.repeatInterval > 0) { - alarm.count += (nowELAPSED - alarm.whenElapsed) / alarm.repeatInterval; + alarm.count += (nowELAPSED - alarm.requestedWhenElapsed) / alarm.repeatInterval; // Also schedule its next recurrence final long delta = alarm.count * alarm.repeatInterval; final long nextElapsed = alarm.whenElapsed + delta; @@ -1228,6 +1273,8 @@ class AlarmManagerService extends SystemService { mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); mLocalDeviceIdleController = LocalServices.getService(DeviceIdleController.LocalService.class); + mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class); + mUsageStatsManagerInternal.addAppIdleStateChangeListener(new AppStandbyTracker()); } } @@ -1377,6 +1424,51 @@ class AlarmManagerService extends SystemService { setImplLocked(a, false, doValidate); } + private long getMinDelayForBucketLocked(int bucket) { + // Return the minimum time that should elapse before an app in the specified bucket + // can receive alarms again + if (bucket == UsageStatsManager.STANDBY_BUCKET_NEVER) { + return mConstants.APP_STANDBY_MIN_DELAYS[4]; + } + else if (bucket >= UsageStatsManager.STANDBY_BUCKET_RARE) { + return mConstants.APP_STANDBY_MIN_DELAYS[3]; + } + else if (bucket >= UsageStatsManager.STANDBY_BUCKET_FREQUENT) { + return mConstants.APP_STANDBY_MIN_DELAYS[2]; + } + else if (bucket >= UsageStatsManager.STANDBY_BUCKET_WORKING_SET) { + return mConstants.APP_STANDBY_MIN_DELAYS[1]; + } + else return mConstants.APP_STANDBY_MIN_DELAYS[0]; + } + + private void adjustDeliveryTimeBasedOnStandbyBucketLocked(Alarm alarm) { + if (alarm.alarmClock != null || UserHandle.isCore(alarm.creatorUid)) { + return; + } + if (mAppStandbyParole) { + if (alarm.whenElapsed > alarm.requestedWhenElapsed) { + // We did throttle this alarm earlier, restore original requirements + alarm.whenElapsed = alarm.requestedWhenElapsed; + alarm.maxWhenElapsed = alarm.requestedMaxWhenElapsed; + } + return; + } + final String sourcePackage = alarm.sourcePackage; + final int sourceUserId = UserHandle.getUserId(alarm.creatorUid); + final int standbyBucket = mUsageStatsManagerInternal.getAppStandbyBucket( + sourcePackage, sourceUserId, SystemClock.elapsedRealtime()); + + final Pair packageUser = Pair.create(sourcePackage, sourceUserId); + final long lastElapsed = mLastAlarmDeliveredForPackage.getOrDefault(packageUser, 0L); + if (lastElapsed > 0) { + final long minElapsed = lastElapsed + getMinDelayForBucketLocked(standbyBucket); + if (alarm.requestedWhenElapsed < minElapsed) { + alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed; + } + } + } + private void setImplLocked(Alarm a, boolean rebatching, boolean doValidate) { if ((a.flags&AlarmManager.FLAG_IDLE_UNTIL) != 0) { // This is a special alarm that will put the system into idle until it goes off. @@ -1428,6 +1520,7 @@ class AlarmManagerService extends SystemService { mAllowWhileIdleDispatches.add(ent); } } + adjustDeliveryTimeBasedOnStandbyBucketLocked(a); int whichBatch = ((a.flags&AlarmManager.FLAG_STANDALONE) != 0) ? -1 : attemptCoalesceLocked(a.whenElapsed, a.maxWhenElapsed); @@ -1655,6 +1748,9 @@ class AlarmManagerService extends SystemService { mForceAppStandbyTracker.dump(pw, " "); pw.println(); + pw.println(" App Standby Parole: " + mAppStandbyParole); + pw.println(); + final long nowRTC = System.currentTimeMillis(); final long nowELAPSED = SystemClock.elapsedRealtime(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); @@ -1753,6 +1849,15 @@ class AlarmManagerService extends SystemService { } pw.println("]"); + pw.println(" mLastAlarmDeliveredForPackage:"); + for (int i = 0; i < mLastAlarmDeliveredForPackage.size(); i++) { + Pair packageUser = mLastAlarmDeliveredForPackage.keyAt(i); + pw.print(" Package " + packageUser.first + ", User " + packageUser.second + ":"); + TimeUtils.formatDuration(mLastAlarmDeliveredForPackage.valueAt(i), nowELAPSED, pw); + pw.println(); + } + pw.println(); + if (mPendingIdleUntil != null || mPendingWhileIdleAlarms.size() > 0) { pw.println(); pw.println(" Idle mode state:"); @@ -1913,6 +2018,8 @@ class AlarmManagerService extends SystemService { } } } + pw.println(); + mStatLogger.dump(pw, " "); if (RECORD_DEVICE_IDLE_ALARMS) { pw.println(); @@ -2746,8 +2853,7 @@ class AlarmManagerService extends SystemService { // Don't block starting foreground components return false; } - final String sourcePackage = - (alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName; + final String sourcePackage = alarm.sourcePackage; final int sourceUid = alarm.creatorUid; return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage, allowWhileIdle); @@ -2856,7 +2962,7 @@ class AlarmManagerService extends SystemService { if (alarm.repeatInterval > 0) { // this adjustment will be zero if we're late by // less than one full repeat interval - alarm.count += (nowELAPSED - alarm.whenElapsed) / alarm.repeatInterval; + alarm.count += (nowELAPSED - alarm.requestedWhenElapsed) / alarm.repeatInterval; // Also schedule its next recurrence final long delta = alarm.count * alarm.repeatInterval; @@ -2925,11 +3031,14 @@ class AlarmManagerService extends SystemService { public final int uid; public final int creatorUid; public final String packageName; + public final String sourcePackage; public int count; public long when; public long windowLength; public long whenElapsed; // 'when' in the elapsed time base public long maxWhenElapsed; // also in the elapsed time base + public final long requestedWhenElapsed; // original expiry time requested by the app + public final long requestedMaxWhenElapsed; public long repeatInterval; public PriorityClass priorityClass; @@ -2943,8 +3052,10 @@ class AlarmManagerService extends SystemService { || _type == AlarmManager.RTC_WAKEUP; when = _when; whenElapsed = _whenElapsed; + requestedWhenElapsed = _whenElapsed; windowLength = _windowLength; maxWhenElapsed = _maxWhen; + requestedMaxWhenElapsed = _maxWhen; repeatInterval = _interval; operation = _op; listener = _rec; @@ -2955,7 +3066,7 @@ class AlarmManagerService extends SystemService { alarmClock = _info; uid = _uid; packageName = _pkgName; - + sourcePackage = (operation != null) ? operation.getCreatorPackage() : packageName; creatorUid = (operation != null) ? operation.getCreatorUid() : uid; } @@ -2980,9 +3091,7 @@ class AlarmManagerService extends SystemService { } public boolean matches(String packageName) { - return (operation != null) - ? packageName.equals(operation.getTargetPackage()) - : packageName.equals(this.packageName); + return packageName.equals(sourcePackage); } @Override @@ -2995,11 +3104,7 @@ class AlarmManagerService extends SystemService { sb.append(" when "); sb.append(when); sb.append(" "); - if (operation != null) { - sb.append(operation.getTargetPackage()); - } else { - sb.append(packageName); - } + sb.append(sourcePackage); sb.append('}'); return sb.toString(); } @@ -3009,6 +3114,8 @@ class AlarmManagerService extends SystemService { final boolean isRtc = (type == RTC || type == RTC_WAKEUP); pw.print(prefix); pw.print("tag="); pw.println(statsTag); pw.print(prefix); pw.print("type="); pw.print(type); + pw.print(" requestedWhenELapsed="); TimeUtils.formatDuration( + requestedWhenElapsed, nowELAPSED, pw); pw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed, nowELAPSED, pw); pw.print(" when="); @@ -3249,8 +3356,6 @@ class AlarmManagerService extends SystemService { // alarms, we need to merge them in to the list. note we don't // just deliver them first because we generally want non-wakeup // alarms delivered after wakeup alarms. - rescheduleKernelAlarmsLocked(); - updateNextAlarmClockLocked(); if (mPendingNonWakeupAlarms.size() > 0) { calculateDeliveryPriorities(mPendingNonWakeupAlarms); triggerList.addAll(mPendingNonWakeupAlarms); @@ -3262,6 +3367,27 @@ class AlarmManagerService extends SystemService { } mPendingNonWakeupAlarms.clear(); } + boolean needRebatch = false; + final HashSet triggerPackages = new HashSet<>(); + for (int i = triggerList.size() - 1; i >= 0; i--) { + triggerPackages.add(triggerList.get(i).sourcePackage); + } + outer: + for (int i = 0; i < mAlarmBatches.size(); i++) { + final Batch batch = mAlarmBatches.get(i); + for (int j = 0; j < batch.size(); j++) { + if (triggerPackages.contains(batch.get(j))) { + needRebatch = true; + break outer; + } + } + } + if (needRebatch) { + rebatchAllAlarmsLocked(false); + } else { + rescheduleKernelAlarmsLocked(); + updateNextAlarmClockLocked(); + } deliverAlarmsLocked(triggerList, nowELAPSED); } } @@ -3318,6 +3444,8 @@ class AlarmManagerService extends SystemService { public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 2; public static final int LISTENER_TIMEOUT = 3; public static final int REPORT_ALARMS_ACTIVE = 4; + public static final int APP_STANDBY_BUCKET_CHANGED = 5; + public static final int APP_STANDBY_PAROLE_CHANGED = 6; public AlarmHandler() { } @@ -3363,6 +3491,19 @@ class AlarmManagerService extends SystemService { } break; + case APP_STANDBY_PAROLE_CHANGED: + synchronized (mLock) { + mAppStandbyParole = (Boolean) msg.obj; + rebatchAllAlarmsLocked(false); + } + break; + + case APP_STANDBY_BUCKET_CHANGED: + synchronized (mLock) { + rebatchAllAlarmsLocked(false); + } + break; + default: // nope, just ignore it break; @@ -3489,6 +3630,13 @@ class AlarmManagerService extends SystemService { int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); if (userHandle >= 0) { removeUserLocked(userHandle); + for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) { + final Pair packageUser = + mLastAlarmDeliveredForPackage.keyAt(i); + if (packageUser.second == userHandle) { + mLastAlarmDeliveredForPackage.removeAt(i); + } + } } } else if (Intent.ACTION_UID_REMOVED.equals(action)) { if (uid >= 0) { @@ -3509,6 +3657,13 @@ class AlarmManagerService extends SystemService { } } if (pkgList != null && (pkgList.length > 0)) { + for (int i = mLastAlarmDeliveredForPackage.size() - 1; i >= 0; i--) { + Pair packageUser = mLastAlarmDeliveredForPackage.keyAt(i); + if (ArrayUtils.contains(pkgList, packageUser.first) + && packageUser.second == UserHandle.getUserId(uid)) { + mLastAlarmDeliveredForPackage.removeAt(i); + } + } for (String pkg : pkgList) { if (uid >= 0) { // package-removed case @@ -3563,6 +3718,33 @@ class AlarmManagerService extends SystemService { } }; + /** + * Tracking of app assignments to standby buckets + */ + final class AppStandbyTracker extends UsageStatsManagerInternal.AppIdleStateChangeListener { + @Override + public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId, + boolean idle, int bucket) { + if (DEBUG_STANDBY) { + Slog.d(TAG, "Package " + packageName + " for user " + userId + " now in bucket " + + bucket); + } + mHandler.removeMessages(AlarmHandler.APP_STANDBY_BUCKET_CHANGED); + mHandler.sendEmptyMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED); + } + + @Override + public void onParoleStateChanged(boolean isParoleOn) { + if (DEBUG_STANDBY) { + Slog.d(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF")); + } + mHandler.removeMessages(AlarmHandler.APP_STANDBY_BUCKET_CHANGED); + mHandler.removeMessages(AlarmHandler.APP_STANDBY_PAROLE_CHANGED); + mHandler.obtainMessage(AlarmHandler.APP_STANDBY_PAROLE_CHANGED, + Boolean.valueOf(isParoleOn)).sendToTarget(); + } + }; + private final Listener mForceAppStandbyListener = new Listener() { @Override public void unblockAllUnrestrictedAlarms() { @@ -3841,7 +4023,6 @@ class AlarmManagerService extends SystemService { alarm.packageName, alarm.type, alarm.statsTag, nowELAPSED); mInFlight.add(inflight); mBroadcastRefCount++; - if (allowWhileIdle) { // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm. mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED); @@ -3860,6 +4041,11 @@ class AlarmManagerService extends SystemService { mAllowWhileIdleDispatches.add(ent); } } + if (!UserHandle.isCore(alarm.creatorUid)) { + final Pair packageUser = Pair.create(alarm.sourcePackage, + UserHandle.getUserId(alarm.creatorUid)); + mLastAlarmDeliveredForPackage.put(packageUser, nowELAPSED); + } final BroadcastStats bs = inflight.mBroadcastStats; bs.count++; diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 0a21f12c1f8..5dce34aa631 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -2204,12 +2204,7 @@ public final class JobSchedulerService extends com.android.server.SystemService Slog.i(TAG, "Moving uid " + uid + " to bucketIndex " + bucketIndex); } synchronized (mLock) { - // TODO: update to be more efficient once we can slice by source UID - mJobs.forEachJob((JobStatus job) -> { - if (job.getSourceUid() == uid) { - job.setStandbyBucket(bucketIndex); - } - }); + mJobs.forEachJobForSourceUid(uid, job -> job.setStandbyBucket(bucketIndex)); onControllerStateChanged(); } }); -- GitLab From 860b8ba71938e9860a31881c1d1431877f9d01a2 Mon Sep 17 00:00:00 2001 From: Patrick Baumann Date: Wed, 31 Jan 2018 01:33:50 +0000 Subject: [PATCH 144/416] Revert "Adds generic intent Instant App resolution" This reverts commit 3e8bd0f3b5ffab9a07189ed3ebcc6c4437778a0e. Reason for revert: b/72710855 Change-Id: I1378ccb5c5c16256e472e1ff7c3ad2460e091300 Fixes: 72710855 --- api/current.txt | 1 - api/system-current.txt | 11 +- .../android/app/EphemeralResolverService.java | 12 + .../java/android/app/IInstantAppResolver.aidl | 8 +- .../app/InstantAppResolverService.java | 107 +++--- core/java/android/content/Intent.java | 50 +-- .../content/pm/AuxiliaryResolveInfo.java | 99 ++---- .../content/pm/InstantAppResolveInfo.java | 96 +----- .../internal/app/ResolverListController.java | 13 +- .../server/am/ActivityStackSupervisor.java | 10 +- .../android/server/am/ActivityStarter.java | 30 +- .../pm/EphemeralResolverConnection.java | 28 +- .../android/server/pm/InstantAppResolver.java | 322 ++++++------------ .../server/pm/PackageManagerService.java | 182 +++++----- 14 files changed, 324 insertions(+), 645 deletions(-) diff --git a/api/current.txt b/api/current.txt index a8cc27cd63b..7a54e6d49a9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10014,7 +10014,6 @@ package android.content { field public static final int FLAG_ACTIVITY_FORWARD_RESULT = 33554432; // 0x2000000 field public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 1048576; // 0x100000 field public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 4096; // 0x1000 - field public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 2048; // 0x800 field public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; // 0x8000000 field public static final int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; // 0x80000 field public static final int FLAG_ACTIVITY_NEW_TASK = 268435456; // 0x10000000 diff --git a/api/system-current.txt b/api/system-current.txt index ed3897c7a8b..7109b5d8b1a 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -310,10 +310,8 @@ package android.app { ctor public InstantAppResolverService(); method public final void attachBaseContext(android.content.Context); method public final android.os.IBinder onBind(android.content.Intent); - method public deprecated void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); - method public void onGetInstantAppIntentFilter(android.content.Intent, int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); - method public deprecated void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); - method public void onGetInstantAppResolveInfo(android.content.Intent, int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); + method public void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); + method public void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); } public static final class InstantAppResolverService.InstantAppResolutionCallback { @@ -822,14 +820,12 @@ package android.content { field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST"; field public static final java.lang.String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS"; field public static final java.lang.String EXTRA_FORCE_FACTORY_RESET = "android.intent.extra.FORCE_FACTORY_RESET"; - field public static final java.lang.String EXTRA_INSTANT_APP_BUNDLES = "android.intent.extra.INSTANT_APP_BUNDLES"; field public static final java.lang.String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID"; field public static final java.lang.String EXTRA_PACKAGES = "android.intent.extra.PACKAGES"; field public static final java.lang.String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; field public static final java.lang.String EXTRA_REASON = "android.intent.extra.REASON"; field public static final java.lang.String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK"; field public static final java.lang.String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED"; - field public static final java.lang.String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP"; } public class IntentFilter implements android.os.Parcelable { @@ -872,7 +868,6 @@ package android.content.pm { ctor public InstantAppResolveInfo(android.content.pm.InstantAppResolveInfo.InstantAppDigest, java.lang.String, java.util.List, int); ctor public InstantAppResolveInfo(android.content.pm.InstantAppResolveInfo.InstantAppDigest, java.lang.String, java.util.List, long, android.os.Bundle); ctor public InstantAppResolveInfo(java.lang.String, java.lang.String, java.util.List); - ctor public InstantAppResolveInfo(android.os.Bundle); method public int describeContents(); method public byte[] getDigestBytes(); method public int getDigestPrefix(); @@ -881,7 +876,6 @@ package android.content.pm { method public long getLongVersionCode(); method public java.lang.String getPackageName(); method public deprecated int getVersionCode(); - method public boolean shouldLetInstallerDecide(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -893,7 +887,6 @@ package android.content.pm { method public int[] getDigestPrefix(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; - field public static final android.content.pm.InstantAppResolveInfo.InstantAppDigest UNDEFINED; } public final class IntentFilterVerificationInfo implements android.os.Parcelable { diff --git a/core/java/android/app/EphemeralResolverService.java b/core/java/android/app/EphemeralResolverService.java index d1c244136ec..427a0386e87 100644 --- a/core/java/android/app/EphemeralResolverService.java +++ b/core/java/android/app/EphemeralResolverService.java @@ -17,10 +17,20 @@ package android.app; import android.annotation.SystemApi; +import android.app.Service; +import android.app.InstantAppResolverService.InstantAppResolutionCallback; +import android.content.Context; +import android.content.Intent; import android.content.pm.EphemeralResolveInfo; import android.content.pm.InstantAppResolveInfo; import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; import android.util.Log; import java.util.ArrayList; @@ -75,6 +85,7 @@ public abstract class EphemeralResolverService extends InstantAppResolverService return super.getLooper(); } + @Override void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, InstantAppResolutionCallback callback) { if (DEBUG_EPHEMERAL) { @@ -90,6 +101,7 @@ public abstract class EphemeralResolverService extends InstantAppResolverService callback.onInstantAppResolveInfo(resultList); } + @Override void _onGetInstantAppIntentFilter(int[] digestPrefix, String token, String hostName, InstantAppResolutionCallback callback) { if (DEBUG_EPHEMERAL) { diff --git a/core/java/android/app/IInstantAppResolver.aidl b/core/java/android/app/IInstantAppResolver.aidl index ae200578d82..805d8c057d2 100644 --- a/core/java/android/app/IInstantAppResolver.aidl +++ b/core/java/android/app/IInstantAppResolver.aidl @@ -16,15 +16,13 @@ package android.app; -import android.content.Intent; import android.os.IRemoteCallback; /** @hide */ oneway interface IInstantAppResolver { - void getInstantAppResolveInfoList(in Intent sanitizedIntent, in int[] hostDigestPrefix, + void getInstantAppResolveInfoList(in int[] digestPrefix, String token, int sequence, IRemoteCallback callback); - void getInstantAppIntentFilterList(in Intent sanitizedIntent, in int[] hostDigestPrefix, - String token, IRemoteCallback callback); - + void getInstantAppIntentFilterList(in int[] digestPrefix, + String token, String hostName, IRemoteCallback callback); } diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java index 89aff36f883..c5dc86c79ef 100644 --- a/core/java/android/app/InstantAppResolverService.java +++ b/core/java/android/app/InstantAppResolverService.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.SystemApi; +import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.InstantAppResolveInfo; @@ -34,7 +35,6 @@ import android.util.Slog; import com.android.internal.os.SomeArgs; import java.util.Arrays; -import java.util.Collections; import java.util.List; /** @@ -53,65 +53,23 @@ public abstract class InstantAppResolverService extends Service { Handler mHandler; /** - * Called to retrieve resolve info for instant applications immediately. + * Called to retrieve resolve info for instant applications. * * @param digestPrefix The hash prefix of the instant app's domain. - * @deprecated should implement {@link #onGetInstantAppResolveInfo(Intent, int[], String, - * InstantAppResolutionCallback)} */ - @Deprecated public void onGetInstantAppResolveInfo( int digestPrefix[], String token, InstantAppResolutionCallback callback) { throw new IllegalStateException("Must define"); } /** - * Called to retrieve intent filters for instant applications from potentially expensive - * sources. + * Called to retrieve intent filters for instant applications. * * @param digestPrefix The hash prefix of the instant app's domain. - * @deprecated should implement {@link #onGetInstantAppIntentFilter(Intent, int[], String, - * InstantAppResolutionCallback)} */ - @Deprecated public void onGetInstantAppIntentFilter( int digestPrefix[], String token, InstantAppResolutionCallback callback) { - throw new IllegalStateException("Must define onGetInstantAppIntentFilter"); - } - - /** - * Called to retrieve resolve info for instant applications immediately. - * - * @param sanitizedIntent The sanitized {@link Intent} used for resolution. - * @param hostDigestPrefix The hash prefix of the instant app's domain. - */ - public void onGetInstantAppResolveInfo(Intent sanitizedIntent, int[] hostDigestPrefix, - String token, InstantAppResolutionCallback callback) { - // if not overridden, forward to old methods and filter out non-web intents - if (sanitizedIntent.isBrowsableWebIntent()) { - onGetInstantAppResolveInfo(hostDigestPrefix, token, callback); - } else { - callback.onInstantAppResolveInfo(Collections.emptyList()); - } - } - - /** - * Called to retrieve intent filters for instant applications from potentially expensive - * sources. - * - * @param sanitizedIntent The sanitized {@link Intent} used for resolution. - * @param hostDigestPrefix The hash prefix of the instant app's domain or null if no host is - * defined. - */ - public void onGetInstantAppIntentFilter(Intent sanitizedIntent, int[] hostDigestPrefix, - String token, InstantAppResolutionCallback callback) { - Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden"); - // if not overridden, forward to old methods and filter out non-web intents - if (sanitizedIntent.isBrowsableWebIntent()) { - onGetInstantAppIntentFilter(hostDigestPrefix, token, callback); - } else { - callback.onInstantAppResolveInfo(Collections.emptyList()); - } + throw new IllegalStateException("Must define"); } /** @@ -131,8 +89,8 @@ public abstract class InstantAppResolverService extends Service { public final IBinder onBind(Intent intent) { return new IInstantAppResolver.Stub() { @Override - public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix, - String token, int sequence, IRemoteCallback callback) { + public void getInstantAppResolveInfoList( + int digestPrefix[], String token, int sequence, IRemoteCallback callback) { if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Phase1 called; posting"); } @@ -140,14 +98,14 @@ public abstract class InstantAppResolverService extends Service { args.arg1 = callback; args.arg2 = digestPrefix; args.arg3 = token; - args.arg4 = sanitizedIntent; - mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, - sequence, 0, args).sendToTarget(); + mHandler.obtainMessage( + ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, args) + .sendToTarget(); } @Override - public void getInstantAppIntentFilterList(Intent sanitizedIntent, - int[] digestPrefix, String token, IRemoteCallback callback) { + public void getInstantAppIntentFilterList( + int digestPrefix[], String token, String hostName, IRemoteCallback callback) { if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Phase2 called; posting"); } @@ -155,9 +113,9 @@ public abstract class InstantAppResolverService extends Service { args.arg1 = callback; args.arg2 = digestPrefix; args.arg3 = token; - args.arg4 = sanitizedIntent; - mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, - callback).sendToTarget(); + args.arg4 = hostName; + mHandler.obtainMessage( + ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, callback).sendToTarget(); } }; } @@ -184,9 +142,29 @@ public abstract class InstantAppResolverService extends Service { } } + @Deprecated + void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, + InstantAppResolutionCallback callback) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "[" + token + "] Phase1 request;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + onGetInstantAppResolveInfo(digestPrefix, token, callback); + } + @Deprecated + void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName, + InstantAppResolutionCallback callback) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "[" + token + "] Phase2 request;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + onGetInstantAppIntentFilter(digestPrefix, token, callback); + } + private final class ServiceHandler extends Handler { public static final int MSG_GET_INSTANT_APP_RESOLVE_INFO = 1; public static final int MSG_GET_INSTANT_APP_INTENT_FILTER = 2; + public ServiceHandler(Looper looper) { super(looper, null /*callback*/, true /*async*/); } @@ -201,13 +179,9 @@ public abstract class InstantAppResolverService extends Service { final IRemoteCallback callback = (IRemoteCallback) args.arg1; final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; - final Intent intent = (Intent) args.arg4; final int sequence = message.arg1; - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "[" + token + "] Phase1 request;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - onGetInstantAppResolveInfo(intent, digestPrefix, token, + _onGetInstantAppResolveInfo( + digestPrefix, token, new InstantAppResolutionCallback(sequence, callback)); } break; @@ -216,12 +190,9 @@ public abstract class InstantAppResolverService extends Service { final IRemoteCallback callback = (IRemoteCallback) args.arg1; final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; - final Intent intent = (Intent) args.arg4; - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "[" + token + "] Phase2 request;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - onGetInstantAppIntentFilter(intent, digestPrefix, token, + final String hostName = (String) args.arg4; + _onGetInstantAppIntentFilter( + digestPrefix, token, hostName, new InstantAppResolutionCallback(-1 /*sequence*/, callback)); } break; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 7b27fc023e9..4923171bd3a 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -50,7 +50,6 @@ import android.provider.DocumentsContract; import android.provider.DocumentsProvider; import android.provider.MediaStore; import android.provider.OpenableColumns; -import android.text.TextUtils; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; @@ -4473,15 +4472,7 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION"; /** - * An array of {@link Bundle}s containing details about resolved instant apps.. - * @hide - */ - @SystemApi - public static final String EXTRA_INSTANT_APP_BUNDLES = - "android.intent.extra.INSTANT_APP_BUNDLES"; - - /** - * A {@link Bundle} of metadata that describes the instant application that needs to be + * A {@link Bundle} of metadata that describes the instanta application that needs to be * installed. This data is populated from the response to * {@link android.content.pm.InstantAppResolveInfo#getExtras()} as provided by the registered * instant application resolver. @@ -4490,16 +4481,6 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_INSTANT_APP_EXTRAS = "android.intent.extra.INSTANT_APP_EXTRAS"; - /** - * A boolean value indicating that the instant app resolver was unable to state with certainty - * that it did or did not have an app for the sanitized {@link Intent} defined at - * {@link #EXTRA_INTENT}. - * @hide - */ - @SystemApi - public static final String EXTRA_UNKNOWN_INSTANT_APP = - "android.intent.extra.UNKNOWN_INSTANT_APP"; - /** * The version code of the app to install components from. * @deprecated Use {@link #EXTRA_LONG_VERSION_CODE). @@ -5048,7 +5029,6 @@ public class Intent implements Parcelable, Cloneable { FLAG_GRANT_PREFIX_URI_PERMISSION, FLAG_DEBUG_TRIAGED_MISSING, FLAG_IGNORE_EPHEMERAL, - FLAG_ACTIVITY_MATCH_EXTERNAL, FLAG_ACTIVITY_NO_HISTORY, FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_NEW_TASK, @@ -5092,7 +5072,6 @@ public class Intent implements Parcelable, Cloneable { FLAG_INCLUDE_STOPPED_PACKAGES, FLAG_DEBUG_TRIAGED_MISSING, FLAG_IGNORE_EPHEMERAL, - FLAG_ACTIVITY_MATCH_EXTERNAL, FLAG_ACTIVITY_NO_HISTORY, FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_NEW_TASK, @@ -5496,14 +5475,6 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 0x00001000; - - /** - * If set, resolution of this intent may take place via an instant app not - * yet on the device if there does not yet exist an app on device to - * resolve it. - */ - public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 0x00000800; - /** * If set, when sending a broadcast only registered receivers will be * called -- no BroadcastReceiver components will be launched. @@ -10053,25 +10024,6 @@ public class Intent implements Parcelable, Cloneable { } } - /** @hide */ - public boolean hasWebURI() { - if (getData() == null) { - return false; - } - final String scheme = getScheme(); - if (TextUtils.isEmpty(scheme)) { - return false; - } - return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS); - } - - /** @hide */ - public boolean isBrowsableWebIntent() { - return ACTION_VIEW.equals(mAction) - && hasCategory(CATEGORY_BROWSABLE) - && hasWebURI(); - } - /** * @hide */ diff --git a/core/java/android/content/pm/AuxiliaryResolveInfo.java b/core/java/android/content/pm/AuxiliaryResolveInfo.java index 202df50dda6..6bdcefbe974 100644 --- a/core/java/android/content/pm/AuxiliaryResolveInfo.java +++ b/core/java/android/content/pm/AuxiliaryResolveInfo.java @@ -21,10 +21,6 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; -import android.os.Bundle; - -import java.util.Collections; -import java.util.List; /** * Auxiliary application resolution response. @@ -35,95 +31,56 @@ import java.util.List; * hasn't been installed. * @hide */ -public final class AuxiliaryResolveInfo { +public final class AuxiliaryResolveInfo extends IntentFilter { + /** Resolved information returned from the external instant resolver */ + public final InstantAppResolveInfo resolveInfo; + /** The resolved package. Copied from {@link #resolveInfo}. */ + public final String packageName; /** The activity to launch if there's an installation failure. */ public final ComponentName installFailureActivity; + /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */ + public final String splitName; /** Whether or not instant resolution needs the second phase */ public final boolean needsPhaseTwo; /** Opaque token to track the instant application resolution */ public final String token; + /** The version code of the package */ + public final long versionCode; /** An intent to start upon failure to install */ public final Intent failureIntent; - /** The matching filters for this resolve info. */ - public final List filters; /** Create a response for installing an instant application. */ - public AuxiliaryResolveInfo(@NonNull String token, + public AuxiliaryResolveInfo(@NonNull InstantAppResolveInfo resolveInfo, + @NonNull IntentFilter orig, + @Nullable String splitName, + @NonNull String token, boolean needsPhase2, - @Nullable Intent failureIntent, - @Nullable List filters) { + @Nullable Intent failureIntent) { + super(orig); + this.resolveInfo = resolveInfo; + this.packageName = resolveInfo.getPackageName(); + this.splitName = splitName; this.token = token; this.needsPhaseTwo = needsPhase2; + this.versionCode = resolveInfo.getVersionCode(); this.failureIntent = failureIntent; - this.filters = filters; this.installFailureActivity = null; } /** Create a response for installing a split on demand. */ - public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity, - @Nullable Intent failureIntent, - @Nullable List filters) { + public AuxiliaryResolveInfo(@NonNull String packageName, + @Nullable String splitName, + @Nullable ComponentName failureActivity, + long versionCode, + @Nullable Intent failureIntent) { super(); + this.packageName = packageName; this.installFailureActivity = failureActivity; - this.filters = filters; + this.splitName = splitName; + this.versionCode = versionCode; + this.resolveInfo = null; this.token = null; this.needsPhaseTwo = false; this.failureIntent = failureIntent; } - - /** Create a response for installing a split on demand. */ - public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity, - String packageName, long versionCode, String splitName) { - this(failureActivity, null, Collections.singletonList( - new AuxiliaryResolveInfo.AuxiliaryFilter(packageName, versionCode, splitName))); - } - - /** @hide */ - public static final class AuxiliaryFilter extends IntentFilter { - /** Resolved information returned from the external instant resolver */ - public final InstantAppResolveInfo resolveInfo; - /** The resolved package. Copied from {@link #resolveInfo}. */ - public final String packageName; - /** The version code of the package */ - public final long versionCode; - /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */ - public final String splitName; - /** The extras to pass on to the installer for this filter. */ - public final Bundle extras; - - public AuxiliaryFilter(IntentFilter orig, InstantAppResolveInfo resolveInfo, - String splitName, Bundle extras) { - super(orig); - this.resolveInfo = resolveInfo; - this.packageName = resolveInfo.getPackageName(); - this.versionCode = resolveInfo.getLongVersionCode(); - this.splitName = splitName; - this.extras = extras; - } - - public AuxiliaryFilter(InstantAppResolveInfo resolveInfo, - String splitName, Bundle extras) { - this.resolveInfo = resolveInfo; - this.packageName = resolveInfo.getPackageName(); - this.versionCode = resolveInfo.getLongVersionCode(); - this.splitName = splitName; - this.extras = extras; - } - - public AuxiliaryFilter(String packageName, long versionCode, String splitName) { - this.resolveInfo = null; - this.packageName = packageName; - this.versionCode = versionCode; - this.splitName = splitName; - this.extras = null; - } - - @Override - public String toString() { - return "AuxiliaryFilter{" - + "packageName='" + packageName + '\'' - + ", versionCode=" + versionCode - + ", splitName='" + splitName + '\'' + '}'; - } - } } \ No newline at end of file diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java index 112c5dae673..19cb9323ba9 100644 --- a/core/java/android/content/pm/InstantAppResolveInfo.java +++ b/core/java/android/content/pm/InstantAppResolveInfo.java @@ -19,7 +19,6 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.content.Intent; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -27,35 +26,11 @@ import android.os.Parcelable; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Locale; /** - * Describes an externally resolvable instant application. There are three states that this class - * can represent:

- *

    - *
  • - * The first, usable only for non http/s intents, implies that the resolver cannot - * immediately resolve this intent and would prefer that resolution be deferred to the - * instant app installer. Represent this state with {@link #InstantAppResolveInfo(Bundle)}. - * If the {@link android.content.Intent} has the scheme set to http/s and a set of digest - * prefixes were passed into one of the resolve methods in - * {@link android.app.InstantAppResolverService}, this state cannot be used. - *
  • - *
  • - * The second represents a partial match and is constructed with any of the other - * constructors. By setting one or more of the {@link Nullable}arguments to null, you - * communicate to the resolver in response to - * {@link android.app.InstantAppResolverService#onGetInstantAppResolveInfo(Intent, int[], - * String, InstantAppResolverService.InstantAppResolutionCallback)} - * that you need a 2nd round of resolution to complete the request. - *
  • - *
  • - * The third represents a complete match and is constructed with all @Nullable parameters - * populated. - *
  • - *
+ * Information about an instant application. * @hide */ @SystemApi @@ -63,8 +38,6 @@ public final class InstantAppResolveInfo implements Parcelable { /** Algorithm that will be used to generate the domain digest */ private static final String SHA_ALGORITHM = "SHA-256"; - private static final byte[] EMPTY_DIGEST = new byte[0]; - private final InstantAppDigest mDigest; private final String mPackageName; /** The filters used to match domain */ @@ -73,30 +46,15 @@ public final class InstantAppResolveInfo implements Parcelable { private final long mVersionCode; /** Data about the app that should be passed along to the Instant App installer on resolve */ private final Bundle mExtras; - /** - * A flag that indicates that the resolver is aware that an app may match, but would prefer - * that the installer get the sanitized intent to decide. This should not be used for - * resolutions that include a host and will be ignored in such cases. - */ - private final boolean mShouldLetInstallerDecide; - /** Constructor for intent-based InstantApp resolution results. */ public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, @Nullable List filters, int versionCode) { this(digest, packageName, filters, (long) versionCode, null /* extras */); } - /** Constructor for intent-based InstantApp resolution results with extras. */ public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, @Nullable List filters, long versionCode, @Nullable Bundle extras) { - this(digest, packageName, filters, versionCode, extras, false); - } - - /** Constructor for intent-based InstantApp resolution results with extras. */ - private InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, - @Nullable List filters, long versionCode, - @Nullable Bundle extras, boolean shouldLetInstallerDecide) { // validate arguments if ((packageName == null && (filters != null && filters.size() != 0)) || (packageName != null && (filters == null || filters.size() == 0))) { @@ -104,7 +62,7 @@ public final class InstantAppResolveInfo implements Parcelable { } mDigest = digest; if (filters != null) { - mFilters = new ArrayList<>(filters.size()); + mFilters = new ArrayList(filters.size()); mFilters.addAll(filters); } else { mFilters = null; @@ -112,48 +70,25 @@ public final class InstantAppResolveInfo implements Parcelable { mPackageName = packageName; mVersionCode = versionCode; mExtras = extras; - mShouldLetInstallerDecide = shouldLetInstallerDecide; } - /** Constructor for intent-based InstantApp resolution results by hostname. */ public InstantAppResolveInfo(@NonNull String hostName, @Nullable String packageName, @Nullable List filters) { this(new InstantAppDigest(hostName), packageName, filters, -1 /*versionCode*/, null /* extras */); } - /** - * Constructor that creates a "let the installer decide" response with optional included - * extras. - */ - public InstantAppResolveInfo(@Nullable Bundle extras) { - this(InstantAppDigest.UNDEFINED, null, null, -1, extras, true); - } - InstantAppResolveInfo(Parcel in) { - mShouldLetInstallerDecide = in.readBoolean(); + mDigest = in.readParcelable(null /*loader*/); + mPackageName = in.readString(); + mFilters = new ArrayList(); + in.readList(mFilters, null /*loader*/); + mVersionCode = in.readLong(); mExtras = in.readBundle(); - if (mShouldLetInstallerDecide) { - mDigest = InstantAppDigest.UNDEFINED; - mPackageName = null; - mFilters = Collections.emptyList(); - mVersionCode = -1; - } else { - mDigest = in.readParcelable(null /*loader*/); - mPackageName = in.readString(); - mFilters = new ArrayList<>(); - in.readList(mFilters, null /*loader*/); - mVersionCode = in.readLong(); - } - } - - /** Returns true if the installer should be notified that it should query for packages. */ - public boolean shouldLetInstallerDecide() { - return mShouldLetInstallerDecide; } public byte[] getDigestBytes() { - return mDigest.mDigestBytes.length > 0 ? mDigest.getDigestBytes()[0] : EMPTY_DIGEST; + return mDigest.getDigestBytes()[0]; } public int getDigestPrefix() { @@ -192,15 +127,11 @@ public final class InstantAppResolveInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeBoolean(mShouldLetInstallerDecide); - out.writeBundle(mExtras); - if (mShouldLetInstallerDecide) { - return; - } out.writeParcelable(mDigest, flags); out.writeString(mPackageName); out.writeList(mFilters); out.writeLong(mVersionCode); + out.writeBundle(mExtras); } public static final Parcelable.Creator CREATOR @@ -228,9 +159,7 @@ public final class InstantAppResolveInfo implements Parcelable { @SystemApi public static final class InstantAppDigest implements Parcelable { private static final int DIGEST_MASK = 0xfffff000; - - public static final InstantAppDigest UNDEFINED = - new InstantAppDigest(new byte[][]{}, new int[]{}); + private static final int DIGEST_PREFIX_COUNT = 5; /** Full digest of the domain hashes */ private final byte[][] mDigestBytes; /** The first 4 bytes of the domain hashes */ @@ -257,11 +186,6 @@ public final class InstantAppResolveInfo implements Parcelable { } } - private InstantAppDigest(byte[][] digestBytes, int[] prefix) { - this.mDigestPrefix = prefix; - this.mDigestBytes = digestBytes; - } - private static byte[][] generateDigest(String hostName, int maxDigests) { ArrayList digests = new ArrayList<>(); try { diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 1dfff5efcab..2ab2d20ed20 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -103,14 +103,11 @@ public class ResolverListController { List resolvedComponents = null; for (int i = 0, N = intents.size(); i < N; i++) { final Intent intent = intents.get(i); - int flags = PackageManager.MATCH_DEFAULT_ONLY - | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) - | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0); - if (intent.isBrowsableWebIntent() - || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { - flags |= PackageManager.MATCH_INSTANT; - } - final List infos = mpm.queryIntentActivities(intent, flags); + final List infos = mpm.queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY + | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) + | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0) + | PackageManager.MATCH_INSTANT); // Remove any activities that are not exported. int totalSize = infos.size(); for (int j = totalSize - 1; j >= 0 ; j--) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 7c9160a2e4c..510a3fa47ec 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1245,14 +1245,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D synchronized (mService) { try { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent"); - int modifiedFlags = flags - | PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS; - if (intent.isBrowsableWebIntent() - || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { - modifiedFlags |= PackageManager.MATCH_INSTANT; - } return mService.getPackageManagerInternalLocked().resolveIntent( - intent, resolvedType, modifiedFlags, userId, true); + intent, resolvedType, PackageManager.MATCH_INSTANT + | PackageManager.MATCH_DEFAULT_ONLY | flags + | ActivityManagerService.STOCK_PM_FLAGS, userId, true); } finally { Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index eab88aa45e4..8fd754af1a0 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -31,7 +31,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.content.Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; @@ -790,7 +789,7 @@ class ActivityStarter { // Instead, launch the ephemeral installer. Once the installer is finished, it // starts either the intent we resolved here [on install error] or the ephemeral // app [on install success]. - if (rInfo != null && rInfo.isInstantAppAvailable) { + if (rInfo != null && rInfo.auxiliaryInfo != null) { intent = createLaunchIntent(rInfo.auxiliaryInfo, ephemeralIntent, callingPackage, verificationBundle, resolvedType, userId); resolvedType = null; @@ -850,27 +849,22 @@ class ActivityStarter { /** * Creates a launch intent for the given auxiliary resolution data. */ - private @NonNull Intent createLaunchIntent(@Nullable AuxiliaryResolveInfo auxiliaryResponse, + private @NonNull Intent createLaunchIntent(@NonNull AuxiliaryResolveInfo auxiliaryResponse, Intent originalIntent, String callingPackage, Bundle verificationBundle, String resolvedType, int userId) { - if (auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo) { + if (auxiliaryResponse.needsPhaseTwo) { // request phase two resolution mService.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo( auxiliaryResponse, originalIntent, resolvedType, callingPackage, verificationBundle, userId); } return InstantAppResolver.buildEphemeralInstallerIntent( - originalIntent, - InstantAppResolver.sanitizeIntent(originalIntent), - auxiliaryResponse == null ? null : auxiliaryResponse.failureIntent, - callingPackage, - verificationBundle, - resolvedType, - userId, - auxiliaryResponse == null ? null : auxiliaryResponse.installFailureActivity, - auxiliaryResponse == null ? null : auxiliaryResponse.token, - auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo, - auxiliaryResponse == null ? null : auxiliaryResponse.filters); + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, originalIntent, + auxiliaryResponse.failureIntent, callingPackage, verificationBundle, + resolvedType, userId, auxiliaryResponse.packageName, auxiliaryResponse.splitName, + auxiliaryResponse.installFailureActivity, auxiliaryResponse.versionCode, + auxiliaryResponse.token, auxiliaryResponse.resolveInfo.getExtras(), + auxiliaryResponse.needsPhaseTwo); } void postStartActivityProcessing(ActivityRecord r, int result, ActivityStack targetStack) { @@ -930,12 +924,12 @@ class ActivityStarter { // Don't modify the client's object! intent = new Intent(intent); if (componentSpecified - && !Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE.equals(intent.getAction()) - && !Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE.equals(intent.getAction()) + && intent.getData() != null + && Intent.ACTION_VIEW.equals(intent.getAction()) && mService.getPackageManagerInternalLocked() .isInstantAppInstallerComponent(intent.getComponent())) { // intercept intents targeted directly to the ephemeral installer the - // ephemeral installer should never be started with a raw Intent; instead + // ephemeral installer should never be started with a raw URL; instead // adjust the intent so it looks like a "normal" instant app launch intent.setComponent(null /*component*/); componentSpecified = false; diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java index 6d6c960eed7..b5ddf8c511f 100644 --- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java +++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java @@ -43,7 +43,6 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.TimeoutException; @@ -85,8 +84,8 @@ final class EphemeralResolverConnection implements DeathRecipient { mIntent = new Intent(action).setComponent(componentName); } - public final List getInstantAppResolveInfoList(Intent sanitizedIntent, - int hashPrefix[], String token) throws ConnectionException { + public final List getInstantAppResolveInfoList(int hashPrefix[], + String token) throws ConnectionException { throwIfCalledOnMainThread(); IInstantAppResolver target = null; try { @@ -99,7 +98,7 @@ final class EphemeralResolverConnection implements DeathRecipient { } try { return mGetEphemeralResolveInfoCaller - .getEphemeralResolveInfoList(target, sanitizedIntent, hashPrefix, token); + .getEphemeralResolveInfoList(target, hashPrefix, token); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_CALL); } catch (RemoteException ignore) { @@ -112,22 +111,26 @@ final class EphemeralResolverConnection implements DeathRecipient { return null; } - public final void getInstantAppIntentFilterList(Intent sanitizedIntent, int hashPrefix[], - String token, PhaseTwoCallback callback, Handler callbackHandler, final long startTime) - throws ConnectionException { + public final void getInstantAppIntentFilterList(int hashPrefix[], String token, + String hostName, PhaseTwoCallback callback, Handler callbackHandler, + final long startTime) throws ConnectionException { final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() { @Override public void sendResult(Bundle data) throws RemoteException { final ArrayList resolveList = data.getParcelableArrayList( InstantAppResolverService.EXTRA_RESOLVE_INFO); - callbackHandler.post(() -> callback.onPhaseTwoResolved(resolveList, startTime)); + callbackHandler.post(new Runnable() { + @Override + public void run() { + callback.onPhaseTwoResolved(resolveList, startTime); + } + }); } }; try { getRemoteInstanceLazy(token) - .getInstantAppIntentFilterList(sanitizedIntent, hashPrefix, token, - remoteCallback); + .getInstantAppIntentFilterList(hashPrefix, token, hostName, remoteCallback); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_BIND); } catch (InterruptedException e) { @@ -334,11 +337,10 @@ final class EphemeralResolverConnection implements DeathRecipient { } public List getEphemeralResolveInfoList( - IInstantAppResolver target, Intent sanitizedIntent, int hashPrefix[], String token) + IInstantAppResolver target, int hashPrefix[], String token) throws RemoteException, TimeoutException { final int sequence = onBeforeRemoteCall(); - target.getInstantAppResolveInfoList(sanitizedIntent, hashPrefix, token, sequence, - mCallback); + target.getInstantAppResolveInfoList(hashPrefix, token, sequence, mCallback); return getResultTimed(sequence); } } diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index 55212cc6b3d..30072d45cca 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -40,14 +40,11 @@ import android.content.pm.InstantAppIntentFilter; import android.content.pm.InstantAppResolveInfo; import android.content.pm.InstantAppResolveInfo.InstantAppDigest; import android.metrics.LogMaker; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; -import android.text.TextUtils; import android.util.Log; -import android.util.Slog; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; @@ -56,12 +53,8 @@ import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; import java.util.List; -import java.util.Set; import java.util.UUID; /** @hide */ @@ -86,7 +79,6 @@ public abstract class InstantAppResolver { public @interface ResolutionStatus {} private static MetricsLogger sMetricsLogger; - private static MetricsLogger getLogger() { if (sMetricsLogger == null) { sMetricsLogger = new MetricsLogger(); @@ -94,49 +86,26 @@ public abstract class InstantAppResolver { return sMetricsLogger; } - /** - * Returns an intent with potential PII removed from the original intent. Fields removed - * include extras and the host + path of the data, if defined. - */ - public static Intent sanitizeIntent(Intent origIntent) { - final Intent sanitizedIntent; - sanitizedIntent = new Intent(origIntent.getAction()); - Set categories = origIntent.getCategories(); - if (categories != null) { - for (String category : categories) { - sanitizedIntent.addCategory(category); - } - } - Uri sanitizedUri = origIntent.getData() == null - ? null - : Uri.fromParts(origIntent.getScheme(), "", ""); - sanitizedIntent.setDataAndType(sanitizedUri, origIntent.getType()); - sanitizedIntent.addFlags(origIntent.getFlags()); - sanitizedIntent.setPackage(origIntent.getPackage()); - return sanitizedIntent; - } - - public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne( + public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(Context context, EphemeralResolverConnection connection, InstantAppRequest requestObj) { final long startTime = System.currentTimeMillis(); final String token = UUID.randomUUID().toString(); if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] Phase1; resolving"); } - final Intent origIntent = requestObj.origIntent; - final Intent sanitizedIntent = sanitizeIntent(origIntent); - - final InstantAppDigest digest = getInstantAppDigest(origIntent); + final Intent intent = requestObj.origIntent; + final InstantAppDigest digest = + new InstantAppDigest(intent.getData().getHost(), 5 /*maxDigests*/); final int[] shaPrefix = digest.getDigestPrefix(); AuxiliaryResolveInfo resolveInfo = null; @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS; try { final List instantAppResolveInfoList = - connection.getInstantAppResolveInfoList(sanitizedIntent, shaPrefix, token); + connection.getInstantAppResolveInfoList(shaPrefix, token); if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) { resolveInfo = InstantAppResolver.filterInstantAppIntent( - instantAppResolveInfoList, origIntent, requestObj.resolvedType, - requestObj.userId, origIntent.getPackage(), digest, token); + instantAppResolveInfoList, intent, requestObj.resolvedType, + requestObj.userId, intent.getPackage(), digest, token); } } catch (ConnectionException e) { if (e.failure == ConnectionException.FAILURE_BIND) { @@ -166,12 +135,6 @@ public abstract class InstantAppResolver { return resolveInfo; } - private static InstantAppDigest getInstantAppDigest(Intent origIntent) { - return origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost()) - ? new InstantAppDigest(origIntent.getData().getHost(), 5 /*maxDigests*/) - : InstantAppDigest.UNDEFINED; - } - public static void doInstantAppResolutionPhaseTwo(Context context, EphemeralResolverConnection connection, InstantAppRequest requestObj, ActivityInfo instantAppInstaller, Handler callbackHandler) { @@ -180,53 +143,73 @@ public abstract class InstantAppResolver { if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] Phase2; resolving"); } - final Intent origIntent = requestObj.origIntent; - final Intent sanitizedIntent = sanitizeIntent(origIntent); - final InstantAppDigest digest = getInstantAppDigest(origIntent); + final Intent intent = requestObj.origIntent; + final String hostName = intent.getData().getHost(); + final InstantAppDigest digest = new InstantAppDigest(hostName, 5 /*maxDigests*/); final int[] shaPrefix = digest.getDigestPrefix(); final PhaseTwoCallback callback = new PhaseTwoCallback() { @Override void onPhaseTwoResolved(List instantAppResolveInfoList, long startTime) { + final String packageName; + final String splitName; + final long versionCode; final Intent failureIntent; + final Bundle extras; if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) { final AuxiliaryResolveInfo instantAppIntentInfo = InstantAppResolver.filterInstantAppIntent( - instantAppResolveInfoList, origIntent, null /*resolvedType*/, - 0 /*userId*/, origIntent.getPackage(), digest, token); - if (instantAppIntentInfo != null) { + instantAppResolveInfoList, intent, null /*resolvedType*/, + 0 /*userId*/, intent.getPackage(), digest, token); + if (instantAppIntentInfo != null + && instantAppIntentInfo.resolveInfo != null) { + packageName = instantAppIntentInfo.resolveInfo.getPackageName(); + splitName = instantAppIntentInfo.splitName; + versionCode = instantAppIntentInfo.resolveInfo.getVersionCode(); failureIntent = instantAppIntentInfo.failureIntent; + extras = instantAppIntentInfo.resolveInfo.getExtras(); } else { + packageName = null; + splitName = null; + versionCode = -1; failureIntent = null; + extras = null; } } else { + packageName = null; + splitName = null; + versionCode = -1; failureIntent = null; + extras = null; } final Intent installerIntent = buildEphemeralInstallerIntent( + Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE, requestObj.origIntent, - sanitizedIntent, failureIntent, requestObj.callingPackage, requestObj.verificationBundle, requestObj.resolvedType, requestObj.userId, + packageName, + splitName, requestObj.responseObj.installFailureActivity, + versionCode, token, - false /*needsPhaseTwo*/, - requestObj.responseObj.filters); + extras, + false /*needsPhaseTwo*/); installerIntent.setComponent(new ComponentName( instantAppInstaller.packageName, instantAppInstaller.name)); logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token, - requestObj.responseObj.filters != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE); + packageName != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE); context.startActivity(installerIntent); } }; try { - connection.getInstantAppIntentFilterList(sanitizedIntent, shaPrefix, token, callback, - callbackHandler, startTime); + connection.getInstantAppIntentFilterList( + shaPrefix, token, hostName, callback, callbackHandler, startTime); } catch (ConnectionException e) { @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE; if (e.failure == ConnectionException.FAILURE_BIND) { @@ -248,20 +231,23 @@ public abstract class InstantAppResolver { * Builds and returns an intent to launch the instant installer. */ public static Intent buildEphemeralInstallerIntent( + @NonNull String action, @NonNull Intent origIntent, - @NonNull Intent sanitizedIntent, - @Nullable Intent failureIntent, + @NonNull Intent failureIntent, @NonNull String callingPackage, @Nullable Bundle verificationBundle, @NonNull String resolvedType, int userId, + @NonNull String instantAppPackageName, + @Nullable String instantAppSplitName, @Nullable ComponentName installFailureActivity, + long versionCode, @Nullable String token, - boolean needsPhaseTwo, - List filters) { + @Nullable Bundle extras, + boolean needsPhaseTwo) { // Construct the intent that launches the instant installer int flags = origIntent.getFlags(); - final Intent intent = new Intent(); + final Intent intent = new Intent(action); intent.setFlags(flags | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK @@ -274,23 +260,20 @@ public abstract class InstantAppResolver { intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost()); } intent.putExtra(Intent.EXTRA_INSTANT_APP_ACTION, origIntent.getAction()); - intent.putExtra(Intent.EXTRA_INTENT, sanitizedIntent); + if (extras != null) { + intent.putExtra(Intent.EXTRA_INSTANT_APP_EXTRAS, extras); + } - if (needsPhaseTwo) { - intent.setAction(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE); - } else { - // We have all of the data we need; just start the installer without a second phase + // We have all of the data we need; just start the installer without a second phase + if (!needsPhaseTwo) { + // Intent that is launched if the package couldn't be installed for any reason. if (failureIntent != null || installFailureActivity != null) { - // Intent that is launched if the package couldn't be installed for any reason. try { final Intent onFailureIntent; if (installFailureActivity != null) { onFailureIntent = new Intent(); onFailureIntent.setComponent(installFailureActivity); - if (filters != null && filters.size() == 1) { - onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME, - filters.get(0).splitName); - } + onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName); onFailureIntent.putExtra(Intent.EXTRA_INTENT, origIntent); } else { onFailureIntent = failureIntent; @@ -326,35 +309,17 @@ public abstract class InstantAppResolver { intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(successIntentTarget)); } catch (RemoteException ignore) { /* ignore; same process */ } + + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, instantAppPackageName); + intent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName); + intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) (versionCode & 0x7fffffff)); + intent.putExtra(Intent.EXTRA_LONG_VERSION_CODE, versionCode); + intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage); if (verificationBundle != null) { intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle); } - intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage); - - if (filters != null) { - Bundle resolvableFilters[] = new Bundle[filters.size()]; - for (int i = 0, max = filters.size(); i < max; i++) { - Bundle resolvableFilter = new Bundle(); - AuxiliaryResolveInfo.AuxiliaryFilter filter = filters.get(i); - resolvableFilter.putBoolean(Intent.EXTRA_UNKNOWN_INSTANT_APP, - filter.resolveInfo != null - && filter.resolveInfo.shouldLetInstallerDecide()); - resolvableFilter.putString(Intent.EXTRA_PACKAGE_NAME, filter.packageName); - resolvableFilter.putString(Intent.EXTRA_SPLIT_NAME, filter.splitName); - resolvableFilter.putLong(Intent.EXTRA_LONG_VERSION_CODE, filter.versionCode); - resolvableFilter.putBundle(Intent.EXTRA_INSTANT_APP_EXTRAS, filter.extras); - resolvableFilters[i] = resolvableFilter; - if (i == 0) { - // for backwards compat, always set the first result on the intent and add - // the int version code - intent.putExtras(resolvableFilter); - intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) filter.versionCode); - } - } - intent.putExtra(Intent.EXTRA_INSTANT_APP_BUNDLES, resolvableFilters); - } - intent.setAction(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE); } + return intent; } @@ -365,131 +330,66 @@ public abstract class InstantAppResolver { final int[] shaPrefix = digest.getDigestPrefix(); final byte[][] digestBytes = digest.getDigestBytes(); final Intent failureIntent = new Intent(origIntent); - boolean requiresSecondPhase = false; failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL); failureIntent.setLaunchToken(token); - ArrayList filters = null; - boolean isWebIntent = origIntent.isBrowsableWebIntent(); - for (InstantAppResolveInfo instantAppResolveInfo : instantAppResolveInfoList) { - if (shaPrefix.length > 0 && instantAppResolveInfo.shouldLetInstallerDecide()) { - Slog.e(TAG, "InstantAppResolveInfo with mShouldLetInstallerDecide=true when digest" - + " provided; ignoring"); - continue; - } - byte[] filterDigestBytes = instantAppResolveInfo.getDigestBytes(); - // Only include matching digests if we have a prefix and we're either dealing with a - // web intent or the resolveInfo specifies digest details. - if (shaPrefix.length > 0 && (isWebIntent || filterDigestBytes.length > 0)) { - boolean matchFound = false; - // Go in reverse order so we match the narrowest scope first. - for (int i = shaPrefix.length - 1; i >= 0; --i) { - if (Arrays.equals(digestBytes[i], filterDigestBytes)) { - matchFound = true; - break; - } - } - if (!matchFound) { + // Go in reverse order so we match the narrowest scope first. + for (int i = shaPrefix.length - 1; i >= 0 ; --i) { + for (InstantAppResolveInfo instantAppInfo : instantAppResolveInfoList) { + if (!Arrays.equals(digestBytes[i], instantAppInfo.getDigestBytes())) { continue; } - } - // We matched a resolve info; resolve the filters to see if anything matches completely. - List matchFilters = computeResolveFilters( - origIntent, resolvedType, userId, packageName, token, instantAppResolveInfo); - if (matchFilters != null) { - if (matchFilters.isEmpty()) { - requiresSecondPhase = true; + if (packageName != null + && !packageName.equals(instantAppInfo.getPackageName())) { + continue; } - if (filters == null) { - filters = new ArrayList<>(matchFilters); - } else { - filters.addAll(matchFilters); + final List instantAppFilters = + instantAppInfo.getIntentFilters(); + // No filters; we need to start phase two + if (instantAppFilters == null || instantAppFilters.isEmpty()) { + if (DEBUG_EPHEMERAL) { + Log.d(TAG, "No app filters; go to phase 2"); + } + return new AuxiliaryResolveInfo(instantAppInfo, + new IntentFilter(Intent.ACTION_VIEW) /*intentFilter*/, + null /*splitName*/, token, true /*needsPhase2*/, + null /*failureIntent*/); } - } - } - if (filters != null && !filters.isEmpty()) { - return new AuxiliaryResolveInfo(token, requiresSecondPhase, failureIntent, filters); - } - // Hash or filter mis-match; no instant apps for this domain. - return null; - } - - /** - * Returns one of three states:

- *

    - *
  • {@code null} if there are no matches will not be; resolution is unnecessary.
  • - *
  • An empty list signifying that a 2nd phase of resolution is required.
  • - *
  • A populated list meaning that matches were found and should be sent directly to the - * installer
  • - *
- * - */ - private static List computeResolveFilters( - Intent origIntent, String resolvedType, int userId, String packageName, String token, - InstantAppResolveInfo instantAppInfo) { - if (instantAppInfo.shouldLetInstallerDecide()) { - return Collections.singletonList( - new AuxiliaryResolveInfo.AuxiliaryFilter( - instantAppInfo, null /* splitName */, - instantAppInfo.getExtras())); - } - if (packageName != null - && !packageName.equals(instantAppInfo.getPackageName())) { - return null; - } - final List instantAppFilters = - instantAppInfo.getIntentFilters(); - if (instantAppFilters == null || instantAppFilters.isEmpty()) { - // No filters on web intent; no matches, 2nd phase unnecessary. - if (origIntent.isBrowsableWebIntent()) { - return null; - } - // No filters; we need to start phase two - if (DEBUG_EPHEMERAL) { - Log.d(TAG, "No app filters; go to phase 2"); - } - return Collections.emptyList(); - } - final PackageManagerService.EphemeralIntentResolver instantAppResolver = - new PackageManagerService.EphemeralIntentResolver(); - for (int j = instantAppFilters.size() - 1; j >= 0; --j) { - final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); - final List splitFilters = instantAppFilter.getFilters(); - if (splitFilters == null || splitFilters.isEmpty()) { - continue; - } - for (int k = splitFilters.size() - 1; k >= 0; --k) { - IntentFilter filter = splitFilters.get(k); - Iterator authorities = - filter.authoritiesIterator(); - // ignore http/s-only filters. - if ((authorities == null || !authorities.hasNext()) - && (filter.hasDataScheme("http") || filter.hasDataScheme("https")) - && filter.hasAction(Intent.ACTION_VIEW) - && filter.hasCategory(Intent.CATEGORY_BROWSABLE)) { - continue; + // We have a domain match; resolve the filters to see if anything matches. + final PackageManagerService.EphemeralIntentResolver instantAppResolver = + new PackageManagerService.EphemeralIntentResolver(); + for (int j = instantAppFilters.size() - 1; j >= 0; --j) { + final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); + final List splitFilters = instantAppFilter.getFilters(); + if (splitFilters == null || splitFilters.isEmpty()) { + continue; + } + for (int k = splitFilters.size() - 1; k >= 0; --k) { + final AuxiliaryResolveInfo intentInfo = + new AuxiliaryResolveInfo(instantAppInfo, + splitFilters.get(k), instantAppFilter.getSplitName(), + token, false /*needsPhase2*/, failureIntent); + instantAppResolver.addFilter(intentInfo); + } } - instantAppResolver.addFilter( - new AuxiliaryResolveInfo.AuxiliaryFilter( - filter, - instantAppInfo, - instantAppFilter.getSplitName(), - instantAppInfo.getExtras() - )); - } - } - List matchedResolveInfoList = - instantAppResolver.queryIntent( + List matchedResolveInfoList = instantAppResolver.queryIntent( origIntent, resolvedType, false /*defaultOnly*/, userId); - if (!matchedResolveInfoList.isEmpty()) { - if (DEBUG_EPHEMERAL) { - Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList); + if (!matchedResolveInfoList.isEmpty()) { + if (DEBUG_EPHEMERAL) { + final AuxiliaryResolveInfo info = matchedResolveInfoList.get(0); + Log.d(TAG, "[" + token + "] Found match;" + + " package: " + info.packageName + + ", split: " + info.splitName + + ", versionCode: " + info.versionCode); + } + return matchedResolveInfoList.get(0); + } else if (DEBUG_EPHEMERAL) { + Log.d(TAG, "[" + token + "] No matches found" + + " package: " + instantAppInfo.getPackageName() + + ", versionCode: " + instantAppInfo.getVersionCode()); + } } - return matchedResolveInfoList; - } else if (DEBUG_EPHEMERAL) { - Log.d(TAG, "[" + token + "] No matches found" - + " package: " + instantAppInfo.getPackageName() - + ", versionCode: " + instantAppInfo.getVersionCode()); } + // Hash or filter mis-match; no instant apps for this domain. return null; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index fdb157e7e92..3049e98099c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3595,35 +3595,24 @@ Slog.e("TODD", } private @Nullable ActivityInfo getInstantAppInstallerLPr() { - String[] orderedActions = Build.IS_ENG - ? new String[]{ - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE + "_TEST", - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, - Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE} - : new String[]{ - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, - Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE}; + final Intent intent = new Intent(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE); final int resolveFlags = MATCH_DIRECT_BOOT_AWARE - | MATCH_DIRECT_BOOT_UNAWARE - | Intent.FLAG_IGNORE_EPHEMERAL - | (!Build.IS_ENG ? MATCH_SYSTEM_ONLY : 0); - final Intent intent = new Intent(); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE); - List matches = null; - for (String action : orderedActions) { - intent.setAction(action); + | MATCH_DIRECT_BOOT_UNAWARE + | (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0); + List matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, + resolveFlags, UserHandle.USER_SYSTEM); + // temporarily look for the old action + if (matches.isEmpty()) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral installer not found with new action; try old one"); + } + intent.setAction(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE); matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, resolveFlags, UserHandle.USER_SYSTEM); - if (matches.isEmpty()) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "Instant App installer not found with " + action); - } - } else { - break; - } } Iterator iter = matches.iterator(); while (iter.hasNext()) { @@ -3631,8 +3620,7 @@ Slog.e("TODD", final PackageSetting ps = mSettings.mPackages.get(rInfo.activityInfo.packageName); if (ps != null) { final PermissionsState permissionsState = ps.getPermissionsState(); - if (permissionsState.hasPermission(Manifest.permission.INSTALL_PACKAGES, 0) - || Build.IS_ENG) { + if (permissionsState.hasPermission(Manifest.permission.INSTALL_PACKAGES, 0)) { continue; } } @@ -4791,7 +4779,10 @@ Slog.e("TODD", flags |= PackageManager.MATCH_INSTANT; } else { final boolean wantMatchInstant = (flags & PackageManager.MATCH_INSTANT) != 0; - final boolean allowMatchInstant = wantInstantApps + final boolean allowMatchInstant = + (wantInstantApps + && Intent.ACTION_VIEW.equals(intent.getAction()) + && hasWebURI(intent)) || (wantMatchInstant && canViewInstantApps(callingUid, userId)); flags &= ~(PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY | PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY); @@ -5981,14 +5972,8 @@ Slog.e("TODD", if (!skipPackageCheck && intent.getPackage() != null) { return false; } - if (!intent.isBrowsableWebIntent()) { - // for non web intents, we should not resolve externally if an app already exists to - // handle it or if the caller didn't explicitly request it. - if ((resolvedActivities != null && resolvedActivities.size() != 0) - || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) == 0) { - return false; - } - } else if (intent.getData() == null) { + final boolean isWebUri = hasWebURI(intent); + if (!isWebUri || intent.getData().getHost() == null) { return false; } // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. @@ -6386,7 +6371,7 @@ Slog.e("TODD", if (matches.get(i).getTargetUserId() == targetUserId) return true; } } - if (intent.hasWebURI()) { + if (hasWebURI(intent)) { // cross-profile app linking works only towards the parent. final int callingUid = Binder.getCallingUid(); final UserInfo parent = getProfileParent(sourceUserId); @@ -6561,7 +6546,7 @@ Slog.e("TODD", sortResult = true; } } - if (intent.hasWebURI()) { + if (hasWebURI(intent)) { CrossProfileDomainInfo xpDomainInfo = null; final UserInfo parent = getProfileParent(userId); if (parent != null) { @@ -6647,6 +6632,7 @@ Slog.e("TODD", if (ps.getInstantApp(userId)) { final long packedStatus = getDomainVerificationStatusLPr(ps, userId); final int status = (int)(packedStatus >> 32); + final int linkGeneration = (int)(packedStatus & 0xFFFFFFFF); if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { // there's a local instant application installed, but, the user has // chosen to never use it; skip resolution and don't acknowledge @@ -6678,8 +6664,9 @@ Slog.e("TODD", null /*responseObj*/, intent /*origIntent*/, resolvedType, null /*callingPackage*/, userId, null /*verificationBundle*/, resolveForStart); - auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne( - mInstantAppResolverConnection, requestObject); + auxiliaryResponse = + InstantAppResolver.doInstantAppResolutionPhaseOne( + mContext, mInstantAppResolverConnection, requestObject); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } else { // we have an instant application locally, but, we can't admit that since @@ -6688,40 +6675,35 @@ Slog.e("TODD", // instant application available externally. when it comes time to start // the instant application, we'll do the right thing. final ApplicationInfo ai = localInstantApp.activityInfo.applicationInfo; - auxiliaryResponse = new AuxiliaryResolveInfo(null /* failureActivity */, - ai.packageName, ai.versionCode, null /* splitName */); + auxiliaryResponse = new AuxiliaryResolveInfo( + ai.packageName, null /*splitName*/, null /*failureActivity*/, + ai.versionCode, null /*failureIntent*/); } } - if (intent.isBrowsableWebIntent() && auxiliaryResponse == null) { - return result; - } - final PackageSetting ps = mSettings.mPackages.get(mInstantAppInstallerActivity.packageName); - if (ps == null) { - return result; - } - final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo); - ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo( - mInstantAppInstallerActivity, 0, ps.readUserState(userId), userId); - ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART - | IntentFilter.MATCH_ADJUSTMENT_NORMAL; - // add a non-generic filter - ephemeralInstaller.filter = new IntentFilter(); - if (intent.getAction() != null) { - ephemeralInstaller.filter.addAction(intent.getAction()); - } - if (intent.getData() != null && intent.getData().getPath() != null) { - ephemeralInstaller.filter.addDataPath( - intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL); - } - ephemeralInstaller.isInstantAppAvailable = true; - // make sure this resolver is the default - ephemeralInstaller.isDefault = true; - ephemeralInstaller.auxiliaryInfo = auxiliaryResponse; - if (DEBUG_EPHEMERAL) { - Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); + if (auxiliaryResponse != null) { + if (DEBUG_EPHEMERAL) { + Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); + } + final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo); + final PackageSetting ps = + mSettings.mPackages.get(mInstantAppInstallerActivity.packageName); + if (ps != null) { + ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo( + mInstantAppInstallerActivity, 0, ps.readUserState(userId), userId); + ephemeralInstaller.activityInfo.launchToken = auxiliaryResponse.token; + ephemeralInstaller.auxiliaryInfo = auxiliaryResponse; + // make sure this resolver is the default + ephemeralInstaller.isDefault = true; + ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART + | IntentFilter.MATCH_ADJUSTMENT_NORMAL; + // add a non-generic filter + ephemeralInstaller.filter = new IntentFilter(intent.getAction()); + ephemeralInstaller.filter.addDataPath( + intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL); + ephemeralInstaller.isInstantAppAvailable = true; + result.add(ephemeralInstaller); + } } - - result.add(ephemeralInstaller); return result; } @@ -6836,11 +6818,10 @@ Slog.e("TODD", final ResolveInfo info = resolveInfos.get(i); // allow activities that are defined in the provided package if (allowDynamicSplits - && info.activityInfo != null && info.activityInfo.splitName != null && !ArrayUtils.contains(info.activityInfo.applicationInfo.splitNames, info.activityInfo.splitName)) { - if (mInstantAppInstallerActivity == null) { + if (mInstantAppInstallerInfo == null) { if (DEBUG_INSTALL) { Slog.v(TAG, "No installer - not adding it to the ResolveInfo list"); } @@ -6852,15 +6833,14 @@ Slog.e("TODD", if (DEBUG_INSTALL) { Slog.v(TAG, "Adding installer to the ResolveInfo list"); } - final ResolveInfo installerInfo = new ResolveInfo( - mInstantAppInstallerInfo); + final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo); final ComponentName installFailureActivity = findInstallFailureActivity( info.activityInfo.packageName, filterCallingUid, userId); installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( + info.activityInfo.packageName, info.activityInfo.splitName, installFailureActivity, - info.activityInfo.packageName, info.activityInfo.applicationInfo.versionCode, - info.activityInfo.splitName); + null /*failureIntent*/); installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART | IntentFilter.MATCH_ADJUSTMENT_NORMAL; // add a non-generic filter @@ -6877,7 +6857,6 @@ Slog.e("TODD", installerInfo.priority = info.priority; installerInfo.preferredOrder = info.preferredOrder; installerInfo.isDefault = info.isDefault; - installerInfo.isInstantAppAvailable = true; resolveInfos.set(i, installerInfo); continue; } @@ -6935,6 +6914,17 @@ Slog.e("TODD", return resolveInfos.size() > 0 && resolveInfos.get(0).priority >= 0; } + private static boolean hasWebURI(Intent intent) { + if (intent.getData() == null) { + return false; + } + final String scheme = intent.getScheme(); + if (TextUtils.isEmpty(scheme)) { + return false; + } + return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS); + } + private List filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent, int matchFlags, List candidates, CrossProfileDomainInfo xpDomainInfo, int userId) { @@ -7607,13 +7597,11 @@ Slog.e("TODD", if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } - final ResolveInfo installerInfo = new ResolveInfo( - mInstantAppInstallerInfo); + final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo); installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( - null /* installFailureActivity */, - info.serviceInfo.packageName, - info.serviceInfo.applicationInfo.versionCode, - info.serviceInfo.splitName); + info.serviceInfo.packageName, info.serviceInfo.splitName, + null /*failureActivity*/, info.serviceInfo.applicationInfo.versionCode, + null /*failureIntent*/); // make sure this resolver is the default installerInfo.isDefault = true; installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART @@ -7729,13 +7717,11 @@ Slog.e("TODD", if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } - final ResolveInfo installerInfo = new ResolveInfo( - mInstantAppInstallerInfo); + final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo); installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( - null /*failureActivity*/, - info.providerInfo.packageName, - info.providerInfo.applicationInfo.versionCode, - info.providerInfo.splitName); + info.providerInfo.packageName, info.providerInfo.splitName, + null /*failureActivity*/, info.providerInfo.applicationInfo.versionCode, + null /*failureIntent*/); // make sure this resolver is the default installerInfo.isDefault = true; installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART @@ -11775,7 +11761,7 @@ Slog.e("TODD", mInstantAppInstallerActivity.exported = true; mInstantAppInstallerActivity.enabled = true; mInstantAppInstallerInfo.activityInfo = mInstantAppInstallerActivity; - mInstantAppInstallerInfo.priority = 1; + mInstantAppInstallerInfo.priority = 0; mInstantAppInstallerInfo.preferredOrder = 1; mInstantAppInstallerInfo.isDefault = true; mInstantAppInstallerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART @@ -13225,8 +13211,7 @@ Slog.e("TODD", } static final class EphemeralIntentResolver - extends IntentResolver { + extends IntentResolver { /** * The result that has the highest defined order. Ordering applies on a * per-package basis. Mapping is from package name to Pair of order and @@ -13241,19 +13226,18 @@ Slog.e("TODD", final ArrayMap> mOrderResult = new ArrayMap<>(); @Override - protected AuxiliaryResolveInfo.AuxiliaryFilter[] newArray(int size) { - return new AuxiliaryResolveInfo.AuxiliaryFilter[size]; + protected AuxiliaryResolveInfo[] newArray(int size) { + return new AuxiliaryResolveInfo[size]; } @Override - protected boolean isPackageForFilter(String packageName, - AuxiliaryResolveInfo.AuxiliaryFilter responseObj) { + protected boolean isPackageForFilter(String packageName, AuxiliaryResolveInfo responseObj) { return true; } @Override - protected AuxiliaryResolveInfo.AuxiliaryFilter newResult( - AuxiliaryResolveInfo.AuxiliaryFilter responseObj, int match, int userId) { + protected AuxiliaryResolveInfo newResult(AuxiliaryResolveInfo responseObj, int match, + int userId) { if (!sUserManager.exists(userId)) { return null; } @@ -13274,7 +13258,7 @@ Slog.e("TODD", } @Override - protected void filterResults(List results) { + protected void filterResults(List results) { // only do work if ordering is enabled [most of the time it won't be] if (mOrderResult.size() == 0) { return; -- GitLab From 622b1f1b64ae717e5c2a1f611965e0a94ec06934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christofer=20=C3=85kersten?= Date: Tue, 30 Jan 2018 18:30:07 +0900 Subject: [PATCH 145/416] Move MediaSession2 builder to impl Bug: 72665881 Test: runtest-MediaComponents Change-Id: I22a299dac9d9576e4383fe9538fe696f09afaae5 --- .../android/media/MediaLibraryService2.java | 53 +++----- media/java/android/media/MediaSession2.java | 114 +++++------------- .../update/MediaLibraryService2Provider.java | 3 + .../media/update/MediaSession2Provider.java | 14 ++- .../android/media/update/ProviderCreator.java | 23 ++++ .../android/media/update/StaticProvider.java | 18 +-- 6 files changed, 92 insertions(+), 133 deletions(-) create mode 100644 media/java/android/media/update/ProviderCreator.java diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java index 79b105fb970..a901c6895d9 100644 --- a/media/java/android/media/MediaLibraryService2.java +++ b/media/java/android/media/MediaLibraryService2.java @@ -19,6 +19,7 @@ package android.media; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.app.PendingIntent; import android.content.Context; import android.media.MediaSession2.BuilderBase; @@ -63,26 +64,16 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 { /** * Session for the media library service. */ - public class MediaLibrarySession extends MediaSession2 { + public static class MediaLibrarySession extends MediaSession2 { private final MediaLibrarySessionProvider mProvider; - MediaLibrarySession(Context context, MediaPlayerInterface player, String id, - VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity, - Executor callbackExecutor, SessionCallback callback) { - super(context, player, id, volumeProvider, ratingType, sessionActivity, - callbackExecutor, callback); - mProvider = (MediaLibrarySessionProvider) getProvider(); - } - - @Override - MediaSession2Provider createProvider(Context context, MediaPlayerInterface player, - String id, VolumeProvider volumeProvider, int ratingType, - PendingIntent sessionActivity, Executor callbackExecutor, - SessionCallback callback) { - return ApiLoader.getProvider(context) - .createMediaLibraryService2MediaLibrarySession(context, this, player, id, - volumeProvider, ratingType, sessionActivity, - callbackExecutor, (MediaLibrarySessionCallback) callback); + /** + * @hide + */ + @SystemApi + public MediaLibrarySession(MediaLibrarySessionProvider provider) { + super(provider); + mProvider = provider; } /** @@ -208,31 +199,15 @@ public abstract class MediaLibraryService2 extends MediaSessionService2 { /** * Builder for {@link MediaLibrarySession}. */ - // TODO(jaewan): Move this to updatable. - public class MediaLibrarySessionBuilder - extends BuilderBase { + public class MediaLibrarySessionBuilder extends BuilderBase { public MediaLibrarySessionBuilder( @NonNull Context context, @NonNull MediaPlayerInterface player, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull MediaLibrarySessionCallback callback) { - super(context, player); - setSessionCallback(callbackExecutor, callback); - } - - @Override - public MediaLibrarySessionBuilder setSessionCallback( - @NonNull @CallbackExecutor Executor callbackExecutor, - @NonNull MediaLibrarySessionCallback callback) { - if (callback == null) { - throw new IllegalArgumentException("MediaLibrarySessionCallback cannot be null"); - } - return super.setSessionCallback(callbackExecutor, callback); - } - - @Override - public MediaLibrarySession build() { - return new MediaLibrarySession(mContext, mPlayer, mId, mVolumeProvider, mRatingType, - mSessionActivity, mCallbackExecutor, mCallback); + super((instance) -> ApiLoader.getProvider(context).createMediaLibraryService2Builder( + context, (MediaLibrarySessionBuilder) instance, player, callbackExecutor, + callback)); } } diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 2d55cc6fc7a..a4105e26f16 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -30,9 +30,11 @@ import android.media.session.MediaSession.Callback; import android.media.session.PlaybackState; import android.media.update.ApiLoader; import android.media.update.MediaSession2Provider; +import android.media.update.MediaSession2Provider.BuilderBaseProvider; import android.media.update.MediaSession2Provider.CommandGroupProvider; import android.media.update.MediaSession2Provider.CommandProvider; import android.media.update.MediaSession2Provider.ControllerInfoProvider; +import android.media.update.ProviderCreator; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -403,36 +405,11 @@ public class MediaSession2 implements AutoCloseable { * @hide */ static abstract class BuilderBase - , C extends SessionCallback> { - final Context mContext; - final MediaPlayerInterface mPlayer; - String mId; - Executor mCallbackExecutor; - C mCallback; - VolumeProvider mVolumeProvider; - int mRatingType; - PendingIntent mSessionActivity; + , C extends SessionCallback> { + private final BuilderBaseProvider mProvider; - /** - * Constructor. - * - * @param context a context - * @param player a player to handle incoming command from any controller. - * @throws IllegalArgumentException if any parameter is null, or the player is a - * {@link MediaSession2} or {@link MediaController2}. - */ - // TODO(jaewan): Also need executor - public BuilderBase(@NonNull Context context, @NonNull MediaPlayerInterface player) { - if (context == null) { - throw new IllegalArgumentException("context shouldn't be null"); - } - if (player == null) { - throw new IllegalArgumentException("player shouldn't be null"); - } - mContext = context; - mPlayer = player; - // Ensure non-null - mId = ""; + BuilderBase(ProviderCreator, BuilderBaseProvider> creator) { + mProvider = creator.createProvider(this); } /** @@ -444,9 +421,9 @@ public class MediaSession2 implements AutoCloseable { * * @param volumeProvider The provider that will handle volume changes. Can be {@code null} */ - public T setVolumeProvider(@Nullable VolumeProvider volumeProvider) { - mVolumeProvider = volumeProvider; - return (T) this; + public U setVolumeProvider(@Nullable VolumeProvider volumeProvider) { + mProvider.setVolumeProvider_impl(volumeProvider); + return (U) this; } /** @@ -462,9 +439,9 @@ public class MediaSession2 implements AutoCloseable { *
  • {@link Rating2#RATING_THUMB_UP_DOWN}
  • * */ - public T setRatingType(@Rating2.Style int type) { - mRatingType = type; - return (T) this; + public U setRatingType(@Rating2.Style int type) { + mProvider.setRatingType_impl(type); + return (U) this; } /** @@ -474,9 +451,9 @@ public class MediaSession2 implements AutoCloseable { * * @param pi The intent to launch to show UI for this session. */ - public T setSessionActivity(@Nullable PendingIntent pi) { - mSessionActivity = pi; - return (T) this; + public U setSessionActivity(@Nullable PendingIntent pi) { + mProvider.setSessionActivity_impl(pi); + return (U) this; } /** @@ -489,12 +466,9 @@ public class MediaSession2 implements AutoCloseable { * @throws IllegalArgumentException if id is {@code null} * @return */ - public T setId(@NonNull String id) { - if (id == null) { - throw new IllegalArgumentException("id shouldn't be null"); - } - mId = id; - return (T) this; + public U setId(@NonNull String id) { + mProvider.setId_impl(id); + return (U) this; } /** @@ -504,17 +478,10 @@ public class MediaSession2 implements AutoCloseable { * @param callback session callback. * @return */ - public T setSessionCallback(@NonNull @CallbackExecutor Executor executor, + public U setSessionCallback(@NonNull @CallbackExecutor Executor executor, @NonNull C callback) { - if (executor == null) { - throw new IllegalArgumentException("executor shouldn't be null"); - } - if (callback == null) { - throw new IllegalArgumentException("callback shouldn't be null"); - } - mCallbackExecutor = executor; - mCallback = callback; - return (T) this; + mProvider.setSessionCallback_impl(executor, callback); + return (U) this; } /** @@ -524,7 +491,9 @@ public class MediaSession2 implements AutoCloseable { * @throws IllegalStateException if the session with the same id is already exists for the * package. */ - public abstract MediaSession2 build(); + public T build() { + return mProvider.build_impl(); + } } /** @@ -533,24 +502,12 @@ public class MediaSession2 implements AutoCloseable { * Any incoming event from the {@link MediaController2} will be handled on the thread * that created session with the {@link Builder#build()}. */ - // TODO(jaewan): Move this to updatable // TODO(jaewan): Add setRatingType() // TODO(jaewan): Add setSessionActivity() - public static final class Builder extends BuilderBase { + public static final class Builder extends BuilderBase { public Builder(Context context, @NonNull MediaPlayerInterface player) { - super(context, player); - } - - @Override - public MediaSession2 build() { - if (mCallbackExecutor == null) { - mCallbackExecutor = mContext.getMainExecutor(); - } - if (mCallback == null) { - mCallback = new SessionCallback(mContext); - } - return new MediaSession2(mContext, mPlayer, mId, mVolumeProvider, mRatingType, - mSessionActivity, mCallbackExecutor, mCallback); + super((instance) -> ApiLoader.getProvider(context).createMediaSession2Builder( + context, (Builder) instance, player)); } } @@ -932,21 +889,10 @@ public class MediaSession2 implements AutoCloseable { * framework had to add heuristics to figure out if an app is * @hide */ - MediaSession2(Context context, MediaPlayerInterface player, String id, - VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity, - Executor callbackExecutor, SessionCallback callback) { + @SystemApi + public MediaSession2(MediaSession2Provider provider) { super(); - mProvider = createProvider(context, player, id, volumeProvider, ratingType, sessionActivity, - callbackExecutor, callback); - mProvider.initialize(); - } - - MediaSession2Provider createProvider(Context context, MediaPlayerInterface player, String id, - VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity, - Executor callbackExecutor, SessionCallback callback) { - return ApiLoader.getProvider(context) - .createMediaSession2(context, this, player, id, volumeProvider, ratingType, - sessionActivity, callbackExecutor, callback); + mProvider = provider; } @SystemApi diff --git a/media/java/android/media/update/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java index a5688391080..87f509a932c 100644 --- a/media/java/android/media/update/MediaLibraryService2Provider.java +++ b/media/java/android/media/update/MediaLibraryService2Provider.java @@ -17,12 +17,15 @@ package android.media.update; import android.annotation.SystemApi; +import android.media.MediaLibraryService2.MediaLibrarySession; +import android.media.MediaLibraryService2.MediaLibrarySessionCallback; import android.media.MediaSession2.ControllerInfo; import android.os.Bundle; /** * @hide */ +// TODO: @SystemApi public interface MediaLibraryService2Provider extends MediaSessionService2Provider { // Nothing new for now diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java index 0c8c2e37104..da4d0c74b30 100644 --- a/media/java/android/media/update/MediaSession2Provider.java +++ b/media/java/android/media/update/MediaSession2Provider.java @@ -16,15 +16,18 @@ package android.media.update; +import android.app.PendingIntent; import android.media.MediaItem2; import android.media.MediaMetadata2; import android.media.MediaPlayerInterface; import android.media.MediaPlayerInterface.PlaybackListener; +import android.media.MediaSession2; import android.media.MediaSession2.Command; import android.media.MediaSession2.CommandButton; import android.media.MediaSession2.CommandGroup; import android.media.MediaSession2.ControllerInfo; import android.media.MediaSession2.PlaylistParams; +import android.media.MediaSession2.SessionCallback; import android.media.SessionToken2; import android.media.VolumeProvider; import android.os.Bundle; @@ -38,8 +41,6 @@ import java.util.concurrent.Executor; */ // TODO: @SystemApi public interface MediaSession2Provider extends TransportControlProvider { - void initialize(); - void close_impl(); void setPlayer_impl(MediaPlayerInterface player); void setPlayer_impl(MediaPlayerInterface player, VolumeProvider volumeProvider); @@ -95,4 +96,13 @@ public interface MediaSession2Provider extends TransportControlProvider { MediaMetadata2 getPlaylistMetadata_impl(); Bundle toBundle_impl(); } + + interface BuilderBaseProvider { + void setVolumeProvider_impl(VolumeProvider volumeProvider); + void setRatingType_impl(int type); + void setSessionActivity_impl(PendingIntent pi); + void setId_impl(String id); + void setSessionCallback_impl(Executor executor, C callback); + T build_impl(); + } } diff --git a/media/java/android/media/update/ProviderCreator.java b/media/java/android/media/update/ProviderCreator.java new file mode 100644 index 00000000000..f5f3e470812 --- /dev/null +++ b/media/java/android/media/update/ProviderCreator.java @@ -0,0 +1,23 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +/** @hide */ +@FunctionalInterface +public interface ProviderCreator { + U createProvider(T instance); +} diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 9282494b32d..5dbb1244d51 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -27,6 +27,7 @@ import android.media.MediaController2.ControllerCallback; import android.media.MediaItem2; import android.media.MediaLibraryService2; import android.media.MediaLibraryService2.MediaLibrarySession; +import android.media.MediaLibraryService2.MediaLibrarySessionBuilder; import android.media.MediaLibraryService2.MediaLibrarySessionCallback; import android.media.MediaMetadata2; import android.media.MediaPlayerInterface; @@ -39,6 +40,7 @@ import android.media.SessionPlayer2; import android.media.SessionToken2; import android.media.VolumeProvider; import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider; +import android.media.update.MediaSession2Provider.BuilderBaseProvider; import android.media.update.MediaSession2Provider.CommandGroupProvider; import android.media.update.MediaSession2Provider.CommandProvider; import android.media.update.MediaSession2Provider.ControllerInfoProvider; @@ -65,9 +67,6 @@ public interface StaticProvider { VideoView2 instance, ViewProvider superProvider, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes); - MediaSession2Provider createMediaSession2(Context context, MediaSession2 instance, - MediaPlayerInterface player, String id, VolumeProvider volumeProvider, int ratingType, - PendingIntent sessionActivity, Executor executor, SessionCallback callback); CommandProvider createMediaSession2Command(MediaSession2.Command instance, int commandCode, String action, Bundle extra); MediaSession2.Command fromBundle_MediaSession2Command(Context context, Bundle bundle); @@ -81,6 +80,9 @@ public interface StaticProvider { PlaylistParams playlistParams, int repeatMode, int shuffleMode, MediaMetadata2 playlistMetadata); PlaylistParams fromBundle_PlaylistParams(Context context, Bundle bundle); + BuilderBaseProvider createMediaSession2Builder( + Context context, MediaSession2.Builder instance, MediaPlayerInterface player); + MediaController2Provider createMediaController2(Context context, MediaController2 instance, SessionToken2 token, Executor executor, ControllerCallback callback); @@ -88,12 +90,12 @@ public interface StaticProvider { SessionToken2 token, Executor executor, BrowserCallback callback); MediaSessionService2Provider createMediaSessionService2(MediaSessionService2 instance); - MediaSessionService2Provider createMediaLibraryService2(MediaLibraryService2 instance); - MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(Context context, - MediaLibrarySession instance, MediaPlayerInterface player, String id, - VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity, - Executor executor, MediaLibrarySessionCallback callback); + MediaSessionService2Provider createMediaLibraryService2(MediaLibraryService2 instance); + BuilderBaseProvider + createMediaLibraryService2Builder( + Context context, MediaLibrarySessionBuilder instance, MediaPlayerInterface player, + Executor callbackExecutor, MediaLibrarySessionCallback callback); SessionToken2Provider createSessionToken2(Context context, SessionToken2 instance, String packageName, String serviceName, int uid); -- GitLab From a6c97e4615890f491f14f22409657b7b7d723dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christofer=20=C3=85kersten?= Date: Thu, 18 Jan 2018 20:50:47 +0900 Subject: [PATCH 146/416] Move View related methods to helper class Test: Instantiate MediaControlView2 Change-Id: Ia958a97b38e67f236ee510978c379af9249d2887 --- .../android/widget/MediaControlView2.java | 96 ++----------- core/java/android/widget/VideoView2.java | 94 +------------ .../media/update/FrameLayoutHelper.java | 128 ++++++++++++++++++ 3 files changed, 142 insertions(+), 176 deletions(-) create mode 100644 media/java/android/media/update/FrameLayoutHelper.java diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java index 39c23b413cc..5f6d9ce665a 100644 --- a/core/java/android/widget/MediaControlView2.java +++ b/core/java/android/widget/MediaControlView2.java @@ -22,13 +22,13 @@ import android.annotation.Nullable; import android.content.Context; import android.media.session.MediaController; import android.media.update.ApiLoader; +import android.media.update.FrameLayoutHelper; import android.media.update.MediaControlView2Provider; -import android.media.update.ViewProvider; import android.util.AttributeSet; -import android.view.MotionEvent; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; + /** * A View that contains the controls for MediaPlayer2. * It provides a wide range of UI including buttons such as "Play/Pause", "Rewind", "Fast Forward", @@ -61,7 +61,7 @@ import java.lang.annotation.RetentionPolicy; * TODO PUBLIC API * @hide */ -public class MediaControlView2 extends FrameLayout { +public class MediaControlView2 extends FrameLayoutHelper { /** @hide */ @IntDef({ BUTTON_PLAY_PAUSE, @@ -140,8 +140,6 @@ public class MediaControlView2 extends FrameLayout { */ public static final String COMMAND_SET_FULLSCREEN = "setFullscreen"; - private final MediaControlView2Provider mProvider; - public MediaControlView2(@NonNull Context context) { this(context, null); } @@ -157,17 +155,10 @@ public class MediaControlView2 extends FrameLayout { public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - mProvider = ApiLoader.getProvider(context) - .createMediaControlView2(this, new SuperProvider()); - } - - /** - * @hide - */ - public MediaControlView2Provider getProvider() { - return mProvider; + super((instance, superProvider) -> + ApiLoader.getProvider(context).createMediaControlView2( + (MediaControlView2) instance, superProvider), + context, attrs, defStyleAttr, defStyleRes); } /** @@ -232,80 +223,9 @@ public class MediaControlView2 extends FrameLayout { } @Override + // TODO Move this method to ViewProvider public void onVisibilityAggregated(boolean isVisible) { mProvider.onVisibilityAggregated_impl(isVisible); } - - @Override - protected void onAttachedToWindow() { - mProvider.onAttachedToWindow_impl(); - } - - @Override - protected void onDetachedFromWindow() { - mProvider.onDetachedFromWindow_impl(); - } - - @Override - public CharSequence getAccessibilityClassName() { - return mProvider.getAccessibilityClassName_impl(); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return mProvider.onTouchEvent_impl(ev); - } - - @Override - public boolean onTrackballEvent(MotionEvent ev) { - return mProvider.onTrackballEvent_impl(ev); - } - - @Override - public void onFinishInflate() { - mProvider.onFinishInflate_impl(); - } - - @Override - public void setEnabled(boolean enabled) { - mProvider.setEnabled_impl(enabled); - } - - private class SuperProvider implements ViewProvider { - @Override - public void onAttachedToWindow_impl() { - MediaControlView2.super.onAttachedToWindow(); - } - - @Override - public void onDetachedFromWindow_impl() { - MediaControlView2.super.onDetachedFromWindow(); - } - - @Override - public CharSequence getAccessibilityClassName_impl() { - return MediaControlView2.super.getAccessibilityClassName(); - } - - @Override - public boolean onTouchEvent_impl(MotionEvent ev) { - return MediaControlView2.super.onTouchEvent(ev); - } - - @Override - public boolean onTrackballEvent_impl(MotionEvent ev) { - return MediaControlView2.super.onTrackballEvent(ev); - } - - @Override - public void onFinishInflate_impl() { - MediaControlView2.super.onFinishInflate(); - } - - @Override - public void setEnabled_impl(boolean enabled) { - MediaControlView2.super.setEnabled(enabled); - } - } } diff --git a/core/java/android/widget/VideoView2.java b/core/java/android/widget/VideoView2.java index 8404223d91c..78ca0114b79 100644 --- a/core/java/android/widget/VideoView2.java +++ b/core/java/android/widget/VideoView2.java @@ -27,12 +27,11 @@ import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.media.update.ApiLoader; +import android.media.update.FrameLayoutHelper; import android.media.update.VideoView2Provider; -import android.media.update.ViewProvider; import android.net.Uri; import android.os.Bundle; import android.util.AttributeSet; -import android.view.MotionEvent; import android.view.View; import java.lang.annotation.Retention; @@ -102,7 +101,7 @@ import java.util.concurrent.Executor; * * @hide */ -public class VideoView2 extends FrameLayout { +public class VideoView2 extends FrameLayoutHelper { /** @hide */ @IntDef({ VIEW_TYPE_TEXTUREVIEW, @@ -125,8 +124,6 @@ public class VideoView2 extends FrameLayout { */ public static final int VIEW_TYPE_TEXTUREVIEW = 2; - private final VideoView2Provider mProvider; - public VideoView2(@NonNull Context context) { this(context, null); } @@ -142,17 +139,10 @@ public class VideoView2 extends FrameLayout { public VideoView2( @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - mProvider = ApiLoader.getProvider(context).createVideoView2(this, new SuperProvider(), - attrs, defStyleAttr, defStyleRes); - } - - /** - * @hide - */ - public VideoView2Provider getProvider() { - return mProvider; + super((instance, superProvider) -> + ApiLoader.getProvider(context).createVideoView2( + (VideoView2) instance, superProvider, attrs, defStyleAttr, defStyleRes), + context, attrs, defStyleAttr, defStyleRes); } /** @@ -497,76 +487,4 @@ public class VideoView2 extends FrameLayout { */ void onCustomAction(String action, Bundle extras); } - - @Override - protected void onAttachedToWindow() { - mProvider.onAttachedToWindow_impl(); - } - - @Override - protected void onDetachedFromWindow() { - mProvider.onDetachedFromWindow_impl(); - } - - @Override - public CharSequence getAccessibilityClassName() { - return mProvider.getAccessibilityClassName_impl(); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - return mProvider.onTouchEvent_impl(ev); - } - - @Override - public boolean onTrackballEvent(MotionEvent ev) { - return mProvider.onTrackballEvent_impl(ev); - } - - @Override - public void onFinishInflate() { - mProvider.onFinishInflate_impl(); - } - - @Override - public void setEnabled(boolean enabled) { - mProvider.setEnabled_impl(enabled); - } - - private class SuperProvider implements ViewProvider { - @Override - public void onAttachedToWindow_impl() { - VideoView2.super.onAttachedToWindow(); - } - - @Override - public void onDetachedFromWindow_impl() { - VideoView2.super.onDetachedFromWindow(); - } - - @Override - public CharSequence getAccessibilityClassName_impl() { - return VideoView2.super.getAccessibilityClassName(); - } - - @Override - public boolean onTouchEvent_impl(MotionEvent ev) { - return VideoView2.super.onTouchEvent(ev); - } - - @Override - public boolean onTrackballEvent_impl(MotionEvent ev) { - return VideoView2.super.onTrackballEvent(ev); - } - - @Override - public void onFinishInflate_impl() { - VideoView2.super.onFinishInflate(); - } - - @Override - public void setEnabled_impl(boolean enabled) { - VideoView2.super.setEnabled(enabled); - } - } } diff --git a/media/java/android/media/update/FrameLayoutHelper.java b/media/java/android/media/update/FrameLayoutHelper.java new file mode 100644 index 00000000000..983dc703a9b --- /dev/null +++ b/media/java/android/media/update/FrameLayoutHelper.java @@ -0,0 +1,128 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +/** + * Helper class for connecting the public API to an updatable implementation. + * + * @see ViewProvider + * + * @hide + */ +public abstract class FrameLayoutHelper extends FrameLayout { + /** @hide */ + final public T mProvider; + + /** @hide */ + public FrameLayoutHelper(ProviderCreator creator, + Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + mProvider = creator.createProvider(this, new SuperProvider()); + } + + /** @hide */ + // TODO @SystemApi + public T getProvider() { + return mProvider; + } + + @Override + public CharSequence getAccessibilityClassName() { + return mProvider.getAccessibilityClassName_impl(); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return mProvider.onTouchEvent_impl(ev); + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + return mProvider.onTrackballEvent_impl(ev); + } + + @Override + public void onFinishInflate() { + mProvider.onFinishInflate_impl(); + } + + @Override + public void setEnabled(boolean enabled) { + mProvider.setEnabled_impl(enabled); + } + + @Override + protected void onAttachedToWindow() { + mProvider.onAttachedToWindow_impl(); + } + + @Override + protected void onDetachedFromWindow() { + mProvider.onDetachedFromWindow_impl(); + } + + /** @hide */ + public class SuperProvider implements ViewProvider { + @Override + public CharSequence getAccessibilityClassName_impl() { + return FrameLayoutHelper.super.getAccessibilityClassName(); + } + + @Override + public boolean onTouchEvent_impl(MotionEvent ev) { + return FrameLayoutHelper.super.onTouchEvent(ev); + } + + @Override + public boolean onTrackballEvent_impl(MotionEvent ev) { + return FrameLayoutHelper.super.onTrackballEvent(ev); + } + + @Override + public void onFinishInflate_impl() { + FrameLayoutHelper.super.onFinishInflate(); + } + + @Override + public void setEnabled_impl(boolean enabled) { + FrameLayoutHelper.super.setEnabled(enabled); + } + + @Override + public void onAttachedToWindow_impl() { + FrameLayoutHelper.super.onAttachedToWindow(); + } + + @Override + public void onDetachedFromWindow_impl() { + FrameLayoutHelper.super.onDetachedFromWindow(); + } + } + + /** @hide */ + @FunctionalInterface + public interface ProviderCreator { + U createProvider(FrameLayoutHelper instance, ViewProvider superProvider); + } +} -- GitLab From 93b64c9a6290aa0f62f8ed5e7c4b6b8839a77264 Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Tue, 30 Jan 2018 18:50:34 -0800 Subject: [PATCH 147/416] Frameworks: Disable Errorprone warnings for platformprotoslite Protos are very noisy with MissingOverride and other warnings. Bug: 72714520 Test: m javac-check-platformprotoslite RUN_ERROR_PRONE=true Change-Id: I91cf96df12e82866dde36ab4f58b6ba7be9f2c4e --- Android.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Android.mk b/Android.mk index 35b5f92be31..d72e3989cf2 100644 --- a/Android.mk +++ b/Android.mk @@ -821,6 +821,8 @@ LOCAL_PROTOC_FLAGS := \ LOCAL_SRC_FILES := \ $(call all-proto-files-under, core/proto) \ $(call all-proto-files-under, libs/incident/proto/android/os) +# Protos have lots of MissingOverride and similar. +LOCAL_ERROR_PRONE_FLAGS := -XepDisableAllChecks include $(BUILD_STATIC_JAVA_LIBRARY) # ==== hiddenapi lists ======================================= -- GitLab From 1e0c91968e802d49c26e2e8d6ca6e8d31f451894 Mon Sep 17 00:00:00 2001 From: Patrick Baumann Date: Wed, 31 Jan 2018 02:30:36 +0000 Subject: [PATCH 148/416] Revert "Removes EphemrealResolverService and related" This reverts commit 5564f880db3292327872a07df8e230eee78be14b. Reason for revert: Resolve merge conflict for another revert (ag/3537193) Bug: 72710855 Change-Id: Id7c3a3993a45c588ee4668d7486d67d764541b1e --- api/current.txt | 18 +- api/system-removed.txt | 50 ++++ .../android/app/EphemeralResolverService.java | 104 ++++++++ .../app/InstantAppResolverService.java | 10 +- core/java/android/content/Intent.java | 30 +++ .../content/pm/EphemeralIntentFilter.java | 85 +++++++ .../content/pm/EphemeralResolveInfo.java | 224 ++++++++++++++++++ ....java => EphemeralResolverConnection.java} | 43 ++-- .../android/server/pm/InstantAppResolver.java | 28 +-- .../server/pm/PackageManagerService.java | 71 ++++-- 10 files changed, 591 insertions(+), 72 deletions(-) create mode 100644 core/java/android/app/EphemeralResolverService.java create mode 100644 core/java/android/content/pm/EphemeralIntentFilter.java create mode 100644 core/java/android/content/pm/EphemeralResolveInfo.java rename services/core/java/com/android/server/pm/{InstantAppResolverConnection.java => EphemeralResolverConnection.java} (91%) diff --git a/api/current.txt b/api/current.txt index b4adfe252e4..5998e7194a1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11608,15 +11608,15 @@ package android.content.res { public final class AssetManager implements java.lang.AutoCloseable { method public void close(); - method public final java.lang.String[] getLocales(); - method public final java.lang.String[] list(java.lang.String) throws java.io.IOException; - method public final java.io.InputStream open(java.lang.String) throws java.io.IOException; - method public final java.io.InputStream open(java.lang.String, int) throws java.io.IOException; - method public final android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException; - method public final android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException; - method public final android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException; - method public final android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException; - method public final android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException; + method public java.lang.String[] getLocales(); + method public java.lang.String[] list(java.lang.String) throws java.io.IOException; + method public java.io.InputStream open(java.lang.String) throws java.io.IOException; + method public java.io.InputStream open(java.lang.String, int) throws java.io.IOException; + method public android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException; + method public android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException; + method public android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException; + method public android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException; + method public android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException; field public static final int ACCESS_BUFFER = 3; // 0x3 field public static final int ACCESS_RANDOM = 1; // 0x1 field public static final int ACCESS_STREAMING = 2; // 0x2 diff --git a/api/system-removed.txt b/api/system-removed.txt index 48f43e0880d..b63703dc003 100644 --- a/api/system-removed.txt +++ b/api/system-removed.txt @@ -1,5 +1,13 @@ package android.app { + public abstract deprecated class EphemeralResolverService extends android.app.InstantAppResolverService { + ctor public EphemeralResolverService(); + method public android.os.Looper getLooper(); + method public abstract deprecated java.util.List onEphemeralResolveInfoList(int[], int); + method public android.content.pm.EphemeralResolveInfo onGetEphemeralIntentFilter(java.lang.String); + method public java.util.List onGetEphemeralResolveInfo(int[]); + } + public class Notification implements android.os.Parcelable { method public static java.lang.Class getNotificationStyleClass(java.lang.String); } @@ -23,7 +31,10 @@ package android.content { public class Intent implements java.lang.Cloneable android.os.Parcelable { field public static final deprecated java.lang.String ACTION_DEVICE_INITIALIZATION_WIZARD = "android.intent.action.DEVICE_INITIALIZATION_WIZARD"; + field public static final deprecated java.lang.String ACTION_EPHEMERAL_RESOLVER_SETTINGS = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS"; + field public static final deprecated java.lang.String ACTION_INSTALL_EPHEMERAL_PACKAGE = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE"; field public static final deprecated java.lang.String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR"; + field public static final deprecated java.lang.String ACTION_RESOLVE_EPHEMERAL_PACKAGE = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE"; field public static final deprecated java.lang.String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE"; field public static final deprecated java.lang.String EXTRA_CDMA_DEFAULT_ROAMING_INDICATOR = "cdmaDefaultRoamingIndicator"; field public static final deprecated java.lang.String EXTRA_CDMA_ROAMING_INDICATOR = "cdmaRoamingIndicator"; @@ -51,6 +62,45 @@ package android.content { } +package android.content.pm { + + public final deprecated class EphemeralIntentFilter implements android.os.Parcelable { + ctor public EphemeralIntentFilter(java.lang.String, java.util.List); + method public int describeContents(); + method public java.util.List getFilters(); + method public java.lang.String getSplitName(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public final deprecated class EphemeralResolveInfo implements android.os.Parcelable { + ctor public deprecated EphemeralResolveInfo(android.net.Uri, java.lang.String, java.util.List); + ctor public deprecated EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List); + ctor public EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List, int); + ctor public EphemeralResolveInfo(java.lang.String, java.lang.String, java.util.List); + method public int describeContents(); + method public byte[] getDigestBytes(); + method public int getDigestPrefix(); + method public deprecated java.util.List getFilters(); + method public java.util.List getIntentFilters(); + method public java.lang.String getPackageName(); + method public int getVersionCode(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final java.lang.String SHA_ALGORITHM = "SHA-256"; + } + + public static final class EphemeralResolveInfo.EphemeralDigest implements android.os.Parcelable { + ctor public EphemeralResolveInfo.EphemeralDigest(java.lang.String); + method public int describeContents(); + method public byte[][] getDigestBytes(); + method public int[] getDigestPrefix(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + +} + package android.media.tv { public final class TvInputManager { diff --git a/core/java/android/app/EphemeralResolverService.java b/core/java/android/app/EphemeralResolverService.java new file mode 100644 index 00000000000..d1c244136ec --- /dev/null +++ b/core/java/android/app/EphemeralResolverService.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.annotation.SystemApi; +import android.content.pm.EphemeralResolveInfo; +import android.content.pm.InstantAppResolveInfo; +import android.os.Build; +import android.os.Looper; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Base class for implementing the resolver service. + * @hide + * @removed + * @deprecated use InstantAppResolverService instead + */ +@Deprecated +@SystemApi +public abstract class EphemeralResolverService extends InstantAppResolverService { + private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; + private static final String TAG = "PackageManager"; + + /** + * Called to retrieve resolve info for ephemeral applications. + * + * @param digestPrefix The hash prefix of the ephemeral's domain. + * @param prefixMask A mask that was applied to each digest prefix. This should + * be used when comparing against the digest prefixes as all bits might + * not be set. + * @deprecated use {@link #onGetEphemeralResolveInfo(int[])} instead + */ + @Deprecated + public abstract List onEphemeralResolveInfoList( + int digestPrefix[], int prefix); + + /** + * Called to retrieve resolve info for ephemeral applications. + * + * @param digestPrefix The hash prefix of the ephemeral's domain. + */ + public List onGetEphemeralResolveInfo(int digestPrefix[]) { + return onEphemeralResolveInfoList(digestPrefix, 0xFFFFF000); + } + + /** + * Called to retrieve intent filters for ephemeral applications. + * + * @param hostName The name of the host to get intent filters for. + */ + public EphemeralResolveInfo onGetEphemeralIntentFilter(String hostName) { + throw new IllegalStateException("Must define"); + } + + @Override + public Looper getLooper() { + return super.getLooper(); + } + + void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, + InstantAppResolutionCallback callback) { + if (DEBUG_EPHEMERAL) { + Log.d(TAG, "Legacy resolver; getInstantAppResolveInfo;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + final List response = onGetEphemeralResolveInfo(digestPrefix); + final int responseSize = response == null ? 0 : response.size(); + final List resultList = new ArrayList<>(responseSize); + for (int i = 0; i < responseSize; i++) { + resultList.add(response.get(i).getInstantAppResolveInfo()); + } + callback.onInstantAppResolveInfo(resultList); + } + + void _onGetInstantAppIntentFilter(int[] digestPrefix, String token, + String hostName, InstantAppResolutionCallback callback) { + if (DEBUG_EPHEMERAL) { + Log.d(TAG, "Legacy resolver; getInstantAppIntentFilter;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + final EphemeralResolveInfo response = onGetEphemeralIntentFilter(hostName); + final List resultList = new ArrayList<>(1); + resultList.add(response.getInstantAppResolveInfo()); + callback.onInstantAppResolveInfo(resultList); + } +} diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java index 76a36820ed8..89aff36f883 100644 --- a/core/java/android/app/InstantAppResolverService.java +++ b/core/java/android/app/InstantAppResolverService.java @@ -43,7 +43,7 @@ import java.util.List; */ @SystemApi public abstract class InstantAppResolverService extends Service { - private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; private static final String TAG = "PackageManager"; /** @hide */ @@ -133,7 +133,7 @@ public abstract class InstantAppResolverService extends Service { @Override public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix, String token, int sequence, IRemoteCallback callback) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Phase1 called; posting"); } final SomeArgs args = SomeArgs.obtain(); @@ -148,7 +148,7 @@ public abstract class InstantAppResolverService extends Service { @Override public void getInstantAppIntentFilterList(Intent sanitizedIntent, int[] digestPrefix, String token, IRemoteCallback callback) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Phase2 called; posting"); } final SomeArgs args = SomeArgs.obtain(); @@ -203,7 +203,7 @@ public abstract class InstantAppResolverService extends Service { final String token = (String) args.arg3; final Intent intent = (Intent) args.arg4; final int sequence = message.arg1; - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "[" + token + "] Phase1 request;" + " prefix: " + Arrays.toString(digestPrefix)); } @@ -217,7 +217,7 @@ public abstract class InstantAppResolverService extends Service { final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; final Intent intent = (Intent) args.arg4; - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "[" + token + "] Phase2 request;" + " prefix: " + Arrays.toString(digestPrefix)); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 908465efa0c..b3c8737a283 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1553,6 +1553,16 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_INSTALL_FAILURE = "android.intent.action.INSTALL_FAILURE"; + /** + * @hide + * @removed + * @deprecated Do not use. This will go away. + * Replace with {@link #ACTION_INSTALL_INSTANT_APP_PACKAGE}. + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE + = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE"; /** * Activity Action: Launch instant application installer. *

    @@ -1566,6 +1576,16 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_INSTALL_INSTANT_APP_PACKAGE = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE"; + /** + * @hide + * @removed + * @deprecated Do not use. This will go away. + * Replace with {@link #ACTION_RESOLVE_INSTANT_APP_PACKAGE}. + */ + @SystemApi + @SdkConstant(SdkConstantType.SERVICE_ACTION) + public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE + = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE"; /** * Service Action: Resolve instant application. *

    @@ -1580,6 +1600,16 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE"; + /** + * @hide + * @removed + * @deprecated Do not use. This will go away. + * Replace with {@link #ACTION_INSTANT_APP_RESOLVER_SETTINGS}. + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_EPHEMERAL_RESOLVER_SETTINGS + = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS"; /** * Activity Action: Launch instant app settings. * diff --git a/core/java/android/content/pm/EphemeralIntentFilter.java b/core/java/android/content/pm/EphemeralIntentFilter.java new file mode 100644 index 00000000000..1dbbf816ed9 --- /dev/null +++ b/core/java/android/content/pm/EphemeralIntentFilter.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.IntentFilter; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Information about an ephemeral application intent filter. + * @hide + * @removed + */ +@Deprecated +@SystemApi +public final class EphemeralIntentFilter implements Parcelable { + private final InstantAppIntentFilter mInstantAppIntentFilter; + + public EphemeralIntentFilter(@Nullable String splitName, @NonNull List filters) { + mInstantAppIntentFilter = new InstantAppIntentFilter(splitName, filters); + } + + EphemeralIntentFilter(@NonNull InstantAppIntentFilter intentFilter) { + mInstantAppIntentFilter = intentFilter; + } + + EphemeralIntentFilter(Parcel in) { + mInstantAppIntentFilter = in.readParcelable(null /*loader*/); + } + + public String getSplitName() { + return mInstantAppIntentFilter.getSplitName(); + } + + public List getFilters() { + return mInstantAppIntentFilter.getFilters(); + } + + /** @hide */ + InstantAppIntentFilter getInstantAppIntentFilter() { + return mInstantAppIntentFilter; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(mInstantAppIntentFilter, flags); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public EphemeralIntentFilter createFromParcel(Parcel in) { + return new EphemeralIntentFilter(in); + } + @Override + public EphemeralIntentFilter[] newArray(int size) { + return new EphemeralIntentFilter[size]; + } + }; +} diff --git a/core/java/android/content/pm/EphemeralResolveInfo.java b/core/java/android/content/pm/EphemeralResolveInfo.java new file mode 100644 index 00000000000..12131a3ebc9 --- /dev/null +++ b/core/java/android/content/pm/EphemeralResolveInfo.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.IntentFilter; +import android.content.pm.InstantAppResolveInfo.InstantAppDigest; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * Information about an ephemeral application. + * @hide + * @removed + */ +@Deprecated +@SystemApi +public final class EphemeralResolveInfo implements Parcelable { + /** Algorithm that will be used to generate the domain digest */ + public static final String SHA_ALGORITHM = "SHA-256"; + + private final InstantAppResolveInfo mInstantAppResolveInfo; + @Deprecated + private final List mLegacyFilters; + + @Deprecated + public EphemeralResolveInfo(@NonNull Uri uri, @NonNull String packageName, + @NonNull List filters) { + if (uri == null || packageName == null || filters == null || filters.isEmpty()) { + throw new IllegalArgumentException(); + } + final List ephemeralFilters = new ArrayList<>(1); + ephemeralFilters.add(new EphemeralIntentFilter(packageName, filters)); + mInstantAppResolveInfo = new InstantAppResolveInfo(uri.getHost(), packageName, + createInstantAppIntentFilterList(ephemeralFilters)); + mLegacyFilters = new ArrayList(filters.size()); + mLegacyFilters.addAll(filters); + } + + @Deprecated + public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName, + @Nullable List filters) { + this(digest, packageName, filters, -1 /*versionCode*/); + } + + public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName, + @Nullable List filters, int versionCode) { + mInstantAppResolveInfo = new InstantAppResolveInfo( + digest.getInstantAppDigest(), packageName, + createInstantAppIntentFilterList(filters), versionCode); + mLegacyFilters = null; + } + + public EphemeralResolveInfo(@NonNull String hostName, @Nullable String packageName, + @Nullable List filters) { + this(new EphemeralDigest(hostName), packageName, filters); + } + + EphemeralResolveInfo(Parcel in) { + mInstantAppResolveInfo = in.readParcelable(null /*loader*/); + mLegacyFilters = new ArrayList(); + in.readList(mLegacyFilters, null /*loader*/); + } + + /** @hide */ + public InstantAppResolveInfo getInstantAppResolveInfo() { + return mInstantAppResolveInfo; + } + + private static List createInstantAppIntentFilterList( + List filters) { + if (filters == null) { + return null; + } + final int filterCount = filters.size(); + final List returnList = new ArrayList<>(filterCount); + for (int i = 0; i < filterCount; i++) { + returnList.add(filters.get(i).getInstantAppIntentFilter()); + } + return returnList; + } + + public byte[] getDigestBytes() { + return mInstantAppResolveInfo.getDigestBytes(); + } + + public int getDigestPrefix() { + return mInstantAppResolveInfo.getDigestPrefix(); + } + + public String getPackageName() { + return mInstantAppResolveInfo.getPackageName(); + } + + public List getIntentFilters() { + final List filters = mInstantAppResolveInfo.getIntentFilters(); + final int filterCount = filters.size(); + final List returnList = new ArrayList<>(filterCount); + for (int i = 0; i < filterCount; i++) { + returnList.add(new EphemeralIntentFilter(filters.get(i))); + } + return returnList; + } + + public int getVersionCode() { + return mInstantAppResolveInfo.getVersionCode(); + } + + @Deprecated + public List getFilters() { + return mLegacyFilters; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(mInstantAppResolveInfo, flags); + out.writeList(mLegacyFilters); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public EphemeralResolveInfo createFromParcel(Parcel in) { + return new EphemeralResolveInfo(in); + } + @Override + public EphemeralResolveInfo[] newArray(int size) { + return new EphemeralResolveInfo[size]; + } + }; + + /** + * Helper class to generate and store each of the digests and prefixes + * sent to the Ephemeral Resolver. + *

    + * Since intent filters may want to handle multiple hosts within a + * domain [eg “*.google.com”], the resolver is presented with multiple + * hash prefixes. For example, "a.b.c.d.e" generates digests for + * "d.e", "c.d.e", "b.c.d.e" and "a.b.c.d.e". + * + * @hide + */ + @SystemApi + public static final class EphemeralDigest implements Parcelable { + private final InstantAppDigest mInstantAppDigest; + + public EphemeralDigest(@NonNull String hostName) { + this(hostName, -1 /*maxDigests*/); + } + + /** @hide */ + public EphemeralDigest(@NonNull String hostName, int maxDigests) { + mInstantAppDigest = new InstantAppDigest(hostName, maxDigests); + } + + EphemeralDigest(Parcel in) { + mInstantAppDigest = in.readParcelable(null /*loader*/); + } + + /** @hide */ + InstantAppDigest getInstantAppDigest() { + return mInstantAppDigest; + } + + public byte[][] getDigestBytes() { + return mInstantAppDigest.getDigestBytes(); + } + + public int[] getDigestPrefix() { + return mInstantAppDigest.getDigestPrefix(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(mInstantAppDigest, flags); + } + + @SuppressWarnings("hiding") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public EphemeralDigest createFromParcel(Parcel in) { + return new EphemeralDigest(in); + } + @Override + public EphemeralDigest[] newArray(int size) { + return new EphemeralDigest[size]; + } + }; + } +} diff --git a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java similarity index 91% rename from services/core/java/com/android/server/pm/InstantAppResolverConnection.java rename to services/core/java/com/android/server/pm/EphemeralResolverConnection.java index a9ee41162ae..6d6c960eed7 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java +++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java @@ -37,29 +37,34 @@ import android.util.Slog; import android.util.TimedRemoteCaller; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.TransferPipe; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.TimeoutException; /** - * Represents a remote instant app resolver. It is responsible for binding to the remote + * Represents a remote ephemeral resolver. It is responsible for binding to the remote * service and handling all interactions in a timely manner. * @hide */ -final class InstantAppResolverConnection implements DeathRecipient { +final class EphemeralResolverConnection implements DeathRecipient { private static final String TAG = "PackageManager"; // This is running in a critical section and the timeout must be sufficiently low private static final long BIND_SERVICE_TIMEOUT_MS = Build.IS_ENG ? 500 : 300; private static final long CALL_SERVICE_TIMEOUT_MS = Build.IS_ENG ? 200 : 100; - private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; private final Object mLock = new Object(); - private final GetInstantAppResolveInfoCaller mGetInstantAppResolveInfoCaller = - new GetInstantAppResolveInfoCaller(); + private final GetEphemeralResolveInfoCaller mGetEphemeralResolveInfoCaller = + new GetEphemeralResolveInfoCaller(); private final ServiceConnection mServiceConnection = new MyServiceConnection(); private final Context mContext; /** Intent used to bind to the service */ @@ -74,7 +79,7 @@ final class InstantAppResolverConnection implements DeathRecipient { @GuardedBy("mLock") private IInstantAppResolver mRemoteInstance; - public InstantAppResolverConnection( + public EphemeralResolverConnection( Context context, ComponentName componentName, String action) { mContext = context; mIntent = new Intent(action).setComponent(componentName); @@ -93,8 +98,8 @@ final class InstantAppResolverConnection implements DeathRecipient { throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED); } try { - return mGetInstantAppResolveInfoCaller - .getInstantAppResolveInfoList(target, sanitizedIntent, hashPrefix, token); + return mGetEphemeralResolveInfoCaller + .getEphemeralResolveInfoList(target, sanitizedIntent, hashPrefix, token); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_CALL); } catch (RemoteException ignore) { @@ -166,7 +171,7 @@ final class InstantAppResolverConnection implements DeathRecipient { if (mBindState == STATE_PENDING) { // there is a pending bind, let's see if we can use it. - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.i(TAG, "[" + token + "] Previous bind timed out; waiting for connection"); } try { @@ -183,7 +188,7 @@ final class InstantAppResolverConnection implements DeathRecipient { if (mBindState == STATE_BINDING) { // someone was binding when we called bind(), or they raced ahead while we were // waiting in the PENDING case; wait for their result instead. Last chance! - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.i(TAG, "[" + token + "] Another thread is binding; waiting for connection"); } waitForBindLocked(token); @@ -201,12 +206,12 @@ final class InstantAppResolverConnection implements DeathRecipient { IInstantAppResolver instance = null; try { if (doUnbind) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.i(TAG, "[" + token + "] Previous connection never established; rebinding"); } mContext.unbindService(mServiceConnection); } - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Binding to instant app resolver"); } final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; @@ -242,7 +247,7 @@ final class InstantAppResolverConnection implements DeathRecipient { @Override public void binderDied() { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Binder to instant app resolver died"); } synchronized (mLock) { @@ -281,7 +286,7 @@ final class InstantAppResolverConnection implements DeathRecipient { private final class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Connected to instant app resolver"); } synchronized (mLock) { @@ -290,7 +295,7 @@ final class InstantAppResolverConnection implements DeathRecipient { mBindState = STATE_IDLE; } try { - service.linkToDeath(InstantAppResolverConnection.this, 0 /*flags*/); + service.linkToDeath(EphemeralResolverConnection.this, 0 /*flags*/); } catch (RemoteException e) { handleBinderDiedLocked(); } @@ -300,7 +305,7 @@ final class InstantAppResolverConnection implements DeathRecipient { @Override public void onServiceDisconnected(ComponentName name) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Disconnected from instant app resolver"); } synchronized (mLock) { @@ -309,11 +314,11 @@ final class InstantAppResolverConnection implements DeathRecipient { } } - private static final class GetInstantAppResolveInfoCaller + private static final class GetEphemeralResolveInfoCaller extends TimedRemoteCaller> { private final IRemoteCallback mCallback; - public GetInstantAppResolveInfoCaller() { + public GetEphemeralResolveInfoCaller() { super(CALL_SERVICE_TIMEOUT_MS); mCallback = new IRemoteCallback.Stub() { @Override @@ -328,7 +333,7 @@ final class InstantAppResolverConnection implements DeathRecipient { }; } - public List getInstantAppResolveInfoList( + public List getEphemeralResolveInfoList( IInstantAppResolver target, Intent sanitizedIntent, int hashPrefix[], String token) throws RemoteException, TimeoutException { final int sequence = onBeforeRemoteCall(); diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index 00fdb9d687d..55212cc6b3d 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -51,8 +51,8 @@ import android.util.Slog; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; -import com.android.server.pm.InstantAppResolverConnection.ConnectionException; -import com.android.server.pm.InstantAppResolverConnection.PhaseTwoCallback; +import com.android.server.pm.EphemeralResolverConnection.ConnectionException; +import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -66,7 +66,7 @@ import java.util.UUID; /** @hide */ public abstract class InstantAppResolver { - private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; private static final String TAG = "PackageManager"; private static final int RESOLUTION_SUCCESS = 0; @@ -117,10 +117,10 @@ public abstract class InstantAppResolver { } public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne( - InstantAppResolverConnection connection, InstantAppRequest requestObj) { + EphemeralResolverConnection connection, InstantAppRequest requestObj) { final long startTime = System.currentTimeMillis(); final String token = UUID.randomUUID().toString(); - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] Phase1; resolving"); } final Intent origIntent = requestObj.origIntent; @@ -152,7 +152,7 @@ public abstract class InstantAppResolver { logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token, resolutionStatus); } - if (DEBUG_INSTANT && resolveInfo == null) { + if (DEBUG_EPHEMERAL && resolveInfo == null) { if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) { Log.d(TAG, "[" + token + "] Phase1; bind timed out"); } else if (resolutionStatus == RESOLUTION_CALL_TIMEOUT) { @@ -173,11 +173,11 @@ public abstract class InstantAppResolver { } public static void doInstantAppResolutionPhaseTwo(Context context, - InstantAppResolverConnection connection, InstantAppRequest requestObj, + EphemeralResolverConnection connection, InstantAppRequest requestObj, ActivityInfo instantAppInstaller, Handler callbackHandler) { final long startTime = System.currentTimeMillis(); final String token = requestObj.responseObj.token; - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] Phase2; resolving"); } final Intent origIntent = requestObj.origIntent; @@ -234,7 +234,7 @@ public abstract class InstantAppResolver { } logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token, resolutionStatus); - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) { Log.d(TAG, "[" + token + "] Phase2; bind timed out"); } else { @@ -444,13 +444,13 @@ public abstract class InstantAppResolver { return null; } // No filters; we need to start phase two - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Log.d(TAG, "No app filters; go to phase 2"); } return Collections.emptyList(); } - final PackageManagerService.InstantAppIntentResolver instantAppResolver = - new PackageManagerService.InstantAppIntentResolver(); + final PackageManagerService.EphemeralIntentResolver instantAppResolver = + new PackageManagerService.EphemeralIntentResolver(); for (int j = instantAppFilters.size() - 1; j >= 0; --j) { final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); final List splitFilters = instantAppFilter.getFilters(); @@ -481,11 +481,11 @@ public abstract class InstantAppResolver { instantAppResolver.queryIntent( origIntent, resolvedType, false /*defaultOnly*/, userId); if (!matchedResolveInfoList.isEmpty()) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList); } return matchedResolveInfoList; - } else if (DEBUG_INSTANT) { + } else if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] No matches found" + " package: " + instantAppInfo.getPackageName() + ", versionCode: " + instantAppInfo.getVersionCode()); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 61569ae8c02..6402b5d2e4b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -424,7 +424,7 @@ public class PackageManagerService extends IPackageManager.Stub public static final boolean DEBUG_DEXOPT = false; private static final boolean DEBUG_ABI_SELECTION = false; - private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; private static final boolean DEBUG_TRIAGED_MISSING = false; private static final boolean DEBUG_APP_DATA = false; @@ -981,7 +981,7 @@ public class PackageManagerService extends IPackageManager.Stub private int mIntentFilterVerificationToken = 0; /** The service connection to the ephemeral resolver */ - final InstantAppResolverConnection mInstantAppResolverConnection; + final EphemeralResolverConnection mInstantAppResolverConnection; /** Component used to show resolver settings for Instant Apps */ final ComponentName mInstantAppResolverSettingsComponent; @@ -3149,10 +3149,10 @@ Slog.e("TODD", final Pair instantAppResolverComponent = getInstantAppResolverLPr(); if (instantAppResolverComponent != null) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Set ephemeral resolver: " + instantAppResolverComponent); } - mInstantAppResolverConnection = new InstantAppResolverConnection( + mInstantAppResolverConnection = new EphemeralResolverConnection( mContext, instantAppResolverComponent.first, instantAppResolverComponent.second); mInstantAppResolverSettingsComponent = @@ -3532,7 +3532,7 @@ Slog.e("TODD", final String[] packageArray = mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage); if (packageArray.length == 0 && !Build.IS_DEBUGGABLE) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Ephemeral resolver NOT found; empty package list"); } return null; @@ -3547,9 +3547,19 @@ Slog.e("TODD", final Intent resolverIntent = new Intent(actionName); List resolvers = queryIntentServicesInternal(resolverIntent, null, resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/); + // temporarily look for the old action + if (resolvers.size() == 0) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral resolver not found with new action; try old one"); + } + actionName = Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE; + resolverIntent.setAction(actionName); + resolvers = queryIntentServicesInternal(resolverIntent, null, + resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/); + } final int N = resolvers.size(); if (N == 0) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Ephemeral resolver NOT found; no matching intent filters"); } return null; @@ -3565,20 +3575,20 @@ Slog.e("TODD", final String packageName = info.serviceInfo.packageName; if (!possiblePackages.contains(packageName) && !Build.IS_DEBUGGABLE) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Ephemeral resolver not in allowed package list;" + " pkg: " + packageName + ", info:" + info); } continue; } - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Ephemeral resolver found;" + " pkg: " + packageName + ", info:" + info); } return new Pair<>(new ComponentName(packageName, info.serviceInfo.name), actionName); } - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Ephemeral resolver NOT found"); } return null; @@ -3588,9 +3598,11 @@ Slog.e("TODD", String[] orderedActions = Build.IS_ENG ? new String[]{ Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE + "_TEST", - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE} + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, + Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE} : new String[]{ - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE}; + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, + Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE}; final int resolveFlags = MATCH_DIRECT_BOOT_AWARE @@ -3606,7 +3618,7 @@ Slog.e("TODD", matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, resolveFlags, UserHandle.USER_SYSTEM); if (matches.isEmpty()) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Instant App installer not found with " + action); } } else { @@ -3644,6 +3656,15 @@ Slog.e("TODD", final int resolveFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; List matches = queryIntentActivitiesInternal(intent, null, resolveFlags, UserHandle.USER_SYSTEM); + // temporarily look for the old action + if (matches.isEmpty()) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral resolver settings not found with new action; try old one"); + } + intent.setAction(Intent.ACTION_EPHEMERAL_RESOLVER_SETTINGS); + matches = queryIntentActivitiesInternal(intent, null, resolveFlags, + UserHandle.USER_SYSTEM); + } if (matches.isEmpty()) { return null; } @@ -5986,7 +6007,7 @@ Slog.e("TODD", final int status = (int) (packedStatus >> 32); if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName + ", status: " + status); } @@ -5994,7 +6015,7 @@ Slog.e("TODD", } } if (ps.getInstantApp(userId)) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "DENY instant app installed;" + " pkg: " + packageName); } @@ -6630,7 +6651,7 @@ Slog.e("TODD", // there's a local instant application installed, but, the user has // chosen to never use it; skip resolution and don't acknowledge // an instant application is even available - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Instant app marked to never run; pkg: " + packageName); } blockResolution = true; @@ -6638,7 +6659,7 @@ Slog.e("TODD", } else { // we have a locally installed instant application; skip resolution // but acknowledge there's an instant application available - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Found installed instant app; pkg: " + packageName); } localInstantApp = info; @@ -6696,7 +6717,7 @@ Slog.e("TODD", // make sure this resolver is the default ephemeralInstaller.isDefault = true; ephemeralInstaller.auxiliaryInfo = auxiliaryResponse; - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } @@ -7583,7 +7604,7 @@ Slog.e("TODD", info.serviceInfo.splitName)) { // requested service is defined in a split that hasn't been installed yet. // add the installer to the resolve list - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } final ResolveInfo installerInfo = new ResolveInfo( @@ -7705,7 +7726,7 @@ Slog.e("TODD", info.providerInfo.splitName)) { // requested provider is defined in a split that hasn't been installed yet. // add the installer to the resolve list - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } final ResolveInfo installerInfo = new ResolveInfo( @@ -11747,14 +11768,14 @@ Slog.e("TODD", private void setUpInstantAppInstallerActivityLP(ActivityInfo installerActivity) { if (installerActivity == null) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Clear ephemeral installer activity"); } mInstantAppInstallerActivity = null; return; } - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Set ephemeral installer activity: " + installerActivity.getComponentName()); } @@ -13214,7 +13235,7 @@ Slog.e("TODD", private int mFlags; } - static final class InstantAppIntentResolver + static final class EphemeralIntentResolver extends IntentResolver { /** @@ -13584,7 +13605,7 @@ Slog.e("TODD", IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, String installerPackageName, int installerUid, UserHandle user, PackageParser.SigningDetails signingDetails) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { Slog.d(TAG, "Ephemeral install of " + packageName); } @@ -15068,7 +15089,7 @@ Slog.e("TODD", pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags, packageAbiOverride); - if (DEBUG_INSTANT && ephemeral) { + if (DEBUG_EPHEMERAL && ephemeral) { Slog.v(TAG, "pkgLite for install: " + pkgLite); } @@ -15135,7 +15156,7 @@ Slog.e("TODD", installFlags |= PackageManager.INSTALL_EXTERNAL; installFlags &= ~PackageManager.INSTALL_INTERNAL; } else if (loc == PackageHelper.RECOMMEND_INSTALL_EPHEMERAL) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "...setting INSTALL_EPHEMERAL install flag"); } installFlags |= PackageManager.INSTALL_INSTANT_APP; -- GitLab From 22ed4122e7849a4538208c65f403298fe0fff891 Mon Sep 17 00:00:00 2001 From: Dave Ingram Date: Tue, 30 Jan 2018 16:11:52 -0800 Subject: [PATCH 149/416] AAPT2: Fix typo in BinaryPrimitives oneofs Follow-up to ag/3449569 Bug: 69587794 Test: aapt2_tests Change-Id: I95d2ba600c00bda2a8420794e43501f9bfca01df --- tools/aapt2/Resources.proto | 2 +- tools/aapt2/format/proto/ProtoDeserialize.cpp | 4 ++-- tools/aapt2/format/proto/ProtoSerialize.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 069360e3d6f..df483b2bdb1 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -318,7 +318,7 @@ message Primitive { float dimension_value = 4; float fraction_value = 5; int32 int_decimal_value = 6; - uint32 int_hexidecimal_value = 7; + uint32 int_hexadecimal_value = 7; bool boolean_value = 8; uint32 color_argb8_value = 9; uint32 color_rgb8_value = 10; diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index 81bc2c88939..f1eb9528905 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -792,9 +792,9 @@ std::unique_ptr DeserializeItemFromPb(const pb::Item& pb_item, val.dataType = android::Res_value::TYPE_INT_DEC; val.data = static_cast(pb_prim.int_decimal_value()); } break; - case pb::Primitive::kIntHexidecimalValue: { + case pb::Primitive::kIntHexadecimalValue: { val.dataType = android::Res_value::TYPE_INT_HEX; - val.data = pb_prim.int_hexidecimal_value(); + val.data = pb_prim.int_hexadecimal_value(); } break; case pb::Primitive::kBooleanValue: { val.dataType = android::Res_value::TYPE_INT_BOOLEAN; diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index e9622f54f6a..1d00852eab7 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -460,7 +460,7 @@ class ValueSerializer : public ConstValueVisitor { pb_prim->set_int_decimal_value(static_cast(val.data)); } break; case android::Res_value::TYPE_INT_HEX: { - pb_prim->set_int_hexidecimal_value(val.data); + pb_prim->set_int_hexadecimal_value(val.data); } break; case android::Res_value::TYPE_INT_BOOLEAN: { pb_prim->set_boolean_value(static_cast(val.data)); -- GitLab From 7d30e1c554b76e7e6b54037ffe1926e0d927ab79 Mon Sep 17 00:00:00 2001 From: Sungsoo Lim Date: Wed, 31 Jan 2018 15:27:12 +0900 Subject: [PATCH 150/416] MediaSession2: Hide system apis Test: build Change-Id: I6c56c260a002086bbd2c20420b301c66635215c3 --- core/java/android/widget/MediaControlView2.java | 9 +++++++++ media/java/android/media/MediaController2.java | 3 +++ media/java/android/media/MediaSession2.java | 9 +++++++++ 3 files changed, 21 insertions(+) diff --git a/core/java/android/widget/MediaControlView2.java b/core/java/android/widget/MediaControlView2.java index 5f6d9ce665a..2e4cccfc230 100644 --- a/core/java/android/widget/MediaControlView2.java +++ b/core/java/android/widget/MediaControlView2.java @@ -19,6 +19,7 @@ package android.widget; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.content.Context; import android.media.session.MediaController; import android.media.update.ApiLoader; @@ -161,6 +162,14 @@ public class MediaControlView2 extends FrameLayoutHelper Date: Tue, 30 Jan 2018 10:25:58 -0800 Subject: [PATCH 151/416] Minor javadoc clarification. Test: mmm -j frameworks/base/:doc-comment-check-docs Bug: 72693031 Fixes: 72562886 Change-Id: Ia9c3ac12cb41eea7ee30f7ec6c2e68b5603751fd --- core/java/android/service/autofill/AutofillService.java | 9 ++++----- .../java/android/service/autofill/CustomDescription.java | 5 ++++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 917efa8bd5f..12aa64e4ecb 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -468,9 +468,8 @@ import com.android.internal.os.SomeArgs; *

    Typically, field classification can be used to detect fields that can be autofilled with * user data that is not associated with a specific app—such as email and physical * address. Once the service identifies that a such field was manually filled by the user, the - * service could use this signal to improve its heuristics, either locally (i.e., in the same - * device) or globally (i.e., by crowdsourcing the results back to the service's server so it can - * be used by other users). + * service could use this signal to improve its heuristics on subsequent requests (for example, by + * infering which resource ids are associated with known fields). * *

    The field classification workflow involves 4 steps: * @@ -481,8 +480,8 @@ import com.android.internal.os.SomeArgs; *

  • Identify which fields should be analysed by calling * {@link FillResponse.Builder#setFieldClassificationIds(AutofillId...)}. *
  • Verify the results through {@link FillEventHistory.Event#getFieldsClassification()}. - *
  • Use the results to dynamically create {@link Dataset} or {@link SaveInfo} objects in future - * requests. + *
  • Use the results to dynamically create {@link Dataset} or {@link SaveInfo} objects in + * subsequent requests. * * *

    The field classification is an expensive operation and should be used carefully, otherwise it diff --git a/core/java/android/service/autofill/CustomDescription.java b/core/java/android/service/autofill/CustomDescription.java index b8e8b19f978..fb468a8dad6 100644 --- a/core/java/android/service/autofill/CustomDescription.java +++ b/core/java/android/service/autofill/CustomDescription.java @@ -173,7 +173,10 @@ public final class CustomDescription implements Parcelable { } /** - * Updates the {@link RemoteViews presentation template} when a condition is satisfied. + * Updates the {@link RemoteViews presentation template} when a condition is satisfied by + * applying a series of remote view operations. This allows dynamic customization of the + * portion of the save UI that is controlled by the autofill service. Such dynamic + * customization is based on the content of target views. * *

    The updates are applied in the sequence they are added, after the * {@link #addChild(int, Transformation) transformations} are applied to the children -- GitLab From 71a96f27c6f20f3b4417f0325c19011e96c397b5 Mon Sep 17 00:00:00 2001 From: Jaewan Kim Date: Wed, 31 Jan 2018 13:28:13 +0900 Subject: [PATCH 152/416] MediaSession2: Polish command codes This is the preliminary step toward permission check. Bug: 72618604 Test: Run all MediaComponents test once Change-Id: I64679dce0c70fd322fa7819e4ba15ba52601d021 --- media/java/android/media/MediaSession2.java | 316 +++++++++++++++----- 1 file changed, 246 insertions(+), 70 deletions(-) diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 2d55cc6fc7a..515055205fb 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -81,34 +81,178 @@ import java.util.concurrent.Executor; public class MediaSession2 implements AutoCloseable { private final MediaSession2Provider mProvider; - // Note: Do not define IntDef because subclass can add more command code on top of these. + // TODO(jaewan): Should we define IntDef? Currently we don't have to allow subclass to add more. // TODO(jaewan): Shouldn't we pull out? - // TODO(jaewan): Should we also protect getPlaybackState()? + // TODO(jaewan): Should we also protect getters not related with metadata? + // Getters are getRatingType(), getPlaybackState(), getSessionActivity(), + // getPlaylistParams()) + // Next ID: 23 + /** + * Command code for the custom command which can be defined by string action in the + * {@link Command}. + */ public static final int COMMAND_CODE_CUSTOM = 0; - public static final int COMMAND_CODE_PLAYBACK_START = 1; + + /** + * Command code for {@link MediaController2#play()}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ + public static final int COMMAND_CODE_PLAYBACK_PLAY = 1; + + /** + * Command code for {@link MediaController2#pause()}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_PAUSE = 2; + + /** + * Command code for {@link MediaController2#stop()}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_STOP = 3; + + /** + * Command code for {@link MediaController2#skipToNext()} ()}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM = 4; + + /** + * Command code for {@link MediaController2#skipToPrevious()} ()}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM = 5; + + /** + * Command code for {@link MediaController2#prepare()}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_PREPARE = 6; + + /** + * Command code for {@link MediaController2#fastForward()} ()}. + *

    + * This is transport control command. Command would be sent directly to the player if the + * session doesn't reject the request through the + * {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_FAST_FORWARD = 7; + + /** + * Command code for {@link MediaController2#rewind()}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_REWIND = 8; + + /** + * Command code for {@link MediaController2#seekTo(long)} ()}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_SEEK_TO = 9; + /** + * Command code for {@link MediaController2#setCurrentPlaylistItem(int)} ()}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM = 10; - public static final int COMMAND_CODE_PLAYLIST_GET = 11; + /** + * Command code for {@link MediaController2#setPlaylistParams(PlaylistParams)} ()}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ + public static final int COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS = 11; + + /** + * Command code for {@link MediaController2#addPlaylistItem(int, MediaItem2)}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYLIST_ADD = 12; + + /** + * Command code for {@link MediaController2#addPlaylistItem(int, MediaItem2)}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ public static final int COMMAND_CODE_PLAYLIST_REMOVE = 13; - public static final int COMMAND_CODE_PLAY_FROM_MEDIA_ID = 14; - public static final int COMMAND_CODE_PLAY_FROM_URI = 15; - public static final int COMMAND_CODE_PLAY_FROM_SEARCH = 16; + /** + * Command code for {@link MediaController2#getPlaylist()}. + *

    + * Command would be sent directly to the player if the session doesn't reject the request + * through the {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ + public static final int COMMAND_CODE_PLAYLIST_GET = 14; - public static final int COMMAND_CODE_PREPARE_FROM_MEDIA_ID = 17; - public static final int COMMAND_CODE_PREPARE_FROM_URI = 18; - public static final int COMMAND_CODE_PREPARE_FROM_SEARCH = 19; + /** + * Command code for both {@link MediaController2#setVolumeTo(int, int)} and + * {@link MediaController2#adjustVolume(int, int)}. + *

    + * Command would adjust the volume or sent to the volume provider directly if the session + * doesn't reject the request through the + * {@link SessionCallback#onCommandRequest(ControllerInfo, Command)}. + */ + public static final int COMMAND_CODE_SET_VOLUME = 15; - public static final int COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS = 20; + /** + * Command code for {@link MediaController2#playFromMediaId(String, Bundle)}. + */ + public static final int COMMAND_CODE_PLAY_FROM_MEDIA_ID = 16; + + /** + * Command code for {@link MediaController2#playFromUri(String, Bundle)}. + */ + public static final int COMMAND_CODE_PLAY_FROM_URI = 17; + + /** + * Command code for {@link MediaController2#playFromSearch(String, Bundle)}. + */ + public static final int COMMAND_CODE_PLAY_FROM_SEARCH = 18; + + /** + * Command code for {@link MediaController2#prepareFromMediaId(String, Bundle)}. + */ + public static final int COMMAND_CODE_PREPARE_FROM_MEDIA_ID = 19; + + /** + * Command code for {@link MediaController2#prepareFromUri(Uri, Bundle)}. + */ + public static final int COMMAND_CODE_PREPARE_FROM_URI = 20; + + /** + * Command code for {@link MediaController2#prepareFromSearch(String, Bundle)}. + */ + public static final int COMMAND_CODE_PREPARE_FROM_SEARCH = 21; + + /** + * Command code for {@link MediaBrowser2} specific functions that allows navigation and search + * from the {@link MediaLibraryService2}. This would be ignored if a {@link MediaSession2}, + * not {@link android.media.MediaLibraryService2.MediaLibrarySession}, specify this. + * + * @see MediaBrowser2 + */ + public static final int COMMAND_CODE_BROWSER = 22; /** * Define a command that a {@link MediaController2} can send to a {@link MediaSession2}. @@ -274,22 +418,36 @@ public class MediaSession2 implements AutoCloseable { public void onDisconnected(@NonNull ControllerInfo controller) { } /** - * Called when a controller sent a command to the session, and the command will be sent to - * the player directly unless you reject the request by {@code false}. + * Called when a controller sent a command that will be sent directly to the player. Return + * {@code false} here to reject the request and stop sending command to the player. * * @param controller controller information. * @param command a command. This method will be called for every single command. * @return {@code true} if you want to accept incoming command. {@code false} otherwise. + * @see #COMMAND_CODE_PLAYBACK_PLAY + * @see #COMMAND_CODE_PLAYBACK_PAUSE + * @see #COMMAND_CODE_PLAYBACK_STOP + * @see #COMMAND_CODE_PLAYBACK_SKIP_NEXT_ITEM + * @see #COMMAND_CODE_PLAYBACK_SKIP_PREV_ITEM + * @see #COMMAND_CODE_PLAYBACK_PREPARE + * @see #COMMAND_CODE_PLAYBACK_FAST_FORWARD + * @see #COMMAND_CODE_PLAYBACK_REWIND + * @see #COMMAND_CODE_PLAYBACK_SEEK_TO + * @see #COMMAND_CODE_PLAYBACK_SET_CURRENT_PLAYLIST_ITEM + * @see #COMMAND_CODE_PLAYBACK_SET_PLAYLIST_PARAMS + * @see #COMMAND_CODE_PLAYLIST_ADD + * @see #COMMAND_CODE_PLAYLIST_REMOVE + * @see #COMMAND_CODE_PLAYLIST_GET + * @see #COMMAND_CODE_SET_VOLUME */ - // TODO(jaewan): Add more documentations (or make it clear) which commands can be filtered - // with this. public boolean onCommandRequest(@NonNull ControllerInfo controller, @NonNull Command command) { return true; } /** - * Called when a controller set rating on the currently playing contents. + * Called when a controller set rating on the currently playing contents by + * {@link MediaController2#setRating(Rating2)}. * * @param controller controller information * @param rating new rating from the controller @@ -297,7 +455,8 @@ public class MediaSession2 implements AutoCloseable { public void onSetRating(@NonNull ControllerInfo controller, @NonNull Rating2 rating) { } /** - * Called when a controller sent a custom command. + * Called when a controller sent a custom command through + * {@link MediaController2#sendCustomCommand(Command, Bundle, ResultReceiver)}. * * @param controller controller information * @param customCommand custom command. @@ -309,7 +468,48 @@ public class MediaSession2 implements AutoCloseable { @Nullable ResultReceiver cb) { } /** - * Override to handle requests to prepare for playing a specific mediaId. + * Called when a controller requested to play a specific mediaId through + * {@link MediaController2#playFromMediaId(String, Bundle)}. + * + * @param controller controller information + * @param mediaId media id + * @param extras optional extra bundle + * @see #COMMAND_CODE_PLAY_FROM_MEDIA_ID + */ + public void onPlayFromMediaId(@NonNull ControllerInfo controller, + @NonNull String mediaId, @Nullable Bundle extras) { } + + /** + * Called when a controller requested to begin playback from a search query through + * {@link MediaController2#playFromSearch(String, Bundle)} + *

    + * An empty query indicates that the app may play any music. The implementation should + * attempt to make a smart choice about what to play. + * + * @param controller controller information + * @param query query string. Can be empty to indicate any suggested media + * @param extras optional extra bundle + * @see #COMMAND_CODE_PLAY_FROM_SEARCH + */ + public void onPlayFromSearch(@NonNull ControllerInfo controller, + @NonNull String query, @Nullable Bundle extras) { } + + /** + * Called when a controller requested to play a specific media item represented by a URI + * through {@link MediaController2#playFromUri(String, Bundle)} + * + * @param controller controller information + * @param uri uri + * @param extras optional extra bundle + * @see #COMMAND_CODE_PLAY_FROM_URI + */ + public void onPlayFromUri(@NonNull ControllerInfo controller, + @NonNull String uri, @Nullable Bundle extras) { } + + /** + * Called when a controller requested to prepare for playing a specific mediaId through + * {@link MediaController2#prepareFromMediaId(String, Bundle)}. + *

    * During the preparation, a session should not hold audio focus in order to allow other * sessions play seamlessly. The state of playback should be updated to * {@link PlaybackState#STATE_PAUSED} after the preparation is done. @@ -319,28 +519,41 @@ public class MediaSession2 implements AutoCloseable { *

    * Override {@link #onPlayFromMediaId} to handle requests for starting * playback without preparation. + * + * @param controller controller information + * @param mediaId media id to prepare + * @param extras optional extra bundle + * @see #COMMAND_CODE_PREPARE_FROM_MEDIA_ID */ - public void onPlayFromMediaId(@NonNull ControllerInfo controller, + public void onPrepareFromMediaId(@NonNull ControllerInfo controller, @NonNull String mediaId, @Nullable Bundle extras) { } /** - * Override to handle requests to prepare playback from a search query. An empty query - * indicates that the app may prepare any music. The implementation should attempt to make a - * smart choice about what to play. During the preparation, a session should not hold audio - * focus in order to allow other sessions play seamlessly. The state of playback should be - * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. + * Called when a controller requested to prepare playback from a search query through + * {@link MediaController2#prepareFromSearch(String, Bundle)}. *

    - * The playback of the prepared content should start in the later calls of - * {@link MediaSession2#play()}. + * An empty query indicates that the app may prepare any music. The implementation should + * attempt to make a smart choice about what to play. + *

    + * The state of playback should be updated to {@link PlaybackState#STATE_PAUSED} after the + * preparation is done. The playback of the prepared content should start in the later + * calls of {@link MediaSession2#play()}. *

    * Override {@link #onPlayFromSearch} to handle requests for starting playback without * preparation. + * + * @param controller controller information + * @param query query string. Can be empty to indicate any suggested media + * @param extras optional extra bundle + * @see #COMMAND_CODE_PREPARE_FROM_SEARCH */ - public void onPlayFromSearch(@NonNull ControllerInfo controller, + public void onPrepareFromSearch(@NonNull ControllerInfo controller, @NonNull String query, @Nullable Bundle extras) { } /** - * Override to handle requests to prepare a specific media item represented by a URI. + * Called when a controller requested to prepare a specific media item represented by a URI + * through {@link MediaController2#prepareFromUri(Uri, Bundle)}. + *

    * During the preparation, a session should not hold audio focus in order to allow * other sessions play seamlessly. The state of playback should be updated to * {@link PlaybackState#STATE_PAUSED} after the preparation is done. @@ -350,51 +563,14 @@ public class MediaSession2 implements AutoCloseable { *

    * Override {@link #onPlayFromUri} to handle requests for starting playback without * preparation. - */ - public void onPlayFromUri(@NonNull ControllerInfo controller, - @NonNull String uri, @Nullable Bundle extras) { } - - /** - * Override to handle requests to play a specific mediaId. - */ - public void onPrepareFromMediaId(@NonNull ControllerInfo controller, - @NonNull String mediaId, @Nullable Bundle extras) { } - - /** - * Override to handle requests to begin playback from a search query. An - * empty query indicates that the app may play any music. The - * implementation should attempt to make a smart choice about what to - * play. - */ - public void onPrepareFromSearch(@NonNull ControllerInfo controller, - @NonNull String query, @Nullable Bundle extras) { } - - /** - * Override to handle requests to play a specific media item represented by a URI. + * + * @param controller controller information + * @param uri uri + * @param extras optional extra bundle + * @see #COMMAND_CODE_PREPARE_FROM_URI */ public void onPrepareFromUri(@NonNull ControllerInfo controller, @NonNull Uri uri, @Nullable Bundle extras) { } - - /** - * Called when a controller wants to add a {@link MediaItem2} at the specified position - * in the play queue. - *

    - * The item from the media controller wouldn't have valid data source descriptor because - * it would have been anonymized when it's sent to the remote process. - * - * @param item The media item to be inserted. - * @param index The index at which the item is to be inserted. - */ - public void onAddPlaylistItem(@NonNull ControllerInfo controller, - @NonNull MediaItem2 item, int index) { } - - /** - * Called when a controller wants to remove the {@link MediaItem2} - * - * @param item - */ - // Can we do this automatically? - public void onRemovePlaylistItem(@NonNull MediaItem2 item) { } }; /** -- GitLab From d960cc4a511e4f659aaae4aea9bc3b032d28c26b Mon Sep 17 00:00:00 2001 From: David Brazdil Date: Wed, 31 Jan 2018 07:59:17 +0000 Subject: [PATCH 153/416] Show hidden API warning once per process In order to not spam users with warning toasts, add a boolean flag that guards the displaying of a warning message about hidden API usage and is set after the first time a message is shown. Bug: 64382372 Test: manual Change-Id: If7ea995ddf4727a15eccf55dad42ef7775b1fc91 --- core/java/android/app/Activity.java | 5 ++++- core/java/android/app/ActivityThread.java | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 3abb24ce7b2..da324fc1b09 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -7071,7 +7071,10 @@ public class Activity extends ContextThemeWrapper boolean isApiWarningEnabled = SystemProperties.getInt("ro.art.hiddenapi.warning", 0) == 1; if (isAppDebuggable || isApiWarningEnabled) { - if (VMRuntime.getRuntime().hasUsedHiddenApi()) { + if (!mMainThread.mHiddenApiWarningShown && VMRuntime.getRuntime().hasUsedHiddenApi()) { + // Only show the warning once per process. + mMainThread.mHiddenApiWarningShown = true; + String appName = getApplicationInfo().loadLabel(getPackageManager()) .toString(); String warning = "Detected problems with API compatiblity\n" diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2ecd3120345..565eaeb632b 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -266,6 +266,7 @@ public final class ActivityThread { boolean mJitEnabled = false; boolean mSomeActivitiesChanged = false; boolean mUpdatingSystemConfig = false; + /* package */ boolean mHiddenApiWarningShown = false; // These can be accessed by multiple threads; mResourcesManager is the lock. // XXX For now we keep around information about all packages we have -- GitLab From 196e1de5af86bb97e573b0b8ebe30d48390b683b Mon Sep 17 00:00:00 2001 From: Hyundo Moon Date: Wed, 31 Jan 2018 14:52:31 +0900 Subject: [PATCH 154/416] MediaSession2: Create VolumeProvider2 Bug: 72721358 Test: Builds successfully Change-Id: Ia7eca3f92deff7ba3fd5633d94748b0b80e86104 --- media/java/android/media/VolumeProvider2.java | 151 ++++++++++++++++++ .../android/media/update/StaticProvider.java | 7 +- .../media/update/VolumeProvider2Provider.java | 27 ++++ 3 files changed, 182 insertions(+), 3 deletions(-) create mode 100644 media/java/android/media/VolumeProvider2.java create mode 100644 media/java/android/media/update/VolumeProvider2Provider.java diff --git a/media/java/android/media/VolumeProvider2.java b/media/java/android/media/VolumeProvider2.java new file mode 100644 index 00000000000..00746e25c24 --- /dev/null +++ b/media/java/android/media/VolumeProvider2.java @@ -0,0 +1,151 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.Context; +import android.media.update.ApiLoader; +import android.media.update.VolumeProvider2Provider; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Handles requests to adjust or set the volume on a session. This is also used + * to push volume updates back to the session. The provider must call + * {@link #setCurrentVolume(int)} each time the volume being provided changes. + *

    + * You can set a volume provider on a session by calling + * {@link MediaSession2#setPlayer(MediaPlayerInterface, VolumeProvider)}. + * + * @hide + */ +public abstract class VolumeProvider2 { + + /** + * @hide + */ + @IntDef({VOLUME_CONTROL_FIXED, VOLUME_CONTROL_RELATIVE, VOLUME_CONTROL_ABSOLUTE}) + @Retention(RetentionPolicy.SOURCE) + public @interface ControlType {} + + /** + * The volume is fixed and can not be modified. Requests to change volume + * should be ignored. + */ + public static final int VOLUME_CONTROL_FIXED = 0; + + /** + * The volume control uses relative adjustment via + * {@link #onAdjustVolume(int)}. Attempts to set the volume to a specific + * value should be ignored. + */ + public static final int VOLUME_CONTROL_RELATIVE = 1; + + /** + * The volume control uses an absolute value. It may be adjusted using + * {@link #onAdjustVolume(int)} or set directly using + * {@link #onSetVolumeTo(int)}. + */ + public static final int VOLUME_CONTROL_ABSOLUTE = 2; + + private final VolumeProvider2Provider mProvider; + + /** + * Create a new volume provider for handling volume events. You must specify + * the type of volume control, the maximum volume that can be used, and the + * current volume on the output. + * + * @param controlType The method for controlling volume that is used by this provider. + * @param maxVolume The maximum allowed volume. + * @param currentVolume The current volume on the output. + */ + public VolumeProvider2(@NonNull Context context, @ControlType int controlType, + int maxVolume, int currentVolume) { + mProvider = ApiLoader.getProvider(context).createVolumeProvider2( + context, this, controlType, maxVolume, currentVolume); + } + + /** + * @hide + */ + @SystemApi + public VolumeProvider2Provider getProvider() { + return mProvider; + } + + /** + * Get the volume control type that this volume provider uses. + * + * @return The volume control type for this volume provider + */ + @ControlType + public final int getControlType() { + return mProvider.getControlType_impl(); + } + + /** + * Get the maximum volume this provider allows. + * + * @return The max allowed volume. + */ + public final int getMaxVolume() { + return mProvider.getMaxVolume_impl(); + } + + /** + * Gets the current volume. This will be the last value set by + * {@link #setCurrentVolume(int)}. + * + * @return The current volume. + */ + public final int getCurrentVolume() { + return mProvider.getCurrentVolume_impl(); + } + + /** + * Notify the system that the current volume has been changed. This must be + * called every time the volume changes to ensure it is displayed properly. + * + * @param currentVolume The current volume on the output. + */ + public final void setCurrentVolume(int currentVolume) { + mProvider.setCurrentVolume_impl(currentVolume); + } + + /** + * Override to handle requests to set the volume of the current output. + * After the volume has been modified {@link #setCurrentVolume} must be + * called to notify the system. + * + * @param volume The volume to set the output to. + */ + public void onSetVolumeTo(int volume) { } + + /** + * Override to handle requests to adjust the volume of the current output. + * Direction will be one of {@link AudioManager#ADJUST_LOWER}, + * {@link AudioManager#ADJUST_RAISE}, {@link AudioManager#ADJUST_SAME}. + * After the volume has been modified {@link #setCurrentVolume} must be + * called to notify the system. + * + * @param direction The direction to change the volume in. + */ + public void onAdjustVolume(int direction) { } +} diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 5dbb1244d51..6dec363c906 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -17,7 +17,6 @@ package android.media.update; import android.annotation.Nullable; -import android.app.PendingIntent; import android.content.Context; import android.media.DataSourceDesc; import android.media.MediaBrowser2; @@ -38,8 +37,7 @@ import android.media.MediaSessionService2; import android.media.Rating2; import android.media.SessionPlayer2; import android.media.SessionToken2; -import android.media.VolumeProvider; -import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider; +import android.media.VolumeProvider2; import android.media.update.MediaSession2Provider.BuilderBaseProvider; import android.media.update.MediaSession2Provider.CommandGroupProvider; import android.media.update.MediaSession2Provider.CommandProvider; @@ -107,6 +105,9 @@ public interface StaticProvider { String mediaId, DataSourceDesc dsd, MediaMetadata2 metadata, int flags); MediaItem2 fromBundle_MediaItem2(Context context, Bundle bundle); + VolumeProvider2Provider createVolumeProvider2(Context context, VolumeProvider2 instance, + int controlType, int maxVolume, int currentVolume); + MediaMetadata2 fromBundle_MediaMetadata2(Context context, Bundle bundle); MediaMetadata2Provider.BuilderProvider createMediaMetadata2Builder( Context context, MediaMetadata2.Builder builder); diff --git a/media/java/android/media/update/VolumeProvider2Provider.java b/media/java/android/media/update/VolumeProvider2Provider.java new file mode 100644 index 00000000000..5657af60eb6 --- /dev/null +++ b/media/java/android/media/update/VolumeProvider2Provider.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +/** + * @hide + */ +// TODO(jaewan): @SystemApi +public interface VolumeProvider2Provider { + int getControlType_impl(); + int getMaxVolume_impl(); + int getCurrentVolume_impl(); + void setCurrentVolume_impl(int currentVolume); +} -- GitLab From f029ae95112c5e64c60b255eb60f93895fcef0d1 Mon Sep 17 00:00:00 2001 From: Jaewan Kim Date: Wed, 31 Jan 2018 14:37:47 +0900 Subject: [PATCH 155/416] MediaSession2: Move PlaybackState2 to updatable Test: Run all MediaComponents test once Bug: 72670371 Change-Id: Ic7953bf4bb7841238dec7082d8e9d9b412775e6d --- media/java/android/media/PlaybackState2.java | 169 ++++++++---------- .../media/update/PlaybackState2Provider.java | 43 +++++ .../android/media/update/StaticProvider.java | 6 + 3 files changed, 121 insertions(+), 97 deletions(-) create mode 100644 media/java/android/media/update/PlaybackState2Provider.java diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java index da776ebefa3..627974a8713 100644 --- a/media/java/android/media/PlaybackState2.java +++ b/media/java/android/media/PlaybackState2.java @@ -17,6 +17,11 @@ package android.media; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.update.ApiLoader; +import android.media.update.PlaybackState2Provider; import android.os.Bundle; import java.lang.annotation.Retention; @@ -29,14 +34,51 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public final class PlaybackState2 { - private static final String TAG = "PlaybackState2"; - + // Similar to the PlaybackState2 with following changes + // - Not implement Parcelable and added from/toBundle() + // - Removed playback state that doesn't match with the MediaPlayer2 + // Full list should be finalized when the MediaPlayer2 has getter for the playback state. + // Here's table for the MP2 state and PlaybackState2.State. + // +----------------------------------------+----------------------------------------+ + // | MediaPlayer2 state | Matching PlaybackState2.State | + // | (Names are from MP2' Javadoc) | | + // +----------------------------------------+----------------------------------------+ + // | Idle: Just finished creating MP2 | STATE_NONE | + // | or reset() is called | | + // +----------------------------------------+----------------------------------------+ + // | Initialized: setDataSource/Playlist | N/A (Session/Controller don't | + // | | differentiate with Prepared) | + // +----------------------------------------+----------------------------------------+ + // | Prepared: Prepared after initialized | STATE_PAUSED | + // +----------------------------------------+----------------------------------------+ + // | Started: Started playback | STATE_PLAYING | + // +----------------------------------------+----------------------------------------+ + // | Paused: Paused playback | STATE_PAUSED | + // +----------------------------------------+----------------------------------------+ + // | PlaybackCompleted: Playback is done | STATE_PAUSED | + // +----------------------------------------+----------------------------------------+ + // | Stopped: MP2.stop() is called. | STATE_STOPPED | + // | prepare() is needed to play again | | + // | (Seemingly the same as initialized | | + // | because cannot set data source | | + // | after this) | | + // +----------------------------------------+----------------------------------------+ + // | Error: an API is called in a state | STATE_ERROR | + // | that the API isn't supported | | + // +----------------------------------------+----------------------------------------+ + // | End: MP2.close() is called to release | N/A (MediaSession will be gone) | + // | MP2. Cannot be reused anymore | | + // +----------------------------------------+----------------------------------------+ + // | Started, but | STATE_BUFFERING | + // | EventCallback.onBufferingUpdate() | | + // +----------------------------------------+----------------------------------------+ + // - Removed actions and custom actions. + // - Repeat mode / shuffle mode is now in the PlaylistParams // TODO(jaewan): Replace states from MediaPlayer2 /** * @hide */ - @IntDef({STATE_NONE, STATE_STOPPED, STATE_PREPARED, STATE_PAUSED, STATE_PLAYING, - STATE_FINISH, STATE_BUFFERING, STATE_ERROR}) + @IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_BUFFERING, STATE_ERROR}) @Retention(RetentionPolicy.SOURCE) public @interface State {} @@ -51,89 +93,47 @@ public final class PlaybackState2 { */ public final static int STATE_STOPPED = 1; - /** - * State indicating this item is currently prepared - */ - public final static int STATE_PREPARED = 2; - /** * State indicating this item is currently paused. */ - public final static int STATE_PAUSED = 3; + public final static int STATE_PAUSED = 2; /** * State indicating this item is currently playing. */ - public final static int STATE_PLAYING = 4; - - /** - * State indicating the playback reaches the end of the item. - */ - public final static int STATE_FINISH = 5; + public final static int STATE_PLAYING = 3; /** * State indicating this item is currently buffering and will begin playing * when enough data has buffered. */ - public final static int STATE_BUFFERING = 6; + public final static int STATE_BUFFERING = 4; /** * State indicating this item is currently in an error state. The error * message should also be set when entering this state. */ - public final static int STATE_ERROR = 7; + public final static int STATE_ERROR = 5; /** * Use this value for the position to indicate the position is not known. */ public final static long PLAYBACK_POSITION_UNKNOWN = -1; - /** - * Keys used for converting a PlaybackState2 to a bundle object and vice versa. - */ - private static final String KEY_STATE = "android.media.playbackstate2.state"; - private static final String KEY_POSITION = "android.media.playbackstate2.position"; - private static final String KEY_BUFFERED_POSITION = - "android.media.playbackstate2.buffered_position"; - private static final String KEY_SPEED = "android.media.playbackstate2.speed"; - private static final String KEY_ERROR_MESSAGE = "android.media.playbackstate2.error_message"; - private static final String KEY_UPDATE_TIME = "android.media.playbackstate2.update_time"; - private static final String KEY_ACTIVE_ITEM_ID = "android.media.playbackstate2.active_item_id"; - - private final int mState; - private final long mPosition; - private final long mUpdateTime; - private final float mSpeed; - private final long mBufferedPosition; - private final long mActiveItemId; - private final CharSequence mErrorMessage; + private final PlaybackState2Provider mProvider; // TODO(jaewan): Better error handling? // E.g. media item at #2 has issue, but continue playing #3 // login error. fire intent xxx to login - public PlaybackState2(int state, long position, long updateTime, float speed, - long bufferedPosition, long activeItemId, CharSequence error) { - mState = state; - mPosition = position; - mSpeed = speed; - mUpdateTime = updateTime; - mBufferedPosition = bufferedPosition; - mActiveItemId = activeItemId; - mErrorMessage = error; + public PlaybackState2(@NonNull Context context, int state, long position, long updateTime, + float speed, long bufferedPosition, long activeItemId, CharSequence error) { + mProvider = ApiLoader.getProvider(context).createPlaybackState2(context, this, state, + position, updateTime, speed, bufferedPosition, activeItemId, error); } @Override public String toString() { - StringBuilder bob = new StringBuilder("PlaybackState {"); - bob.append("state=").append(mState); - bob.append(", position=").append(mPosition); - bob.append(", buffered position=").append(mBufferedPosition); - bob.append(", speed=").append(mSpeed); - bob.append(", updated=").append(mUpdateTime); - bob.append(", active item id=").append(mActiveItemId); - bob.append(", error=").append(mErrorMessage); - bob.append("}"); - return bob.toString(); + return mProvider.toString_impl(); } /** @@ -141,22 +141,24 @@ public final class PlaybackState2 { *

      *
    • {@link PlaybackState2#STATE_NONE}
    • *
    • {@link PlaybackState2#STATE_STOPPED}
    • - *
    • {@link PlaybackState2#STATE_PLAYING}
    • + *
    • {@link PlaybackState2#STATE_PREPARED}
    • *
    • {@link PlaybackState2#STATE_PAUSED}
    • + *
    • {@link PlaybackState2#STATE_PLAYING}
    • + *
    • {@link PlaybackState2#STATE_FINISH}
    • *
    • {@link PlaybackState2#STATE_BUFFERING}
    • *
    • {@link PlaybackState2#STATE_ERROR}
    • *
    */ @State public int getState() { - return mState; + return mProvider.getState_impl(); } /** * Get the current playback position in ms. */ public long getPosition() { - return mPosition; + return mProvider.getPosition_impl(); } /** @@ -165,7 +167,7 @@ public final class PlaybackState2 { * content. */ public long getBufferedPosition() { - return mBufferedPosition; + return mProvider.getBufferedPosition_impl(); } /** @@ -176,7 +178,7 @@ public final class PlaybackState2 { * @return The current speed of playback. */ public float getPlaybackSpeed() { - return mSpeed; + return mProvider.getPlaybackSpeed_impl(); } /** @@ -184,7 +186,7 @@ public final class PlaybackState2 { * {@link PlaybackState2#STATE_ERROR}. */ public CharSequence getErrorMessage() { - return mErrorMessage; + return mProvider.getErrorMessage_impl(); } /** @@ -194,7 +196,7 @@ public final class PlaybackState2 { * @return The last time the position was updated. */ public long getLastPositionUpdateTime() { - return mUpdateTime; + return mProvider.getLastPositionUpdateTime_impl(); } /** @@ -203,53 +205,26 @@ public final class PlaybackState2 { * @return The id of the currently active item in the queue */ public long getCurrentPlaylistItemIndex() { - return mActiveItemId; + return mProvider.getCurrentPlaylistItemIndex_impl(); } /** * Returns this object as a bundle to share between processes. */ - public Bundle toBundle() { - Bundle bundle = new Bundle(); - bundle.putInt(KEY_STATE, mState); - bundle.putLong(KEY_POSITION, mPosition); - bundle.putLong(KEY_UPDATE_TIME, mUpdateTime); - bundle.putFloat(KEY_SPEED, mSpeed); - bundle.putLong(KEY_BUFFERED_POSITION, mBufferedPosition); - bundle.putLong(KEY_ACTIVE_ITEM_ID, mActiveItemId); - bundle.putCharSequence(KEY_ERROR_MESSAGE, mErrorMessage); - return bundle; + public @NonNull Bundle toBundle() { + return mProvider.toBundle_impl(); } /** * Creates an instance from a bundle which is previously created by {@link #toBundle()}. * + * @param context context * @param bundle A bundle created by {@link #toBundle()}. * @return A new {@link PlaybackState2} instance. Returns {@code null} if the given * {@param bundle} is null, or if the {@param bundle} has no playback state parameters. */ - public static PlaybackState2 fromBundle(Bundle bundle) { - if (bundle == null) { - return null; - } - - if (!bundle.containsKey(KEY_STATE) - || !bundle.containsKey(KEY_POSITION) - || !bundle.containsKey(KEY_UPDATE_TIME) - || !bundle.containsKey(KEY_SPEED) - || !bundle.containsKey(KEY_BUFFERED_POSITION) - || !bundle.containsKey(KEY_ACTIVE_ITEM_ID) - || !bundle.containsKey(KEY_ERROR_MESSAGE)) { - return null; - } - - return new PlaybackState2( - bundle.getInt(KEY_STATE), - bundle.getLong(KEY_POSITION), - bundle.getLong(KEY_UPDATE_TIME), - bundle.getFloat(KEY_SPEED), - bundle.getLong(KEY_BUFFERED_POSITION), - bundle.getLong(KEY_ACTIVE_ITEM_ID), - bundle.getCharSequence(KEY_ERROR_MESSAGE)); + public @Nullable static PlaybackState2 fromBundle(@NonNull Context context, + @Nullable Bundle bundle) { + return ApiLoader.getProvider(context).fromBundle_PlaybackState2(context, bundle); } } \ No newline at end of file diff --git a/media/java/android/media/update/PlaybackState2Provider.java b/media/java/android/media/update/PlaybackState2Provider.java new file mode 100644 index 00000000000..2875e98bb8a --- /dev/null +++ b/media/java/android/media/update/PlaybackState2Provider.java @@ -0,0 +1,43 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +import android.os.Bundle; + +/** + * @hide + */ +// TODO(jaewan): @SystemApi +public interface PlaybackState2Provider { + String toString_impl(); + + int getState_impl(); + + long getPosition_impl(); + + long getBufferedPosition_impl(); + + float getPlaybackSpeed_impl(); + + CharSequence getErrorMessage_impl(); + + long getLastPositionUpdateTime_impl(); + + long getCurrentPlaylistItemIndex_impl(); + + Bundle toBundle_impl(); +} diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 9282494b32d..447f6f774df 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -34,6 +34,7 @@ import android.media.MediaSession2; import android.media.MediaSession2.PlaylistParams; import android.media.MediaSession2.SessionCallback; import android.media.MediaSessionService2; +import android.media.PlaybackState2; import android.media.Rating2; import android.media.SessionPlayer2; import android.media.SessionToken2; @@ -117,4 +118,9 @@ public interface StaticProvider { Rating2 newThumbRating_Rating2(Context context, boolean thumbIsUp); Rating2 newStarRating_Rating2(Context context, int starRatingStyle, float starRating); Rating2 newPercentageRating_Rating2(Context context, float percent); + + PlaybackState2Provider createPlaybackState2(Context context, PlaybackState2 instance, int state, + long position, long updateTime, float speed, long bufferedPosition, long activeItemId, + CharSequence error); + PlaybackState2 fromBundle_PlaybackState2(Context context, Bundle bundle); } -- GitLab From fe20cdd9101c68031a7174c597a43030e167e3b4 Mon Sep 17 00:00:00 2001 From: Abodunrinwa Toki Date: Tue, 12 Dec 2017 02:31:25 +0000 Subject: [PATCH 156/416] Smart Linkify API Uses the TextClassifier to generate links on a background thread. The links are applied on the calling thread. Test: see topic Bug: 67629726 Change-Id: I0f1940a2ffbf19f4436c0a20b0c62e6bbc03cd7a --- api/current.txt | 27 +- core/java/android/text/util/Linkify.java | 200 ++++++++++++++ .../textclassifier/TextClassifierImpl.java | 3 +- .../view/textclassifier/TextLinks.java | 260 +++++++++++++++--- .../view/textclassifier/TextLinksTest.java | 4 +- .../android/widget/TextViewActivityTest.java | 4 +- 6 files changed, 447 insertions(+), 51 deletions(-) diff --git a/api/current.txt b/api/current.txt index f5da9b05e6b..f4e1b6ceadd 100644 --- a/api/current.txt +++ b/api/current.txt @@ -44343,6 +44343,11 @@ package android.text.util { method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String); method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter); method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, java.lang.String[], android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter); + method public static java.util.concurrent.Future addLinksAsync(android.widget.TextView, android.view.textclassifier.TextLinks.Options); + method public static java.util.concurrent.Future addLinksAsync(android.widget.TextView, android.view.textclassifier.TextLinks.Options, java.util.concurrent.Executor, java.util.function.Consumer); + method public static java.util.concurrent.Future addLinksAsync(android.text.Spannable, android.view.textclassifier.TextClassifier, android.view.textclassifier.TextLinks.Options); + method public static java.util.concurrent.Future addLinksAsync(android.text.Spannable, android.view.textclassifier.TextClassifier, int); + method public static java.util.concurrent.Future addLinksAsync(android.text.Spannable, android.view.textclassifier.TextClassifier, android.view.textclassifier.TextLinks.Options, java.util.concurrent.Executor, java.util.function.Consumer); field public static final int ALL = 15; // 0xf field public static final int EMAIL_ADDRESSES = 2; // 0x2 field public static final int MAP_ADDRESSES = 8; // 0x8 @@ -50125,32 +50130,42 @@ package android.view.textclassifier { } public final class TextLinks implements android.os.Parcelable { - method public boolean apply(android.text.SpannableString, java.util.function.Function); method public int describeContents(); method public java.util.Collection getLinks(); method public void writeToParcel(android.os.Parcel, int); + field public static final int APPLY_STRATEGY_IGNORE = 0; // 0x0 + field public static final int APPLY_STRATEGY_REPLACE = 1; // 0x1 field public static final android.os.Parcelable.Creator CREATOR; + field public static final int STATUS_DIFFERENT_TEXT = 3; // 0x3 + field public static final int STATUS_LINKS_APPLIED = 0; // 0x0 + field public static final int STATUS_NO_LINKS_APPLIED = 2; // 0x2 + field public static final int STATUS_NO_LINKS_FOUND = 1; // 0x1 } public static final class TextLinks.Builder { ctor public TextLinks.Builder(java.lang.String); - method public android.view.textclassifier.TextLinks.Builder addLink(android.view.textclassifier.TextLinks.TextLink); + method public android.view.textclassifier.TextLinks.Builder addLink(int, int, java.util.Map); method public android.view.textclassifier.TextLinks build(); + method public android.view.textclassifier.TextLinks.Builder clearTextLinks(); } public static final class TextLinks.Options implements android.os.Parcelable { ctor public TextLinks.Options(); method public int describeContents(); + method public static android.view.textclassifier.TextLinks.Options fromLinkMask(int); + method public int getApplyStrategy(); method public android.os.LocaleList getDefaultLocales(); method public android.view.textclassifier.TextClassifier.EntityConfig getEntityConfig(); + method public java.util.function.Function getSpanFactory(); + method public android.view.textclassifier.TextLinks.Options setApplyStrategy(int); method public android.view.textclassifier.TextLinks.Options setDefaultLocales(android.os.LocaleList); method public android.view.textclassifier.TextLinks.Options setEntityConfig(android.view.textclassifier.TextClassifier.EntityConfig); + method public android.view.textclassifier.TextLinks.Options setSpanFactory(java.util.function.Function); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } public static final class TextLinks.TextLink implements android.os.Parcelable { - ctor public TextLinks.TextLink(java.lang.String, int, int, java.util.Map); method public int describeContents(); method public float getConfidenceScore(java.lang.String); method public int getEnd(); @@ -50161,6 +50176,12 @@ package android.view.textclassifier { field public static final android.os.Parcelable.Creator CREATOR; } + public static class TextLinks.TextLinkSpan extends android.text.style.ClickableSpan { + ctor public TextLinks.TextLinkSpan(android.view.textclassifier.TextLinks.TextLink); + method public final android.view.textclassifier.TextLinks.TextLink getTextLink(); + method public void onClick(android.view.View); + } + public final class TextSelection implements android.os.Parcelable { method public int describeContents(); method public float getConfidenceScore(java.lang.String); diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java index 768aee91e5b..d973d4ac076 100644 --- a/core/java/android/text/util/Linkify.java +++ b/core/java/android/text/util/Linkify.java @@ -19,6 +19,7 @@ package android.text.util; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UiThread; import android.content.Context; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; @@ -29,12 +30,16 @@ import android.text.method.LinkMovementMethod; import android.text.method.MovementMethod; import android.text.style.URLSpan; import android.util.Patterns; +import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextLinks; +import android.view.textclassifier.TextLinks.TextLinkSpan; import android.webkit.WebView; import android.widget.TextView; import com.android.i18n.phonenumbers.PhoneNumberMatch; import com.android.i18n.phonenumbers.PhoneNumberUtil; import com.android.i18n.phonenumbers.PhoneNumberUtil.Leniency; +import com.android.internal.util.Preconditions; import libcore.util.EmptyArray; @@ -46,6 +51,12 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Locale; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -479,6 +490,195 @@ public class Linkify { return hasMatches; } + /** + * Scans the text of the provided TextView and turns all occurrences of the entity types + * specified by {@code options} into clickable links. If links are found, this method + * removes any pre-existing {@link TextLinkSpan} attached to the text (to avoid + * problems if you call it repeatedly on the same text) and sets the movement method for the + * TextView to LinkMovementMethod. + * + *

    Note: This method returns immediately but generates the links with + * the specified classifier on a background thread. The generated links are applied on the + * calling thread. + * + * @param textView TextView whose text is to be marked-up with links + * @param options optional parameters to specify how to generate the links + * + * @return a future that may be used to interrupt or query the background task + */ + @UiThread + public static Future addLinksAsync( + @NonNull TextView textView, + @Nullable TextLinks.Options options) { + return addLinksAsync(textView, options, null /* executor */, null /* callback */); + } + + /** + * Scans the text of the provided TextView and turns all occurrences of the entity types + * specified by {@code options} into clickable links. If links are found, this method + * removes any pre-existing {@link TextLinkSpan} attached to the text (to avoid + * problems if you call it repeatedly on the same text) and sets the movement method for the + * TextView to LinkMovementMethod. + * + *

    Note: This method returns immediately but generates the links with + * the specified classifier on a background thread. The generated links are applied on the + * calling thread. + * + * @param textView TextView whose text is to be marked-up with links + * @param options optional parameters to specify how to generate the links + * @param executor Executor that runs the background task + * @param callback Callback that receives the final status of the background task execution + * + * @return a future that may be used to interrupt or query the background task + */ + @UiThread + public static Future addLinksAsync( + @NonNull TextView textView, + @Nullable TextLinks.Options options, + @Nullable Executor executor, + @Nullable Consumer callback) { + Preconditions.checkNotNull(textView); + final CharSequence text = textView.getText(); + final Spannable spannable = (text instanceof Spannable) + ? (Spannable) text : SpannableString.valueOf(text); + final Runnable modifyTextView = () -> { + addLinkMovementMethod(textView); + if (spannable != text) { + textView.setText(spannable); + } + }; + return addLinksAsync(spannable, textView.getTextClassifier(), + options, executor, callback, modifyTextView); + } + + /** + * Scans the text of the provided TextView and turns all occurrences of the entity types + * specified by {@code options} into clickable links. If links are found, this method + * removes any pre-existing {@link TextLinkSpan} attached to the text to avoid + * problems if you call it repeatedly on the same text. + * + *

    Note: This method returns immediately but generates the links with + * the specified classifier on a background thread. The generated links are applied on the + * calling thread. + * + *

    Note: If the text is currently attached to a TextView, this method + * should be called on the UI thread. + * + * @param text Spannable whose text is to be marked-up with links + * @param classifier the TextClassifier to use to generate the links + * @param options optional parameters to specify how to generate the links + * + * @return a future that may be used to interrupt or query the background task + */ + public static Future addLinksAsync( + @NonNull Spannable text, + @NonNull TextClassifier classifier, + @Nullable TextLinks.Options options) { + return addLinksAsync(text, classifier, options, null /* executor */, null /* callback */); + } + + /** + * Scans the text of the provided TextView and turns all occurrences of the entity types + * specified by the link {@code mask} into clickable links. If links are found, this method + * removes any pre-existing {@link TextLinkSpan} attached to the text to avoid + * problems if you call it repeatedly on the same text. + * + *

    Note: This method returns immediately but generates the links with + * the specified classifier on a background thread. The generated links are applied on the + * calling thread. + * + *

    Note: If the text is currently attached to a TextView, this method + * should be called on the UI thread. + * + * @param text Spannable whose text is to be marked-up with links + * @param classifier the TextClassifier to use to generate the links + * @param mask mask to define which kinds of links will be generated + * + * @return a future that may be used to interrupt or query the background task + */ + public static Future addLinksAsync( + @NonNull Spannable text, + @NonNull TextClassifier classifier, + @LinkifyMask int mask) { + return addLinksAsync(text, classifier, TextLinks.Options.fromLinkMask(mask), + null /* executor */, null /* callback */); + } + + /** + * Scans the text of the provided TextView and turns all occurrences of the entity types + * specified by {@code options} into clickable links. If links are found, this method + * removes any pre-existing {@link TextLinkSpan} attached to the text to avoid + * problems if you call it repeatedly on the same text. + * + *

    Note: This method returns immediately but generates the links with + * the specified classifier on a background thread. The generated links are applied on the + * calling thread. + * + *

    Note: If the text is currently attached to a TextView, this method + * should be called on the UI thread. + * + * @param text Spannable whose text is to be marked-up with links + * @param classifier the TextClassifier to use to generate the links + * @param options optional parameters to specify how to generate the links + * @param executor Executor that runs the background task + * @param callback Callback that receives the final status of the background task execution + * + * @return a future that may be used to interrupt or query the background task + */ + public static Future addLinksAsync( + @NonNull Spannable text, + @NonNull TextClassifier classifier, + @Nullable TextLinks.Options options, + @Nullable Executor executor, + @Nullable Consumer callback) { + return addLinksAsync(text, classifier, options, executor, callback, + null /* modifyTextView */); + } + + private static Future addLinksAsync( + @NonNull Spannable text, + @NonNull TextClassifier classifier, + @Nullable TextLinks.Options options, + @Nullable Executor executor, + @Nullable Consumer callback, + @Nullable Runnable modifyTextView) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(classifier); + final Supplier supplier = () -> classifier.generateLinks(text, options); + final Consumer consumer = links -> { + if (links.getLinks().isEmpty()) { + if (callback != null) { + callback.accept(TextLinks.STATUS_NO_LINKS_FOUND); + } + return; + } + + final TextLinkSpan[] old = text.getSpans(0, text.length(), TextLinkSpan.class); + for (int i = old.length - 1; i >= 0; i--) { + text.removeSpan(old[i]); + } + + final Function spanFactory = (options == null) + ? null : options.getSpanFactory(); + final @TextLinks.ApplyStrategy int applyStrategy = (options == null) + ? TextLinks.APPLY_STRATEGY_IGNORE : options.getApplyStrategy(); + final @TextLinks.Status int result = links.apply(text, applyStrategy, spanFactory); + if (result == TextLinks.STATUS_LINKS_APPLIED) { + if (modifyTextView != null) { + modifyTextView.run(); + } + } + if (callback != null) { + callback.accept(result); + } + }; + if (executor == null) { + return CompletableFuture.supplyAsync(supplier).thenAccept(consumer); + } else { + return CompletableFuture.supplyAsync(supplier, executor).thenAccept(consumer); + } + } + private static final void applyLink(String url, int start, int end, Spannable text) { URLSpan span = new URLSpan(url); diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 4da5bbf69a7..861345eb23f 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -221,8 +221,7 @@ public final class TextClassifierImpl implements TextClassifier { for (int i = 0; i < results.length; i++) { entityScores.put(results[i].mCollection, results[i].mScore); } - builder.addLink(new TextLinks.TextLink( - textString, span.getStartIndex(), span.getEndIndex(), entityScores)); + builder.addLink(span.getStartIndex(), span.getEndIndex(), entityScores); } } catch (Throwable t) { // Avoid throwing from this method. Log the error. diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index ba854e04046..670efdd286e 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -17,18 +17,24 @@ package android.view.textclassifier; import android.annotation.FloatRange; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; -import android.text.SpannableString; +import android.widget.TextView; +import android.text.Spannable; import android.text.style.ClickableSpan; +import android.text.util.Linkify; +import android.text.util.Linkify.LinkifyMask; import android.view.View; -import android.widget.TextView; +import android.view.textclassifier.TextClassifier.EntityType; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -41,12 +47,51 @@ import java.util.function.Function; * address, url, etc) they may be. */ public final class TextLinks implements Parcelable { + + /** + * Return status of an attempt to apply TextLinks to text. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATUS_LINKS_APPLIED, STATUS_NO_LINKS_FOUND, STATUS_NO_LINKS_APPLIED, + STATUS_DIFFERENT_TEXT}) + public @interface Status {} + + /** Links were successfully applied to the text. */ + public static final int STATUS_LINKS_APPLIED = 0; + + /** No links exist to apply to text. Links count is zero. */ + public static final int STATUS_NO_LINKS_FOUND = 1; + + /** No links applied to text. The links were filtered out. */ + public static final int STATUS_NO_LINKS_APPLIED = 2; + + /** The specified text does not match the text used to generate the links. */ + public static final int STATUS_DIFFERENT_TEXT = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({APPLY_STRATEGY_IGNORE, APPLY_STRATEGY_REPLACE}) + public @interface ApplyStrategy {} + + /** + * Do not replace {@link ClickableSpan}s that exist where the {@link TextLinkSpan} needs to + * be applied to. Do not apply the TextLinkSpan. + */ + public static final int APPLY_STRATEGY_IGNORE = 0; + + /** + * Replace any {@link ClickableSpan}s that exist where the {@link TextLinkSpan} needs to be + * applied to. + */ + public static final int APPLY_STRATEGY_REPLACE = 1; + private final String mFullText; private final List mLinks; - private TextLinks(String fullText, Collection links) { + private TextLinks(String fullText, ArrayList links) { mFullText = fullText; - mLinks = Collections.unmodifiableList(new ArrayList<>(links)); + mLinks = Collections.unmodifiableList(links); } /** @@ -60,29 +105,57 @@ public final class TextLinks implements Parcelable { * Annotates the given text with the generated links. It will fail if the provided text doesn't * match the original text used to crete the TextLinks. * - * @param text the text to apply the links to. Must match the original text. - * @param spanFactory a factory to generate spans from TextLinks. Will use a default if null. + * @param text the text to apply the links to. Must match the original text + * @param applyStrategy strategy for resolving link conflicts + * @param spanFactory a factory to generate spans from TextLinks. Will use a default if null + * + * @return a status code indicating whether or not the links were successfully applied * - * @return Success or failure. + * @hide */ - public boolean apply( - @NonNull SpannableString text, - @Nullable Function spanFactory) { + @Status + public int apply( + @NonNull Spannable text, + @ApplyStrategy int applyStrategy, + @Nullable Function spanFactory) { Preconditions.checkNotNull(text); + checkValidApplyStrategy(applyStrategy); if (!mFullText.equals(text.toString())) { - return false; + return STATUS_DIFFERENT_TEXT; + } + if (mLinks.isEmpty()) { + return STATUS_NO_LINKS_FOUND; } if (spanFactory == null) { spanFactory = DEFAULT_SPAN_FACTORY; } + int applyCount = 0; for (TextLink link : mLinks) { - final ClickableSpan span = spanFactory.apply(link); + final TextLinkSpan span = spanFactory.apply(link); if (span != null) { - text.setSpan(span, link.getStart(), link.getEnd(), 0); + final ClickableSpan[] existingSpans = text.getSpans( + link.getStart(), link.getEnd(), ClickableSpan.class); + if (existingSpans.length > 0) { + if (applyStrategy == APPLY_STRATEGY_REPLACE) { + for (ClickableSpan existingSpan : existingSpans) { + text.removeSpan(existingSpan); + } + text.setSpan(span, link.getStart(), link.getEnd(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + applyCount++; + } + } else { + text.setSpan(span, link.getStart(), link.getEnd(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + applyCount++; + } } } - return true; + if (applyCount == 0) { + return STATUS_NO_LINKS_APPLIED; + } + return STATUS_LINKS_APPLIED; } @Override @@ -119,7 +192,6 @@ public final class TextLinks implements Parcelable { */ public static final class TextLink implements Parcelable { private final EntityConfidence mEntityScores; - private final String mOriginalText; private final int mStart; private final int mEnd; @@ -128,12 +200,10 @@ public final class TextLinks implements Parcelable { * * @throws IllegalArgumentException if entityScores is null or empty. */ - public TextLink(String originalText, int start, int end, Map entityScores) { - Preconditions.checkNotNull(originalText); + TextLink(int start, int end, Map entityScores) { Preconditions.checkNotNull(entityScores); Preconditions.checkArgument(!entityScores.isEmpty()); Preconditions.checkArgument(start <= end); - mOriginalText = originalText; mStart = start; mEnd = end; mEntityScores = new EntityConfidence(entityScores); @@ -171,7 +241,7 @@ public final class TextLinks implements Parcelable { * * @return the entity type at the provided index. */ - @NonNull public @TextClassifier.EntityType String getEntity(int index) { + @NonNull public @EntityType String getEntity(int index) { return mEntityScores.getEntities().get(index); } @@ -181,7 +251,7 @@ public final class TextLinks implements Parcelable { * @param entityType the entity type. */ public @FloatRange(from = 0.0, to = 1.0) float getConfidenceScore( - @TextClassifier.EntityType String entityType) { + @EntityType String entityType) { return mEntityScores.getConfidenceScore(entityType); } @@ -193,7 +263,6 @@ public final class TextLinks implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { mEntityScores.writeToParcel(dest, flags); - dest.writeString(mOriginalText); dest.writeInt(mStart); dest.writeInt(mEnd); } @@ -213,7 +282,6 @@ public final class TextLinks implements Parcelable { private TextLink(Parcel in) { mEntityScores = EntityConfidence.CREATOR.createFromParcel(in); - mOriginalText = in.readString(); mStart = in.readInt(); mEnd = in.readInt(); } @@ -227,6 +295,32 @@ public final class TextLinks implements Parcelable { private LocaleList mDefaultLocales; private TextClassifier.EntityConfig mEntityConfig; + private @ApplyStrategy int mApplyStrategy; + private Function mSpanFactory; + + /** + * Returns a new options object based on the specified link mask. + */ + public static Options fromLinkMask(@LinkifyMask int mask) { + final TextClassifier.EntityConfig entityConfig = + new TextClassifier.EntityConfig(TextClassifier.ENTITY_PRESET_NONE); + + if ((mask & Linkify.WEB_URLS) != 0) { + entityConfig.includeEntities(TextClassifier.TYPE_URL); + } + if ((mask & Linkify.EMAIL_ADDRESSES) != 0) { + entityConfig.includeEntities(TextClassifier.TYPE_EMAIL); + } + if ((mask & Linkify.PHONE_NUMBERS) != 0) { + entityConfig.includeEntities(TextClassifier.TYPE_PHONE); + } + if ((mask & Linkify.MAP_ADDRESSES) != 0) { + entityConfig.includeEntities(TextClassifier.TYPE_ADDRESS); + } + + return new Options().setEntityConfig(entityConfig); + } + public Options() {} /** @@ -250,6 +344,31 @@ public final class TextLinks implements Parcelable { return this; } + /** + * Sets a strategy for resolving conflicts when applying generated links to text that + * already have links. + * + * @throws IllegalArgumentException if applyStrategy is not valid + * + * @see #APPLY_STRATEGY_IGNORE + * @see #APPLY_STRAGETY_REPLACE + */ + public Options setApplyStrategy(@ApplyStrategy int applyStrategy) { + checkValidApplyStrategy(applyStrategy); + mApplyStrategy = applyStrategy; + return this; + } + + /** + * Sets a factory for converting a TextLink to a TextLinkSpan. + * + *

    Note: This is not parceled over IPC. + */ + public Options setSpanFactory(@Nullable Function spanFactory) { + mSpanFactory = spanFactory; + return this; + } + /** * @return ordered list of locale preferences that can be used to disambiguate * the provided text. @@ -260,7 +379,7 @@ public final class TextLinks implements Parcelable { } /** - * @return The config representing the set of entities to look for. + * @return The config representing the set of entities to look for * @see #setEntityConfig(TextClassifier.EntityConfig) */ @Nullable @@ -268,6 +387,29 @@ public final class TextLinks implements Parcelable { return mEntityConfig; } + /** + * @return the strategy for resolving conflictswhen applying generated links to text that + * already have links. + * + * @see #APPLY_STATEGY_IGNORE + * @see #APPLY_STRAGETY_REPLACE + */ + @ApplyStrategy + public int getApplyStrategy() { + return mApplyStrategy; + } + + /** + * Returns a factory for converting a TextLink to a TextLinkSpan. + * + *

    Note: This is not parcelable and will always return null if read + * from a parcel + */ + @Nullable + public Function getSpanFactory() { + return mSpanFactory; + } + @Override public int describeContents() { return 0; @@ -283,6 +425,7 @@ public final class TextLinks implements Parcelable { if (mEntityConfig != null) { mEntityConfig.writeToParcel(dest, flags); } + dest.writeInt(mApplyStrategy); } public static final Parcelable.Creator CREATOR = @@ -305,39 +448,53 @@ public final class TextLinks implements Parcelable { if (in.readInt() > 0) { mEntityConfig = TextClassifier.EntityConfig.CREATOR.createFromParcel(in); } + mApplyStrategy = in.readInt(); } } /** * A function to create spans from TextLinks. + */ + private static final Function DEFAULT_SPAN_FACTORY = + textLink -> new TextLinkSpan(textLink); + + /** + * A ClickableSpan for a TextLink. * - * Applies only to TextViews. - * We can hide this until we are convinced we want it to be part of the public API. - * - * @hide + *

    Applies only to TextViews. */ - public static final Function DEFAULT_SPAN_FACTORY = - textLink -> new ClickableSpan() { - @Override - public void onClick(View widget) { - if (widget instanceof TextView) { - final TextView textView = (TextView) widget; - textView.requestActionMode(textLink); - } - } - }; + public static class TextLinkSpan extends ClickableSpan { + + private final TextLink mTextLink; + + public TextLinkSpan(@Nullable TextLink textLink) { + mTextLink = textLink; + } + + @Override + public void onClick(View widget) { + if (widget instanceof TextView) { + final TextView textView = (TextView) widget; + textView.requestActionMode(mTextLink); + } + } + + public final TextLink getTextLink() { + return mTextLink; + } + } /** * A builder to construct a TextLinks instance. */ public static final class Builder { private final String mFullText; - private final Collection mLinks; + private final ArrayList mLinks; /** * Create a new TextLinks.Builder. * - * @param fullText The full text that links will be added to. + * @param fullText The full text to annotate with links. */ public Builder(@NonNull String fullText) { mFullText = Preconditions.checkNotNull(fullText); @@ -348,10 +505,19 @@ public final class TextLinks implements Parcelable { * Adds a TextLink. * * @return this instance. + * + * @throws IllegalArgumentException if entityScores is null or empty. + */ + public Builder addLink(int start, int end, Map entityScores) { + mLinks.add(new TextLink(start, end, entityScores)); + return this; + } + + /** + * Removes all {@link TextLink}s. */ - public Builder addLink(TextLink link) { - Preconditions.checkNotNull(link); - mLinks.add(link); + public Builder clearTextLinks() { + mLinks.clear(); return this; } @@ -364,4 +530,14 @@ public final class TextLinks implements Parcelable { return new TextLinks(mFullText, mLinks); } } + + /** + * @throws IllegalArgumentException if the value is invalid + */ + private static void checkValidApplyStrategy(int applyStrategy) { + if (applyStrategy != APPLY_STRATEGY_IGNORE && applyStrategy != APPLY_STRATEGY_REPLACE) { + throw new IllegalArgumentException( + "Invalid apply strategy. See TextLinks.ApplyStrategy for options."); + } + } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java index a82542cd91c..d6ac8454b86 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextLinksTest.java @@ -68,8 +68,8 @@ public class TextLinksTest { public void testParcel() { final String fullText = "this is just a test"; final TextLinks reference = new TextLinks.Builder(fullText) - .addLink(new TextLinks.TextLink(fullText, 0, 4, getEntityScores(0.f, 0.f, 1.f))) - .addLink(new TextLinks.TextLink(fullText, 5, 12, getEntityScores(.8f, .1f, .5f))) + .addLink(0, 4, getEntityScores(0.f, 0.f, 1.f)) + .addLink(5, 12, getEntityScores(.8f, .1f, .5f)) .build(); // Parcel and unparcel. diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java index bbca12f9d45..69e5670f02d 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -325,9 +325,9 @@ public class TextViewActivityTest { TextClassificationManager textClassificationManager = mActivity.getSystemService(TextClassificationManager.class); TextClassifier textClassifier = textClassificationManager.getTextClassifier(); - SpannableString content = new SpannableString("Call me at +19148277737"); + Spannable content = new SpannableString("Call me at +19148277737"); TextLinks links = textClassifier.generateLinks(content); - links.apply(content, null); + links.apply(content, TextLinks.APPLY_STRATEGY_REPLACE, null); mActivityRule.runOnUiThread(() -> { textView.setText(content); -- GitLab From 3bb443613820c7e54512cef9659ef2e9428243c6 Mon Sep 17 00:00:00 2001 From: Abodunrinwa Toki Date: Tue, 5 Dec 2017 07:33:41 +0000 Subject: [PATCH 157/416] Implement TextClassifier.getLogger API - Introduces getLogger() API. - A logger should run in the client's process. This helps us manage sessions specific to a client. - The logger exposes a tokenizer that clients may use to tokenize strings for logging purposes. - Logger subclasses need to provide a writeEvent() implementation. - SelectionEvent is serializable over IPC. - Logger takes care of the session management. It writes session specific information into the SelectionEvent. - We still keep the SmartSelectionEventTracker for now so clients can slowly move off of it. The plan is to delete it. - The plan is to include support other event types. e.g. link events. Bug: 64914512 Bug: 67609167 Test: See topic Change-Id: Ic9470cf8f969add8a4c6570f78603d0b118956cd --- api/current.txt | 70 +++ .../view/textclassifier/TextClassifier.java | 12 +- .../textclassifier/TextClassifierImpl.java | 31 +- .../textclassifier/logging/DefaultLogger.java | 263 +++++++++++ .../view/textclassifier/logging/Logger.java | 429 ++++++++++++++++++ .../logging/SelectionEvent.java | 337 ++++++++++++++ .../widget/SelectionActionModeHelper.java | 85 ++-- 7 files changed, 1172 insertions(+), 55 deletions(-) create mode 100644 core/java/android/view/textclassifier/logging/DefaultLogger.java create mode 100644 core/java/android/view/textclassifier/logging/Logger.java create mode 100644 core/java/android/view/textclassifier/logging/SelectionEvent.java diff --git a/api/current.txt b/api/current.txt index f5da9b05e6b..4eb360e2000 100644 --- a/api/current.txt +++ b/api/current.txt @@ -50096,6 +50096,7 @@ package android.view.textclassifier { method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options); method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence); method public default java.util.Collection getEntitiesForPreset(int); + method public default android.view.textclassifier.logging.Logger getLogger(android.view.textclassifier.logging.Logger.Config); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList); @@ -50191,6 +50192,75 @@ package android.view.textclassifier { } +package android.view.textclassifier.logging { + + public abstract class Logger { + ctor public Logger(android.view.textclassifier.logging.Logger.Config); + method public java.text.BreakIterator getTokenIterator(java.util.Locale); + method public boolean isSmartSelection(java.lang.String); + method public final void logSelectionActionEvent(int, int, int); + method public final void logSelectionActionEvent(int, int, int, android.view.textclassifier.TextClassification); + method public final void logSelectionModifiedEvent(int, int); + method public final void logSelectionModifiedEvent(int, int, android.view.textclassifier.TextClassification); + method public final void logSelectionModifiedEvent(int, int, android.view.textclassifier.TextSelection); + method public final void logSelectionStartedEvent(int); + method public abstract void writeEvent(android.view.textclassifier.logging.SelectionEvent); + field public static final int OUT_OF_BOUNDS = 2147483647; // 0x7fffffff + field public static final int OUT_OF_BOUNDS_NEGATIVE = -2147483648; // 0x80000000 + field public static final java.lang.String WIDGET_CUSTOM_EDITTEXT = "customedit"; + field public static final java.lang.String WIDGET_CUSTOM_TEXTVIEW = "customview"; + field public static final java.lang.String WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; + field public static final java.lang.String WIDGET_EDITTEXT = "edittext"; + field public static final java.lang.String WIDGET_EDIT_WEBVIEW = "edit-webview"; + field public static final java.lang.String WIDGET_TEXTVIEW = "textview"; + field public static final java.lang.String WIDGET_UNKNOWN = "unknown"; + field public static final java.lang.String WIDGET_UNSELECTABLE_TEXTVIEW = "nosel-textview"; + field public static final java.lang.String WIDGET_WEBVIEW = "webview"; + } + + public static final class Logger.Config { + ctor public Logger.Config(android.content.Context, java.lang.String, java.lang.String); + method public java.lang.String getPackageName(); + method public java.lang.String getWidgetType(); + method public java.lang.String getWidgetVersion(); + } + + public final class SelectionEvent { + method public long getDurationSincePreviousEvent(); + method public long getDurationSinceSessionStart(); + method public int getEnd(); + method public java.lang.String getEntityType(); + method public int getEventIndex(); + method public long getEventTime(); + method public int getEventType(); + method public java.lang.String getPackageName(); + method public java.lang.String getSessionId(); + method public java.lang.String getSignature(); + method public int getSmartEnd(); + method public int getSmartStart(); + method public int getStart(); + method public java.lang.String getWidgetType(); + method public java.lang.String getWidgetVersion(); + field public static final int ACTION_ABANDON = 107; // 0x6b + field public static final int ACTION_COPY = 101; // 0x65 + field public static final int ACTION_CUT = 103; // 0x67 + field public static final int ACTION_DRAG = 106; // 0x6a + field public static final int ACTION_OTHER = 108; // 0x6c + field public static final int ACTION_OVERTYPE = 100; // 0x64 + field public static final int ACTION_PASTE = 102; // 0x66 + field public static final int ACTION_RESET = 201; // 0xc9 + field public static final int ACTION_SELECT_ALL = 200; // 0xc8 + field public static final int ACTION_SHARE = 104; // 0x68 + field public static final int ACTION_SMART_SHARE = 105; // 0x69 + field public static final int EVENT_AUTO_SELECTION = 5; // 0x5 + field public static final int EVENT_SELECTION_MODIFIED = 2; // 0x2 + field public static final int EVENT_SELECTION_STARTED = 1; // 0x1 + field public static final int EVENT_SMART_SELECTION_MULTI = 4; // 0x4 + field public static final int EVENT_SMART_SELECTION_SINGLE = 3; // 0x3 + } + +} + package android.view.textservice { public final class SentenceSuggestionsInfo implements android.os.Parcelable { diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index 5dd9ac62fbb..9f75c4a80ca 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -28,6 +28,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; import android.util.Slog; +import android.view.textclassifier.logging.Logger; import com.android.internal.util.Preconditions; @@ -319,14 +320,15 @@ public interface TextClassifier { } /** - * Logs a TextClassifier event. + * Returns a helper for logging TextClassifier related events. * - * @param source the text classifier used to generate this event - * @param event the text classifier related event - * @hide + * @param config logger configuration */ @WorkerThread - default void logEvent(String source, String event) {} + default Logger getLogger(@NonNull Logger.Config config) { + Preconditions.checkNotNull(config); + return Logger.DISABLED; + } /** * Returns this TextClassifier's settings. diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index 4da5bbf69a7..4d4c171c1cf 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -35,6 +35,8 @@ import android.provider.ContactsContract; import android.provider.Settings; import android.text.util.Linkify; import android.util.Patterns; +import android.view.textclassifier.logging.DefaultLogger; +import android.view.textclassifier.logging.Logger; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; @@ -43,6 +45,7 @@ import com.android.internal.util.Preconditions; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -104,6 +107,12 @@ public final class TextClassifierImpl implements TextClassifier { @GuardedBy("mLock") // Do not access outside this lock. private SmartSelection mSmartSelection; + private final Object mLoggerLock = new Object(); + @GuardedBy("mLoggerLock") // Do not access outside this lock. + private WeakReference mLoggerConfig = new WeakReference<>(null); + @GuardedBy("mLoggerLock") // Do not access outside this lock. + private Logger mLogger; // Should never be null if mLoggerConfig.get() is not null. + private TextClassifierConstants mSettings; public TextClassifierImpl(Context context) { @@ -245,11 +254,15 @@ public final class TextClassifierImpl implements TextClassifier { } } - /** @hide */ @Override - public void logEvent(String source, String event) { - if (LOG_TAG.equals(source)) { - mMetricsLogger.count(event, 1); + public Logger getLogger(@NonNull Logger.Config config) { + Preconditions.checkNotNull(config); + synchronized (mLoggerLock) { + if (mLoggerConfig.get() == null || !mLoggerConfig.get().equals(config)) { + mLoggerConfig = new WeakReference<>(config); + mLogger = new DefaultLogger(config); + } + return mLogger; } } @@ -285,11 +298,7 @@ public final class TextClassifierImpl implements TextClassifier { private String getSignature(String text, int start, int end) { synchronized (mLock) { - final String versionInfo = (mLocale != null) - ? String.format(Locale.US, "%s_v%d", mLocale.toLanguageTag(), mVersion) - : ""; - final int hash = Objects.hash(text, start, end, mContext.getPackageName()); - return String.format(Locale.US, "%s|%s|%d", LOG_TAG, versionInfo, hash); + return DefaultLogger.createSignature(text, start, end, mContext, mVersion, mLocale); } } @@ -328,7 +337,7 @@ public final class TextClassifierImpl implements TextClassifier { return factoryFd; } else { throw new FileNotFoundException( - String.format("No model file found for %s", locale)); + String.format(Locale.US, "No model file found for %s", locale)); } } @@ -342,7 +351,7 @@ public final class TextClassifierImpl implements TextClassifier { } else { closeAndLogError(updateFd); throw new FileNotFoundException( - String.format("No model file found for %s", locale)); + String.format(Locale.US, "No model file found for %s", locale)); } } diff --git a/core/java/android/view/textclassifier/logging/DefaultLogger.java b/core/java/android/view/textclassifier/logging/DefaultLogger.java new file mode 100644 index 00000000000..6b848351cbf --- /dev/null +++ b/core/java/android/view/textclassifier/logging/DefaultLogger.java @@ -0,0 +1,263 @@ +/* + * 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.view.textclassifier.logging; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.metrics.LogMaker; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.util.Preconditions; + +import java.util.Locale; +import java.util.Objects; + +/** + * Default Logger. + * Used internally by TextClassifierImpl. + * @hide + */ +public final class DefaultLogger extends Logger { + + private static final String LOG_TAG = "DefaultLogger"; + private static final String CLASSIFIER_ID = "androidtc"; + + private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START; + private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS; + private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX; + private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE; + private static final int WIDGET_VERSION = MetricsEvent.FIELD_SELECTION_WIDGET_VERSION; + private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL; + private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE; + private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START; + private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END; + private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START; + private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END; + private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID; + + private static final String ZERO = "0"; + private static final String UNKNOWN = "unknown"; + + private final MetricsLogger mMetricsLogger; + + public DefaultLogger(@NonNull Config config) { + super(config); + mMetricsLogger = new MetricsLogger(); + } + + @VisibleForTesting + public DefaultLogger(@NonNull Config config, @NonNull MetricsLogger metricsLogger) { + super(config); + mMetricsLogger = Preconditions.checkNotNull(metricsLogger); + } + + @Override + public boolean isSmartSelection(@NonNull String signature) { + return CLASSIFIER_ID.equals(SignatureParser.getClassifierId(signature)); + } + + @Override + public void writeEvent(@NonNull SelectionEvent event) { + Preconditions.checkNotNull(event); + final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION) + .setType(getLogType(event)) + .setSubtype(MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL) + .setPackageName(event.getPackageName()) + .addTaggedData(START_EVENT_DELTA, event.getDurationSinceSessionStart()) + .addTaggedData(PREV_EVENT_DELTA, event.getDurationSincePreviousEvent()) + .addTaggedData(INDEX, event.getEventIndex()) + .addTaggedData(WIDGET_TYPE, event.getWidgetType()) + .addTaggedData(WIDGET_VERSION, event.getWidgetVersion()) + .addTaggedData(MODEL_NAME, SignatureParser.getModelName(event.getSignature())) + .addTaggedData(ENTITY_TYPE, event.getEntityType()) + .addTaggedData(SMART_START, event.getSmartStart()) + .addTaggedData(SMART_END, event.getSmartEnd()) + .addTaggedData(EVENT_START, event.getStart()) + .addTaggedData(EVENT_END, event.getEnd()) + .addTaggedData(SESSION_ID, event.getSessionId()); + mMetricsLogger.write(log); + debugLog(log); + } + + private static int getLogType(SelectionEvent event) { + switch (event.getEventType()) { + case SelectionEvent.ACTION_OVERTYPE: + return MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE; + case SelectionEvent.ACTION_COPY: + return MetricsEvent.ACTION_TEXT_SELECTION_COPY; + case SelectionEvent.ACTION_PASTE: + return MetricsEvent.ACTION_TEXT_SELECTION_PASTE; + case SelectionEvent.ACTION_CUT: + return MetricsEvent.ACTION_TEXT_SELECTION_CUT; + case SelectionEvent.ACTION_SHARE: + return MetricsEvent.ACTION_TEXT_SELECTION_SHARE; + case SelectionEvent.ACTION_SMART_SHARE: + return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE; + case SelectionEvent.ACTION_DRAG: + return MetricsEvent.ACTION_TEXT_SELECTION_DRAG; + case SelectionEvent.ACTION_ABANDON: + return MetricsEvent.ACTION_TEXT_SELECTION_ABANDON; + case SelectionEvent.ACTION_OTHER: + return MetricsEvent.ACTION_TEXT_SELECTION_OTHER; + case SelectionEvent.ACTION_SELECT_ALL: + return MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL; + case SelectionEvent.ACTION_RESET: + return MetricsEvent.ACTION_TEXT_SELECTION_RESET; + case SelectionEvent.EVENT_SELECTION_STARTED: + return MetricsEvent.ACTION_TEXT_SELECTION_START; + case SelectionEvent.EVENT_SELECTION_MODIFIED: + return MetricsEvent.ACTION_TEXT_SELECTION_MODIFY; + case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: + return MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE; + case SelectionEvent.EVENT_SMART_SELECTION_MULTI: + return MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI; + case SelectionEvent.EVENT_AUTO_SELECTION: + return MetricsEvent.ACTION_TEXT_SELECTION_AUTO; + default: + return MetricsEvent.VIEW_UNKNOWN; + } + } + + private static String getLogTypeString(int logType) { + switch (logType) { + case MetricsEvent.ACTION_TEXT_SELECTION_OVERTYPE: + return "OVERTYPE"; + case MetricsEvent.ACTION_TEXT_SELECTION_COPY: + return "COPY"; + case MetricsEvent.ACTION_TEXT_SELECTION_PASTE: + return "PASTE"; + case MetricsEvent.ACTION_TEXT_SELECTION_CUT: + return "CUT"; + case MetricsEvent.ACTION_TEXT_SELECTION_SHARE: + return "SHARE"; + case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SHARE: + return "SMART_SHARE"; + case MetricsEvent.ACTION_TEXT_SELECTION_DRAG: + return "DRAG"; + case MetricsEvent.ACTION_TEXT_SELECTION_ABANDON: + return "ABANDON"; + case MetricsEvent.ACTION_TEXT_SELECTION_OTHER: + return "OTHER"; + case MetricsEvent.ACTION_TEXT_SELECTION_SELECT_ALL: + return "SELECT_ALL"; + case MetricsEvent.ACTION_TEXT_SELECTION_RESET: + return "RESET"; + case MetricsEvent.ACTION_TEXT_SELECTION_START: + return "SELECTION_STARTED"; + case MetricsEvent.ACTION_TEXT_SELECTION_MODIFY: + return "SELECTION_MODIFIED"; + case MetricsEvent.ACTION_TEXT_SELECTION_SMART_SINGLE: + return "SMART_SELECTION_SINGLE"; + case MetricsEvent.ACTION_TEXT_SELECTION_SMART_MULTI: + return "SMART_SELECTION_MULTI"; + case MetricsEvent.ACTION_TEXT_SELECTION_AUTO: + return "AUTO_SELECTION"; + default: + return UNKNOWN; + } + } + + private static void debugLog(LogMaker log) { + if (!DEBUG_LOG_ENABLED) return; + + final String widgetType = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN); + final String widgetVersion = Objects.toString(log.getTaggedData(WIDGET_VERSION), ""); + final String widget = widgetVersion.isEmpty() + ? widgetType : widgetType + "-" + widgetVersion; + final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO)); + if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) { + String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), ""); + sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1); + Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId)); + } + + final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN); + final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN); + final String type = getLogTypeString(log.getType()); + final int smartStart = Integer.parseInt( + Objects.toString(log.getTaggedData(SMART_START), ZERO)); + final int smartEnd = Integer.parseInt( + Objects.toString(log.getTaggedData(SMART_END), ZERO)); + final int eventStart = Integer.parseInt( + Objects.toString(log.getTaggedData(EVENT_START), ZERO)); + final int eventEnd = Integer.parseInt( + Objects.toString(log.getTaggedData(EVENT_END), ZERO)); + + Log.d(LOG_TAG, String.format("%2d: %s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)", + index, type, entity, eventStart, eventEnd, smartStart, smartEnd, widget, model)); + } + + /** + * Creates a signature string that may be used to tag TextClassifier results. + */ + public static String createSignature( + String text, int start, int end, Context context, int modelVersion, + @Nullable Locale locale) { + Preconditions.checkNotNull(text); + Preconditions.checkNotNull(context); + final String modelName = (locale != null) + ? String.format(Locale.US, "%s_v%d", locale.toLanguageTag(), modelVersion) + : ""; + final int hash = Objects.hash(text, start, end, context.getPackageName()); + return SignatureParser.createSignature(CLASSIFIER_ID, modelName, hash); + } + + /** + * Helper for creating and parsing signature strings for + * {@link android.view.textclassifier.TextClassifierImpl}. + */ + @VisibleForTesting + public static final class SignatureParser { + + static String createSignature(String classifierId, String modelName, int hash) { + return String.format(Locale.US, "%s|%s|%d", classifierId, modelName, hash); + } + + static String getClassifierId(String signature) { + Preconditions.checkNotNull(signature); + final int end = signature.indexOf("|"); + if (end >= 0) { + return signature.substring(0, end); + } + return ""; + } + + static String getModelName(String signature) { + Preconditions.checkNotNull(signature); + final int start = signature.indexOf("|"); + final int end = signature.indexOf("|", start); + if (start >= 0 && end >= start) { + return signature.substring(start, end); + } + return ""; + } + + static int getHash(String signature) { + Preconditions.checkNotNull(signature); + final int index1 = signature.indexOf("|"); + final int index2 = signature.indexOf("|", index1); + if (index2 > 0) { + return Integer.parseInt(signature.substring(index2)); + } + return 0; + } + } +} diff --git a/core/java/android/view/textclassifier/logging/Logger.java b/core/java/android/view/textclassifier/logging/Logger.java new file mode 100644 index 00000000000..40e4d8ce1a7 --- /dev/null +++ b/core/java/android/view/textclassifier/logging/Logger.java @@ -0,0 +1,429 @@ +/* + * 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.view.textclassifier.logging; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringDef; +import android.content.Context; +import android.util.Log; +import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextSelection; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.text.BreakIterator; +import java.util.Locale; +import java.util.Objects; +import java.util.UUID; + +/** + * A helper for logging TextClassifier related events. + */ +public abstract class Logger { + + /** + * Use this to specify an indeterminate positive index. + */ + public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE; + + /** + * Use this to specify an indeterminate negative index. + */ + public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE; + + private static final String LOG_TAG = "Logger"; + /* package */ static final boolean DEBUG_LOG_ENABLED = true; + + private static final String NO_SIGNATURE = ""; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef({WIDGET_TEXTVIEW, WIDGET_WEBVIEW, WIDGET_EDITTEXT, + WIDGET_EDIT_WEBVIEW, WIDGET_CUSTOM_TEXTVIEW, WIDGET_CUSTOM_EDITTEXT, + WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW, WIDGET_UNKNOWN}) + public @interface WidgetType {} + + public static final String WIDGET_TEXTVIEW = "textview"; + public static final String WIDGET_EDITTEXT = "edittext"; + public static final String WIDGET_UNSELECTABLE_TEXTVIEW = "nosel-textview"; + public static final String WIDGET_WEBVIEW = "webview"; + public static final String WIDGET_EDIT_WEBVIEW = "edit-webview"; + public static final String WIDGET_CUSTOM_TEXTVIEW = "customview"; + public static final String WIDGET_CUSTOM_EDITTEXT = "customedit"; + public static final String WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; + public static final String WIDGET_UNKNOWN = "unknown"; + + private SelectionEvent mPrevEvent; + private SelectionEvent mSmartEvent; + private SelectionEvent mStartEvent; + + /** + * Logger that does not log anything. + * @hide + */ + public static final Logger DISABLED = new Logger() { + @Override + public void writeEvent(SelectionEvent event) {} + }; + + @Nullable + private final Config mConfig; + + public Logger(Config config) { + mConfig = Preconditions.checkNotNull(config); + } + + private Logger() { + mConfig = null; + } + + /** + * Writes the selection event. + * + *

    NOTE: This method is designed for subclasses. + * Apps should not call it directly. + */ + public abstract void writeEvent(@NonNull SelectionEvent event); + + /** + * Returns true if the signature matches that of a smart selection event (i.e. + * {@link SelectionEvent#EVENT_SMART_SELECTION_SINGLE} or + * {@link SelectionEvent#EVENT_SMART_SELECTION_MULTI}). + * Returns false otherwise. + */ + public boolean isSmartSelection(@NonNull String signature) { + return false; + } + + + /** + * Returns a token iterator for tokenizing text for logging purposes. + */ + public BreakIterator getTokenIterator(@NonNull Locale locale) { + return BreakIterator.getWordInstance(Preconditions.checkNotNull(locale)); + } + + /** + * Logs a "selection started" event. + * + * @param start the token index of the selected token + */ + public final void logSelectionStartedEvent(int start) { + if (mConfig == null) { + return; + } + + logEvent(new SelectionEvent( + start, start + 1, SelectionEvent.EVENT_SELECTION_STARTED, + TextClassifier.TYPE_UNKNOWN, NO_SIGNATURE, mConfig)); + } + + /** + * Logs a "selection modified" event. + * Use when the user modifies the selection. + * + * @param start the start token (inclusive) index of the selection + * @param end the end token (exclusive) index of the selection + */ + public final void logSelectionModifiedEvent(int start, int end) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + + if (mConfig == null) { + return; + } + + logEvent(new SelectionEvent( + start, end, SelectionEvent.EVENT_SELECTION_MODIFIED, + TextClassifier.TYPE_UNKNOWN, NO_SIGNATURE, mConfig)); + } + + /** + * Logs a "selection modified" event. + * Use when the user modifies the selection and the selection's entity type is known. + * + * @param start the start token (inclusive) index of the selection + * @param end the end token (exclusive) index of the selection + * @param classification the TextClassification object returned by the TextClassifier that + * classified the selected text + */ + public final void logSelectionModifiedEvent( + int start, int end, @NonNull TextClassification classification) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + Preconditions.checkNotNull(classification); + + if (mConfig == null) { + return; + } + + final String entityType = classification.getEntityCount() > 0 + ? classification.getEntity(0) + : TextClassifier.TYPE_UNKNOWN; + final String signature = classification.getSignature(); + logEvent(new SelectionEvent( + start, end, SelectionEvent.EVENT_SELECTION_MODIFIED, + entityType, signature, mConfig)); + } + + /** + * Logs a "selection modified" event. + * Use when a TextClassifier modifies the selection. + * + * @param start the start token (inclusive) index of the selection + * @param end the end token (exclusive) index of the selection + * @param selection the TextSelection object returned by the TextClassifier for the + * specified selection + */ + public final void logSelectionModifiedEvent( + int start, int end, @NonNull TextSelection selection) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + Preconditions.checkNotNull(selection); + + if (mConfig == null) { + return; + } + + final int eventType; + if (isSmartSelection(selection.getSignature())) { + eventType = end - start > 1 + ? SelectionEvent.EVENT_SMART_SELECTION_MULTI + : SelectionEvent.EVENT_SMART_SELECTION_SINGLE; + + } else { + eventType = SelectionEvent.EVENT_AUTO_SELECTION; + } + final String entityType = selection.getEntityCount() > 0 + ? selection.getEntity(0) + : TextClassifier.TYPE_UNKNOWN; + final String signature = selection.getSignature(); + logEvent(new SelectionEvent(start, end, eventType, entityType, signature, mConfig)); + } + + /** + * Logs an event specifying an action taken on a selection. + * Use when the user clicks on an action to act on the selected text. + * + * @param start the start token (inclusive) index of the selection + * @param end the end token (exclusive) index of the selection + * @param actionType the action that was performed on the selection + */ + public final void logSelectionActionEvent( + int start, int end, @SelectionEvent.ActionType int actionType) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + checkActionType(actionType); + + if (mConfig == null) { + return; + } + + logEvent(new SelectionEvent( + start, end, actionType, TextClassifier.TYPE_UNKNOWN, NO_SIGNATURE, mConfig)); + } + + /** + * Logs an event specifying an action taken on a selection. + * Use when the user clicks on an action to act on the selected text and the selection's + * entity type is known. + * + * @param start the start token (inclusive) index of the selection + * @param end the end token (exclusive) index of the selection + * @param actionType the action that was performed on the selection + * @param classification the TextClassification object returned by the TextClassifier that + * classified the selected text + * + * @throws IllegalArgumentException If actionType is not a valid SelectionEvent actionType + */ + public final void logSelectionActionEvent( + int start, int end, @SelectionEvent.ActionType int actionType, + @NonNull TextClassification classification) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + Preconditions.checkNotNull(classification); + checkActionType(actionType); + + if (mConfig == null) { + return; + } + + final String entityType = classification.getEntityCount() > 0 + ? classification.getEntity(0) + : TextClassifier.TYPE_UNKNOWN; + final String signature = classification.getSignature(); + logEvent(new SelectionEvent(start, end, actionType, entityType, signature, mConfig)); + } + + private void logEvent(@NonNull SelectionEvent event) { + Preconditions.checkNotNull(event); + + if (event.getEventType() != SelectionEvent.EVENT_SELECTION_STARTED + && mStartEvent == null) { + if (DEBUG_LOG_ENABLED) { + Log.d(LOG_TAG, "Selection session not yet started. Ignoring event"); + } + return; + } + + final long now = System.currentTimeMillis(); + switch (event.getEventType()) { + case SelectionEvent.EVENT_SELECTION_STARTED: + Preconditions.checkArgument(event.getAbsoluteEnd() == event.getAbsoluteStart() + 1); + event.setSessionId(startNewSession()); + mStartEvent = event; + break; + case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: // fall through + case SelectionEvent.EVENT_SMART_SELECTION_MULTI: + mSmartEvent = event; + break; + case SelectionEvent.EVENT_SELECTION_MODIFIED: // fall through + case SelectionEvent.EVENT_AUTO_SELECTION: + if (mPrevEvent != null + && mPrevEvent.getAbsoluteStart() == event.getAbsoluteStart() + && mPrevEvent.getAbsoluteEnd() == event.getAbsoluteEnd()) { + // Selection did not change. Ignore event. + return; + } + } + + event.setEventTime(now); + if (mStartEvent != null) { + event.setSessionId(mStartEvent.getSessionId()) + .setDurationSinceSessionStart(now - mStartEvent.getEventTime()) + .setStart(event.getAbsoluteStart() - mStartEvent.getAbsoluteStart()) + .setEnd(event.getAbsoluteEnd() - mStartEvent.getAbsoluteStart()); + } + if (mSmartEvent != null) { + event.setSignature(mSmartEvent.getSignature()) + .setSmartStart(mSmartEvent.getAbsoluteStart() - mStartEvent.getAbsoluteStart()) + .setSmartEnd(mSmartEvent.getAbsoluteEnd() - mStartEvent.getAbsoluteStart()); + } + if (mPrevEvent != null) { + event.setDurationSincePreviousEvent(now - mPrevEvent.getEventTime()) + .setEventIndex(mPrevEvent.getEventIndex() + 1); + } + writeEvent(event); + mPrevEvent = event; + + if (event.isTerminal()) { + endSession(); + } + } + + private String startNewSession() { + endSession(); + return UUID.randomUUID().toString(); + } + + private void endSession() { + mPrevEvent = null; + mSmartEvent = null; + mStartEvent = null; + } + + /** + * @throws IllegalArgumentException If eventType is not an {@link SelectionEvent.ActionType} + */ + private static void checkActionType(@SelectionEvent.EventType int eventType) + throws IllegalArgumentException { + switch (eventType) { + case SelectionEvent.ACTION_OVERTYPE: // fall through + case SelectionEvent.ACTION_COPY: // fall through + case SelectionEvent.ACTION_PASTE: // fall through + case SelectionEvent.ACTION_CUT: // fall through + case SelectionEvent.ACTION_SHARE: // fall through + case SelectionEvent.ACTION_SMART_SHARE: // fall through + case SelectionEvent.ACTION_DRAG: // fall through + case SelectionEvent.ACTION_ABANDON: // fall through + case SelectionEvent.ACTION_SELECT_ALL: // fall through + case SelectionEvent.ACTION_RESET: // fall through + return; + default: + throw new IllegalArgumentException( + String.format(Locale.US, "%d is not an eventType", eventType)); + } + } + + + /** + * A Logger config. + */ + public static final class Config { + + private final String mPackageName; + private final String mWidgetType; + @Nullable private final String mWidgetVersion; + + /** + * @param context Context of the widget the logger logs for + * @param widgetType a name for the widget being logged for. e.g. + * {@link #WIDGET_TEXTVIEW} + * @param widgetVersion a string version info for the widget the logger logs for + */ + public Config( + @NonNull Context context, + @WidgetType String widgetType, + @Nullable String widgetVersion) { + mPackageName = Preconditions.checkNotNull(context).getPackageName(); + mWidgetType = widgetType; + mWidgetVersion = widgetVersion; + } + + /** + * Returns the package name of the application the logger logs for. + */ + public String getPackageName() { + return mPackageName; + } + + /** + * Returns the name for the widget being logged for. e.g. {@link #WIDGET_TEXTVIEW}. + */ + public String getWidgetType() { + return mWidgetType; + } + + /** + * Returns string version info for the logger. This is specific to the text classifier. + */ + @Nullable + public String getWidgetVersion() { + return mWidgetVersion; + } + + @Override + public int hashCode() { + return Objects.hash(mPackageName, mWidgetType, mWidgetVersion); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (!(obj instanceof Config)) { + return false; + } + + final Config other = (Config) obj; + return Objects.equals(mPackageName, other.mPackageName) + && Objects.equals(mWidgetType, other.mWidgetType) + && Objects.equals(mWidgetVersion, other.mWidgetType); + } + } +} diff --git a/core/java/android/view/textclassifier/logging/SelectionEvent.java b/core/java/android/view/textclassifier/logging/SelectionEvent.java new file mode 100644 index 00000000000..f40b6557114 --- /dev/null +++ b/core/java/android/view/textclassifier/logging/SelectionEvent.java @@ -0,0 +1,337 @@ +/* + * 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.view.textclassifier.logging; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.view.textclassifier.TextClassifier.EntityType; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Locale; + +/** + * A selection event. + * Specify index parameters as word token indices. + */ +public final class SelectionEvent { + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ACTION_OVERTYPE, ACTION_COPY, ACTION_PASTE, ACTION_CUT, + ACTION_SHARE, ACTION_SMART_SHARE, ACTION_DRAG, ACTION_ABANDON, + ACTION_OTHER, ACTION_SELECT_ALL, ACTION_RESET}) + // NOTE: ActionType values should not be lower than 100 to avoid colliding with the other + // EventTypes declared below. + public @interface ActionType { + /* + * Terminal event types range: [100,200). + * Non-terminal event types range: [200,300). + */ + } + + /** User typed over the selection. */ + public static final int ACTION_OVERTYPE = 100; + /** User copied the selection. */ + public static final int ACTION_COPY = 101; + /** User pasted over the selection. */ + public static final int ACTION_PASTE = 102; + /** User cut the selection. */ + public static final int ACTION_CUT = 103; + /** User shared the selection. */ + public static final int ACTION_SHARE = 104; + /** User clicked the textAssist menu item. */ + public static final int ACTION_SMART_SHARE = 105; + /** User dragged+dropped the selection. */ + public static final int ACTION_DRAG = 106; + /** User abandoned the selection. */ + public static final int ACTION_ABANDON = 107; + /** User performed an action on the selection. */ + public static final int ACTION_OTHER = 108; + + // Non-terminal actions. + /** User activated Select All */ + public static final int ACTION_SELECT_ALL = 200; + /** User reset the smart selection. */ + public static final int ACTION_RESET = 201; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ACTION_OVERTYPE, ACTION_COPY, ACTION_PASTE, ACTION_CUT, + ACTION_SHARE, ACTION_SMART_SHARE, ACTION_DRAG, ACTION_ABANDON, + ACTION_OTHER, ACTION_SELECT_ALL, ACTION_RESET, + EVENT_SELECTION_STARTED, EVENT_SELECTION_MODIFIED, + EVENT_SMART_SELECTION_SINGLE, EVENT_SMART_SELECTION_MULTI, + EVENT_AUTO_SELECTION}) + // NOTE: EventTypes declared here must be less than 100 to avoid colliding with the + // ActionTypes declared above. + public @interface EventType { + /* + * Range: 1 -> 99. + */ + } + + /** User started a new selection. */ + public static final int EVENT_SELECTION_STARTED = 1; + /** User modified an existing selection. */ + public static final int EVENT_SELECTION_MODIFIED = 2; + /** Smart selection triggered for a single token (word). */ + public static final int EVENT_SMART_SELECTION_SINGLE = 3; + /** Smart selection triggered spanning multiple tokens (words). */ + public static final int EVENT_SMART_SELECTION_MULTI = 4; + /** Something else other than User or the default TextClassifier triggered a selection. */ + public static final int EVENT_AUTO_SELECTION = 5; + + private final int mAbsoluteStart; + private final int mAbsoluteEnd; + private final @EventType int mEventType; + private final @EntityType String mEntityType; + @Nullable private final String mWidgetVersion; + private final String mPackageName; + private final String mWidgetType; + + // These fields should only be set by creator of a SelectionEvent. + private String mSignature; + private long mEventTime; + private long mDurationSinceSessionStart; + private long mDurationSinceLastEvent; + private int mEventIndex; + private String mSessionId; + private int mStart; + private int mEnd; + private int mSmartStart; + private int mSmartEnd; + + SelectionEvent( + int start, int end, + @EventType int eventType, @EntityType String entityType, + String signature, Logger.Config config) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + mAbsoluteStart = start; + mAbsoluteEnd = end; + mEventType = eventType; + mEntityType = Preconditions.checkNotNull(entityType); + mSignature = Preconditions.checkNotNull(signature); + Preconditions.checkNotNull(config); + mWidgetVersion = config.getWidgetVersion(); + mPackageName = Preconditions.checkNotNull(config.getPackageName()); + mWidgetType = Preconditions.checkNotNull(config.getWidgetType()); + } + + int getAbsoluteStart() { + return mAbsoluteStart; + } + + int getAbsoluteEnd() { + return mAbsoluteEnd; + } + + /** + * Returns the type of event that was triggered. e.g. {@link #ACTION_COPY}. + */ + public int getEventType() { + return mEventType; + } + + /** + * Returns the type of entity that is associated with this event. e.g. + * {@link android.view.textclassifier.TextClassifier#TYPE_EMAIL}. + */ + @EntityType + public String getEntityType() { + return mEntityType; + } + + /** + * Returns the package name of the app that this event originated in. + */ + public String getPackageName() { + return mPackageName; + } + + /** + * Returns the type of widget that was involved in triggering this event. + */ + public String getWidgetType() { + return mWidgetType; + } + + /** + * Returns a string version info for the widget this event was triggered in. + */ + public String getWidgetVersion() { + return mWidgetVersion; + } + + /** + * Returns the signature of the text classifier result associated with this event. + */ + public String getSignature() { + return mSignature; + } + + SelectionEvent setSignature(String signature) { + mSignature = Preconditions.checkNotNull(signature); + return this; + } + + /** + * Returns the time this event was triggered. + */ + public long getEventTime() { + return mEventTime; + } + + SelectionEvent setEventTime(long timeMs) { + mEventTime = timeMs; + return this; + } + + /** + * Returns the duration in ms between when this event was triggered and when the first event in + * the selection session was triggered. + */ + public long getDurationSinceSessionStart() { + return mDurationSinceSessionStart; + } + + SelectionEvent setDurationSinceSessionStart(long durationMs) { + mDurationSinceSessionStart = durationMs; + return this; + } + + /** + * Returns the duration in ms between when this event was triggered and when the previous event + * in the selection session was triggered. + */ + public long getDurationSincePreviousEvent() { + return mDurationSinceLastEvent; + } + + SelectionEvent setDurationSincePreviousEvent(long durationMs) { + this.mDurationSinceLastEvent = durationMs; + return this; + } + + /** + * Returns the index (e.g. 1st event, 2nd event, etc.) of this event in the selection session. + */ + public int getEventIndex() { + return mEventIndex; + } + + SelectionEvent setEventIndex(int index) { + mEventIndex = index; + return this; + } + + /** + * Returns the selection session id. + */ + public String getSessionId() { + return mSessionId; + } + + SelectionEvent setSessionId(String id) { + mSessionId = id; + return this; + } + + /** + * Returns the start index of this events token relative to the index of the start selection + * event in the selection session. + */ + public int getStart() { + return mStart; + } + + SelectionEvent setStart(int start) { + mStart = start; + return this; + } + + /** + * Returns the end index of this events token relative to the index of the start selection + * event in the selection session. + */ + public int getEnd() { + return mEnd; + } + + SelectionEvent setEnd(int end) { + mEnd = end; + return this; + } + + /** + * Returns the start index of this events token relative to the index of the smart selection + * event in the selection session. + */ + public int getSmartStart() { + return mSmartStart; + } + + SelectionEvent setSmartStart(int start) { + this.mSmartStart = start; + return this; + } + + /** + * Returns the end index of this events token relative to the index of the smart selection + * event in the selection session. + */ + public int getSmartEnd() { + return mSmartEnd; + } + + SelectionEvent setSmartEnd(int end) { + mSmartEnd = end; + return this; + } + + boolean isTerminal() { + switch (mEventType) { + case ACTION_OVERTYPE: // fall through + case ACTION_COPY: // fall through + case ACTION_PASTE: // fall through + case ACTION_CUT: // fall through + case ACTION_SHARE: // fall through + case ACTION_SMART_SHARE: // fall through + case ACTION_DRAG: // fall through + case ACTION_ABANDON: // fall through + case ACTION_OTHER: // fall through + return true; + default: + return false; + } + } + + @Override + public String toString() { + return String.format(Locale.US, + "SelectionEvent {absoluteStart=%d, absoluteEnd=%d, eventType=%d, entityType=%s, " + + "widgetVersion=%s, packageName=%s, widgetType=%s, signature=%s, " + + "eventTime=%d, durationSinceSessionStart=%d, durationSinceLastEvent=%d, " + + "eventIndex=%d, sessionId=%s, start=%d, end=%d, smartStart=%d, smartEnd=%d}", + mAbsoluteStart, mAbsoluteEnd, mEventType, mEntityType, + mWidgetVersion, mPackageName, mWidgetType, mSignature, + mEventTime, mDurationSinceSessionStart, mDurationSinceLastEvent, + mEventIndex, mSessionId, mStart, mEnd, mSmartStart, mSmartEnd); + } +} diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 3f5584e6d3f..2e354c1eee1 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -37,8 +37,8 @@ import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextLinks; import android.view.textclassifier.TextSelection; -import android.view.textclassifier.logging.SmartSelectionEventTracker; -import android.view.textclassifier.logging.SmartSelectionEventTracker.SelectionEvent; +import android.view.textclassifier.logging.Logger; +import android.view.textclassifier.logging.SelectionEvent; import android.widget.Editor.SelectionModifierCursorController; import com.android.internal.annotations.VisibleForTesting; @@ -173,7 +173,7 @@ public final class SelectionActionModeHelper { public void onSelectionDrag() { mSelectionTracker.onSelectionAction( mTextView.getSelectionStart(), mTextView.getSelectionEnd(), - SelectionEvent.ActionType.DRAG, mTextClassification); + SelectionEvent.ACTION_DRAG, mTextClassification); } public void onTextChanged(int start, int end) { @@ -575,7 +575,7 @@ public final class SelectionActionModeHelper { mSelectionEnd = editor.getTextView().getSelectionEnd(); mLogger.logSelectionAction( textView.getSelectionStart(), textView.getSelectionEnd(), - SelectionEvent.ActionType.RESET, null /* classification */); + SelectionEvent.ACTION_RESET, null /* classification */); } return selected; } @@ -584,7 +584,7 @@ public final class SelectionActionModeHelper { public void onTextChanged(int start, int end, TextClassification classification) { if (isSelectionStarted() && start == mSelectionStart && end == mSelectionEnd) { - onSelectionAction(start, end, SelectionEvent.ActionType.OVERTYPE, classification); + onSelectionAction(start, end, SelectionEvent.ACTION_OVERTYPE, classification); } } @@ -623,7 +623,7 @@ public final class SelectionActionModeHelper { if (mIsPending) { mLogger.logSelectionAction( mSelectionStart, mSelectionEnd, - SelectionEvent.ActionType.ABANDON, null /* classification */); + SelectionEvent.ACTION_ABANDON, null /* classification */); mSelectionStart = mSelectionEnd = -1; mIsPending = false; } @@ -650,22 +650,29 @@ public final class SelectionActionModeHelper { private static final String LOG_TAG = "SelectionMetricsLogger"; private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s+"); - private final SmartSelectionEventTracker mDelegate; + private final Logger mLogger; private final boolean mEditTextLogger; - private final BreakIterator mWordIterator; + private final BreakIterator mTokenIterator; private int mStartIndex; private String mText; SelectionMetricsLogger(TextView textView) { Preconditions.checkNotNull(textView); - final @SmartSelectionEventTracker.WidgetType int widgetType = textView.isTextEditable() - ? SmartSelectionEventTracker.WidgetType.EDITTEXT - : (textView.isTextSelectable() - ? SmartSelectionEventTracker.WidgetType.TEXTVIEW - : SmartSelectionEventTracker.WidgetType.UNSELECTABLE_TEXTVIEW); - mDelegate = new SmartSelectionEventTracker(textView.getContext(), widgetType); + mLogger = textView.getTextClassifier().getLogger( + new Logger.Config(textView.getContext(), getWidetType(textView), null)); mEditTextLogger = textView.isTextEditable(); - mWordIterator = BreakIterator.getWordInstance(textView.getTextLocale()); + mTokenIterator = mLogger.getTokenIterator(textView.getTextLocale()); + } + + @Logger.WidgetType + private static String getWidetType(TextView textView) { + if (textView.isTextEditable()) { + return Logger.WIDGET_EDITTEXT; + } + if (textView.isTextSelectable()) { + return Logger.WIDGET_TEXTVIEW; + } + return Logger.WIDGET_UNSELECTABLE_TEXTVIEW; } public void logSelectionStarted(CharSequence text, int index) { @@ -675,9 +682,9 @@ public final class SelectionActionModeHelper { if (mText == null || !mText.contentEquals(text)) { mText = text.toString(); } - mWordIterator.setText(mText); + mTokenIterator.setText(mText); mStartIndex = index; - mDelegate.logEvent(SelectionEvent.selectionStarted(0)); + mLogger.logSelectionStartedEvent(0); } catch (Exception e) { // Avoid crashes due to logging. Log.d(LOG_TAG, e.getMessage()); @@ -691,14 +698,14 @@ public final class SelectionActionModeHelper { Preconditions.checkArgumentInRange(end, start, mText.length(), "end"); int[] wordIndices = getWordDelta(start, end); if (selection != null) { - mDelegate.logEvent(SelectionEvent.selectionModified( - wordIndices[0], wordIndices[1], selection)); + mLogger.logSelectionModifiedEvent( + wordIndices[0], wordIndices[1], selection); } else if (classification != null) { - mDelegate.logEvent(SelectionEvent.selectionModified( - wordIndices[0], wordIndices[1], classification)); + mLogger.logSelectionModifiedEvent( + wordIndices[0], wordIndices[1], classification); } else { - mDelegate.logEvent(SelectionEvent.selectionModified( - wordIndices[0], wordIndices[1])); + mLogger.logSelectionModifiedEvent( + wordIndices[0], wordIndices[1]); } } catch (Exception e) { // Avoid crashes due to logging. @@ -715,11 +722,11 @@ public final class SelectionActionModeHelper { Preconditions.checkArgumentInRange(end, start, mText.length(), "end"); int[] wordIndices = getWordDelta(start, end); if (classification != null) { - mDelegate.logEvent(SelectionEvent.selectionAction( - wordIndices[0], wordIndices[1], action, classification)); + mLogger.logSelectionActionEvent( + wordIndices[0], wordIndices[1], action, classification); } else { - mDelegate.logEvent(SelectionEvent.selectionAction( - wordIndices[0], wordIndices[1], action)); + mLogger.logSelectionActionEvent( + wordIndices[0], wordIndices[1], action); } } catch (Exception e) { // Avoid crashes due to logging. @@ -742,10 +749,10 @@ public final class SelectionActionModeHelper { wordIndices[0] = countWordsBackward(start); // For the selection start index, avoid counting a partial word backwards. - if (!mWordIterator.isBoundary(start) + if (!mTokenIterator.isBoundary(start) && !isWhitespace( - mWordIterator.preceding(start), - mWordIterator.following(start))) { + mTokenIterator.preceding(start), + mTokenIterator.following(start))) { // We counted a partial word. Remove it. wordIndices[0]--; } @@ -767,7 +774,7 @@ public final class SelectionActionModeHelper { int wordCount = 0; int offset = from; while (offset > mStartIndex) { - int start = mWordIterator.preceding(offset); + int start = mTokenIterator.preceding(offset); if (!isWhitespace(start, offset)) { wordCount++; } @@ -781,7 +788,7 @@ public final class SelectionActionModeHelper { int wordCount = 0; int offset = from; while (offset < mStartIndex) { - int end = mWordIterator.following(offset); + int end = mTokenIterator.following(offset); if (!isWhitespace(offset, end)) { wordCount++; } @@ -1022,20 +1029,20 @@ public final class SelectionActionModeHelper { private static int getActionType(int menuItemId) { switch (menuItemId) { case TextView.ID_SELECT_ALL: - return SelectionEvent.ActionType.SELECT_ALL; + return SelectionEvent.ACTION_SELECT_ALL; case TextView.ID_CUT: - return SelectionEvent.ActionType.CUT; + return SelectionEvent.ACTION_CUT; case TextView.ID_COPY: - return SelectionEvent.ActionType.COPY; + return SelectionEvent.ACTION_COPY; case TextView.ID_PASTE: // fall through case TextView.ID_PASTE_AS_PLAIN_TEXT: - return SelectionEvent.ActionType.PASTE; + return SelectionEvent.ACTION_PASTE; case TextView.ID_SHARE: - return SelectionEvent.ActionType.SHARE; + return SelectionEvent.ACTION_SHARE; case TextView.ID_ASSIST: - return SelectionEvent.ActionType.SMART_SHARE; + return SelectionEvent.ACTION_SMART_SHARE; default: - return SelectionEvent.ActionType.OTHER; + return SelectionEvent.ACTION_OTHER; } } -- GitLab From ff5d60b268dfd848c236b62ae241370a68aae39f Mon Sep 17 00:00:00 2001 From: David Brazdil Date: Wed, 31 Jan 2018 07:36:35 +0000 Subject: [PATCH 158/416] Revert "Make AndroidRuntime only start the debugger for zygote forked apps." This reverts commit daf17d415c1a99c515ffa75f3ec3bb0fb87627fe. Reason for revert: Topic broke go/art-build. Reverting as ART Sheriff. Change-Id: I913dcb82532d448116b0c60d98a91b9b7442d5c9 --- core/jni/AndroidRuntime.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 3784d4daa2e..d7f725d03ed 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -761,17 +761,18 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) /* * Enable debugging only for apps forked from zygote. + * Set suspend=y to pause during VM init and use android ADB transport. */ if (zygote) { - // Set the JDWP provider and required arguments. By default let the runtime choose how JDWP is - // implemented. When this is not set the runtime defaults to not allowing JDWP. addOption("-XjdwpOptions:suspend=n,server=y"); - parseRuntimeOption("dalvik.vm.jdwp-provider", - jdwpProviderBuf, - "-XjdwpProvider:", - "default"); } + // Set the JDWP provider. By default let the runtime choose. + parseRuntimeOption("dalvik.vm.jdwp-provider", + jdwpProviderBuf, + "-XjdwpProvider:", + "default"); + parseRuntimeOption("dalvik.vm.lockprof.threshold", lockProfThresholdBuf, "-Xlockprofthreshold:"); -- GitLab From 09a02c3094d9ea2069203a9f5f56edf338d2a664 Mon Sep 17 00:00:00 2001 From: Emilian Peev Date: Tue, 30 Jan 2018 12:24:37 +0000 Subject: [PATCH 159/416] camera: Extend the multiple capture request requirements Additional requirements need to be met before submitting individual physical camera capture requests. Describe the specific details in the java docs. Bug: 72524603 Test: Successful build Change-Id: Ic8d37ab05a3ccd3b4b07a34824f421a71ca69ba0 --- core/java/android/hardware/camera2/CameraDevice.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 40ee8348ac4..df644012ffb 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -923,6 +923,14 @@ public abstract class CameraDevice implements AutoCloseable { * {@link CameraCaptureSession#setRepeatingRequest} or * {@link CameraCaptureSession#setRepeatingBurst}

    * + *

    Individual physical camera settings will only be honored for camera session + * that was initialiazed with corresponding physical camera id output configuration + * {@link OutputConfiguration#setPhysicalCameraId} and the same output targets are + * also attached in the request by {@link CaptureRequest.Builder#addTarget}.

    + * + *

    The output is undefined for any logical camera streams in case valid physical camera + * settings are attached.

    + * * @param templateType An enumeration selecting the use case for this request. Not all template * types are supported on every device. See the documentation for each template type for * details. -- GitLab From cc155ddc69efce0579118e873ae991cebc083ca6 Mon Sep 17 00:00:00 2001 From: Peeyush Agarwal Date: Wed, 10 Jan 2018 11:51:33 +0000 Subject: [PATCH 160/416] Add Ambient Brightness tracker API Test: atest com.android.server.display.AmbientBrightnessStatsTrackerTest && atest android.hardware.display.AmbientBrightnessDayStatsTest Bug: 69406079 Change-Id: I4b13c6bdd3e9fdded8086371f46dba0fd3102b98 --- .../display/AmbientBrightnessDayStats.aidl | 19 + .../display/AmbientBrightnessDayStats.java | 196 ++++++++ .../hardware/display/DisplayManager.java | 10 + .../display/DisplayManagerGlobal.java | 15 + .../display/DisplayManagerInternal.java | 4 +- .../hardware/display/IDisplayManager.aidl | 3 + core/res/AndroidManifest.xml | 7 + .../AmbientBrightnessDayStatsTest.java | 103 ++++ .../AmbientBrightnessStatsTracker.java | 363 ++++++++++++++ .../server/display/BrightnessIdleJob.java | 2 +- .../server/display/BrightnessTracker.java | 94 +++- .../server/display/DisplayManagerService.java | 23 +- .../display/DisplayPowerController.java | 16 +- .../AmbientBrightnessStatsTrackerTest.java | 443 ++++++++++++++++++ .../server/display/BrightnessTrackerTest.java | 2 +- 15 files changed, 1281 insertions(+), 19 deletions(-) create mode 100644 core/java/android/hardware/display/AmbientBrightnessDayStats.aidl create mode 100644 core/java/android/hardware/display/AmbientBrightnessDayStats.java create mode 100644 core/tests/coretests/src/android/hardware/display/AmbientBrightnessDayStatsTest.java create mode 100644 services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java create mode 100644 services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java diff --git a/core/java/android/hardware/display/AmbientBrightnessDayStats.aidl b/core/java/android/hardware/display/AmbientBrightnessDayStats.aidl new file mode 100644 index 00000000000..9070777bab6 --- /dev/null +++ b/core/java/android/hardware/display/AmbientBrightnessDayStats.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.hardware.display; + +parcelable AmbientBrightnessDayStats; diff --git a/core/java/android/hardware/display/AmbientBrightnessDayStats.java b/core/java/android/hardware/display/AmbientBrightnessDayStats.java new file mode 100644 index 00000000000..41be397cabc --- /dev/null +++ b/core/java/android/hardware/display/AmbientBrightnessDayStats.java @@ -0,0 +1,196 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.hardware.display; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.time.LocalDate; +import java.util.Arrays; + +/** + * AmbientBrightnessDayStats stores and manipulates brightness stats over a single day. + * {@see DisplayManager.getAmbientBrightnessStats()} + * TODO: Make this system API + * + * @hide + */ +public class AmbientBrightnessDayStats implements Parcelable { + + /** The localdate for which brightness stats are being tracked */ + private final LocalDate mLocalDate; + + /** Ambient brightness values for creating bucket boundaries from */ + private final float[] mBucketBoundaries; + + /** Stats of how much time (in seconds) was spent in each of the buckets */ + private final float[] mStats; + + /** + * @hide + */ + public AmbientBrightnessDayStats(@NonNull LocalDate localDate, + @NonNull float[] bucketBoundaries) { + Preconditions.checkNotNull(localDate); + Preconditions.checkNotNull(bucketBoundaries); + int numBuckets = bucketBoundaries.length; + if (numBuckets < 1) { + throw new IllegalArgumentException("Bucket boundaries must contain at least 1 value"); + } + mLocalDate = localDate; + mBucketBoundaries = bucketBoundaries; + mStats = new float[numBuckets]; + } + + /** + * @hide + */ + public AmbientBrightnessDayStats(@NonNull LocalDate localDate, + @NonNull float[] bucketBoundaries, @NonNull float[] stats) { + Preconditions.checkNotNull(localDate); + Preconditions.checkNotNull(bucketBoundaries); + Preconditions.checkNotNull(stats); + if (bucketBoundaries.length < 1) { + throw new IllegalArgumentException("Bucket boundaries must contain at least 1 value"); + } + if (bucketBoundaries.length != stats.length) { + throw new IllegalArgumentException("Bucket boundaries and stats must be of same size."); + } + mLocalDate = localDate; + mBucketBoundaries = bucketBoundaries; + mStats = stats; + } + + public LocalDate getLocalDate() { + return mLocalDate; + } + + public float[] getStats() { + return mStats; + } + + public float[] getBucketBoundaries() { + return mBucketBoundaries; + } + + private AmbientBrightnessDayStats(Parcel source) { + mLocalDate = LocalDate.parse(source.readString()); + mBucketBoundaries = source.createFloatArray(); + mStats = source.createFloatArray(); + } + + public static final Creator CREATOR = + new Creator() { + + @Override + public AmbientBrightnessDayStats createFromParcel(Parcel source) { + return new AmbientBrightnessDayStats(source); + } + + @Override + public AmbientBrightnessDayStats[] newArray(int size) { + return new AmbientBrightnessDayStats[size]; + } + }; + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + AmbientBrightnessDayStats other = (AmbientBrightnessDayStats) obj; + return mLocalDate.equals(other.mLocalDate) && Arrays.equals(mBucketBoundaries, + other.mBucketBoundaries) && Arrays.equals(mStats, other.mStats); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = result * prime + mLocalDate.hashCode(); + result = result * prime + Arrays.hashCode(mBucketBoundaries); + result = result * prime + Arrays.hashCode(mStats); + return result; + } + + @Override + public String toString() { + StringBuilder bucketBoundariesString = new StringBuilder(); + StringBuilder statsString = new StringBuilder(); + for (int i = 0; i < mBucketBoundaries.length; i++) { + if (i != 0) { + bucketBoundariesString.append(", "); + statsString.append(", "); + } + bucketBoundariesString.append(mBucketBoundaries[i]); + statsString.append(mStats[i]); + } + return new StringBuilder() + .append(mLocalDate).append(" ") + .append("{").append(bucketBoundariesString).append("} ") + .append("{").append(statsString).append("}").toString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mLocalDate.toString()); + dest.writeFloatArray(mBucketBoundaries); + dest.writeFloatArray(mStats); + } + + /** @hide */ + public void log(float ambientBrightness, float durationSec) { + int bucketIndex = getBucketIndex(ambientBrightness); + if (bucketIndex >= 0) { + mStats[bucketIndex] += durationSec; + } + } + + private int getBucketIndex(float ambientBrightness) { + if (ambientBrightness < mBucketBoundaries[0]) { + return -1; + } + int low = 0; + int high = mBucketBoundaries.length - 1; + while (low < high) { + int mid = (low + high) / 2; + if (mBucketBoundaries[mid] <= ambientBrightness + && ambientBrightness < mBucketBoundaries[mid + 1]) { + return mid; + } else if (mBucketBoundaries[mid] < ambientBrightness) { + low = mid + 1; + } else if (mBucketBoundaries[mid] > ambientBrightness) { + high = mid - 1; + } + } + return low; + } +} diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 4de4880b7c1..22fb8e75289 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -630,6 +630,16 @@ public final class DisplayManager { return mGlobal.getBrightnessEvents(mContext.getOpPackageName()); } + /** + * Fetch {@link AmbientBrightnessDayStats}s. + * + * @hide until we make it a system api + */ + @RequiresPermission(Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS) + public List getAmbientBrightnessStats() { + return mGlobal.getAmbientBrightnessStats(); + } + /** * Sets the global display brightness configuration. * diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index 2d5f5e04148..d7f7c865b8f 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -525,6 +525,21 @@ public final class DisplayManagerGlobal { } } + /** + * Retrieves ambient brightness stats. + */ + public List getAmbientBrightnessStats() { + try { + ParceledListSlice stats = mDm.getAmbientBrightnessStats(); + if (stats == null) { + return Collections.emptyList(); + } + return stats.getList(); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub { @Override public void onDisplayEvent(int displayId, int event) { diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 1cfad4f0168..f468942cc95 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -174,9 +174,9 @@ public abstract class DisplayManagerInternal { public abstract boolean isUidPresentOnDisplay(int uid, int displayId); /** - * Persist brightness slider events. + * Persist brightness slider events and ambient brightness stats. */ - public abstract void persistBrightnessSliderEvents(); + public abstract void persistBrightnessTrackerState(); /** * Notifies the display manager that resource overlays have changed. diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl index 13599cfa0b7..0571ae1fe82 100644 --- a/core/java/android/hardware/display/IDisplayManager.aidl +++ b/core/java/android/hardware/display/IDisplayManager.aidl @@ -87,6 +87,9 @@ interface IDisplayManager { // Requires BRIGHTNESS_SLIDER_USAGE permission. ParceledListSlice getBrightnessEvents(String callingPackage); + // Requires ACCESS_AMBIENT_LIGHT_STATS permission. + ParceledListSlice getAmbientBrightnessStats(); + // Sets the global brightness configuration for a given user. Requires // CONFIGURE_DISPLAY_BRIGHTNESS, and INTERACT_ACROSS_USER if the user being configured is not // the same as the calling user. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b23a64b9f38..4598b388cd6 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3012,6 +3012,13 @@ + + + diff --git a/core/tests/coretests/src/android/hardware/display/AmbientBrightnessDayStatsTest.java b/core/tests/coretests/src/android/hardware/display/AmbientBrightnessDayStatsTest.java new file mode 100644 index 00000000000..84409d48a4d --- /dev/null +++ b/core/tests/coretests/src/android/hardware/display/AmbientBrightnessDayStatsTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.hardware.display; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.os.Parcel; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.time.LocalDate; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AmbientBrightnessDayStatsTest { + + @Test + public void testAmbientBrightnessDayStatsAdd() { + AmbientBrightnessDayStats dayStats = new AmbientBrightnessDayStats(LocalDate.now(), + new float[]{0, 1, 10, 100}); + dayStats.log(0, 1); + dayStats.log(0.5f, 1.5f); + dayStats.log(50, 12.5f); + dayStats.log(2000, 1.24f); + dayStats.log(-10, 0.5f); + assertEquals(2.5f, dayStats.getStats()[0], 0); + assertEquals(0, dayStats.getStats()[1], 0); + assertEquals(12.5f, dayStats.getStats()[2], 0); + assertEquals(1.24f, dayStats.getStats()[3], 0); + } + + @Test + public void testAmbientBrightnessDayStatsEquals() { + LocalDate today = LocalDate.now(); + AmbientBrightnessDayStats dayStats1 = new AmbientBrightnessDayStats(today, + new float[]{0, 1, 10, 100}); + AmbientBrightnessDayStats dayStats2 = new AmbientBrightnessDayStats(today, + new float[]{0, 1, 10, 100}, new float[4]); + AmbientBrightnessDayStats dayStats3 = new AmbientBrightnessDayStats(today, + new float[]{0, 1, 10, 100}, new float[]{1, 3, 5, 7}); + AmbientBrightnessDayStats dayStats4 = new AmbientBrightnessDayStats(today, + new float[]{0, 1, 10, 100}, new float[]{1, 3, 5, 0}); + assertEquals(dayStats1, dayStats2); + assertEquals(dayStats1.hashCode(), dayStats2.hashCode()); + assertNotEquals(dayStats1, dayStats3); + assertNotEquals(dayStats1.hashCode(), dayStats3.hashCode()); + dayStats4.log(100, 7); + assertEquals(dayStats3, dayStats4); + assertEquals(dayStats3.hashCode(), dayStats4.hashCode()); + } + + @Test + public void testAmbientBrightnessDayStatsIncorrectInit() { + try { + new AmbientBrightnessDayStats(LocalDate.now(), new float[]{1, 10, 100}, + new float[]{1, 5, 6, 7}); + } catch (IllegalArgumentException e) { + // Expected + } + try { + new AmbientBrightnessDayStats(LocalDate.now(), new float[]{}); + } catch (IllegalArgumentException e) { + // Expected + } + } + + @Test + public void testParcelUnparcelAmbientBrightnessDayStats() { + LocalDate today = LocalDate.now(); + AmbientBrightnessDayStats stats = new AmbientBrightnessDayStats(today, + new float[]{0, 1, 10, 100}, new float[]{1.3f, 2.6f, 5.8f, 10}); + // Parcel the data + Parcel parcel = Parcel.obtain(); + stats.writeToParcel(parcel, 0); + byte[] parceled = parcel.marshall(); + parcel.recycle(); + // Unparcel and check that it has not changed + parcel = Parcel.obtain(); + parcel.unmarshall(parceled, 0, parceled.length); + parcel.setDataPosition(0); + AmbientBrightnessDayStats statsAgain = AmbientBrightnessDayStats.CREATOR.createFromParcel( + parcel); + assertEquals(stats, statsAgain); + } +} diff --git a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java new file mode 100644 index 00000000000..6e571bd7594 --- /dev/null +++ b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java @@ -0,0 +1,363 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.display; + +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.hardware.display.AmbientBrightnessDayStats; +import android.os.SystemClock; +import android.os.UserManager; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FastXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.Map; + +/** + * Class that stores stats of ambient brightness regions as histogram. + */ +public class AmbientBrightnessStatsTracker { + + private static final String TAG = "AmbientBrightnessStatsTracker"; + private static final boolean DEBUG = false; + + @VisibleForTesting + static final float[] BUCKET_BOUNDARIES_FOR_NEW_STATS = + {0, 0.1f, 0.3f, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000}; + @VisibleForTesting + static final int MAX_DAYS_TO_TRACK = 7; + + private final AmbientBrightnessStats mAmbientBrightnessStats; + private final Timer mTimer; + private final Injector mInjector; + private final UserManager mUserManager; + private float mCurrentAmbientBrightness; + private @UserIdInt int mCurrentUserId; + + public AmbientBrightnessStatsTracker(UserManager userManager, @Nullable Injector injector) { + mUserManager = userManager; + if (injector != null) { + mInjector = injector; + } else { + mInjector = new Injector(); + } + mAmbientBrightnessStats = new AmbientBrightnessStats(); + mTimer = new Timer(() -> mInjector.elapsedRealtimeMillis()); + mCurrentAmbientBrightness = -1; + } + + public synchronized void start() { + mTimer.reset(); + mTimer.start(); + } + + public synchronized void stop() { + if (mTimer.isRunning()) { + mAmbientBrightnessStats.log(mCurrentUserId, mInjector.getLocalDate(), + mCurrentAmbientBrightness, mTimer.totalDurationSec()); + } + mTimer.reset(); + mCurrentAmbientBrightness = -1; + } + + public synchronized void add(@UserIdInt int userId, float newAmbientBrightness) { + if (mTimer.isRunning()) { + if (userId == mCurrentUserId) { + mAmbientBrightnessStats.log(mCurrentUserId, mInjector.getLocalDate(), + mCurrentAmbientBrightness, mTimer.totalDurationSec()); + } else { + if (DEBUG) { + Slog.v(TAG, "User switched since last sensor event."); + } + mCurrentUserId = userId; + } + mTimer.reset(); + mTimer.start(); + mCurrentAmbientBrightness = newAmbientBrightness; + } else { + if (DEBUG) { + Slog.e(TAG, "Timer not running while trying to add brightness stats."); + } + } + } + + public synchronized void writeStats(OutputStream stream) throws IOException { + mAmbientBrightnessStats.writeToXML(stream); + } + + public synchronized void readStats(InputStream stream) throws IOException { + mAmbientBrightnessStats.readFromXML(stream); + } + + public synchronized ArrayList getUserStats(int userId) { + return mAmbientBrightnessStats.getUserStats(userId); + } + + public synchronized void dump(PrintWriter pw) { + pw.println("AmbientBrightnessStats:"); + pw.print(mAmbientBrightnessStats); + } + + /** + * AmbientBrightnessStats tracks ambient brightness stats across users over multiple days. + * This class is not ThreadSafe. + */ + class AmbientBrightnessStats { + + private static final String TAG_AMBIENT_BRIGHTNESS_STATS = "ambient-brightness-stats"; + private static final String TAG_AMBIENT_BRIGHTNESS_DAY_STATS = + "ambient-brightness-day-stats"; + private static final String ATTR_USER = "user"; + private static final String ATTR_LOCAL_DATE = "local-date"; + private static final String ATTR_BUCKET_BOUNDARIES = "bucket-boundaries"; + private static final String ATTR_BUCKET_STATS = "bucket-stats"; + + private Map> mStats; + + public AmbientBrightnessStats() { + mStats = new HashMap<>(); + } + + public void log(@UserIdInt int userId, LocalDate localDate, float ambientBrightness, + float durationSec) { + Deque userStats = getOrCreateUserStats(mStats, userId); + AmbientBrightnessDayStats dayStats = getOrCreateDayStats(userStats, localDate); + dayStats.log(ambientBrightness, durationSec); + } + + public ArrayList getUserStats(@UserIdInt int userId) { + if (mStats.containsKey(userId)) { + return new ArrayList<>(mStats.get(userId)); + } else { + return null; + } + } + + public void writeToXML(OutputStream stream) throws IOException { + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(stream, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + + final LocalDate cutOffDate = mInjector.getLocalDate().minusDays(MAX_DAYS_TO_TRACK); + out.startTag(null, TAG_AMBIENT_BRIGHTNESS_STATS); + for (Map.Entry> entry : mStats.entrySet()) { + for (AmbientBrightnessDayStats userDayStats : entry.getValue()) { + int userSerialNumber = mInjector.getUserSerialNumber(mUserManager, + entry.getKey()); + if (userSerialNumber != -1 && userDayStats.getLocalDate().isAfter(cutOffDate)) { + out.startTag(null, TAG_AMBIENT_BRIGHTNESS_DAY_STATS); + out.attribute(null, ATTR_USER, Integer.toString(userSerialNumber)); + out.attribute(null, ATTR_LOCAL_DATE, + userDayStats.getLocalDate().toString()); + StringBuilder bucketBoundariesValues = new StringBuilder(); + StringBuilder timeSpentValues = new StringBuilder(); + for (int i = 0; i < userDayStats.getBucketBoundaries().length; i++) { + if (i > 0) { + bucketBoundariesValues.append(","); + timeSpentValues.append(","); + } + bucketBoundariesValues.append(userDayStats.getBucketBoundaries()[i]); + timeSpentValues.append(userDayStats.getStats()[i]); + } + out.attribute(null, ATTR_BUCKET_BOUNDARIES, + bucketBoundariesValues.toString()); + out.attribute(null, ATTR_BUCKET_STATS, timeSpentValues.toString()); + out.endTag(null, TAG_AMBIENT_BRIGHTNESS_DAY_STATS); + } + } + } + out.endTag(null, TAG_AMBIENT_BRIGHTNESS_STATS); + out.endDocument(); + stream.flush(); + } + + public void readFromXML(InputStream stream) throws IOException { + try { + Map> parsedStats = new HashMap<>(); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, StandardCharsets.UTF_8.name()); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + } + String tag = parser.getName(); + if (!TAG_AMBIENT_BRIGHTNESS_STATS.equals(tag)) { + throw new XmlPullParserException( + "Ambient brightness stats not found in tracker file " + tag); + } + + final LocalDate cutOffDate = mInjector.getLocalDate().minusDays(MAX_DAYS_TO_TRACK); + parser.next(); + int outerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + tag = parser.getName(); + if (TAG_AMBIENT_BRIGHTNESS_DAY_STATS.equals(tag)) { + String userSerialNumber = parser.getAttributeValue(null, ATTR_USER); + LocalDate localDate = LocalDate.parse( + parser.getAttributeValue(null, ATTR_LOCAL_DATE)); + String[] bucketBoundaries = parser.getAttributeValue(null, + ATTR_BUCKET_BOUNDARIES).split(","); + String[] bucketStats = parser.getAttributeValue(null, + ATTR_BUCKET_STATS).split(","); + if (bucketBoundaries.length != bucketStats.length + || bucketBoundaries.length < 1) { + throw new IOException("Invalid brightness stats string."); + } + float[] parsedBucketBoundaries = new float[bucketBoundaries.length]; + float[] parsedBucketStats = new float[bucketStats.length]; + for (int i = 0; i < bucketBoundaries.length; i++) { + parsedBucketBoundaries[i] = Float.parseFloat(bucketBoundaries[i]); + parsedBucketStats[i] = Float.parseFloat(bucketStats[i]); + } + int userId = mInjector.getUserId(mUserManager, + Integer.parseInt(userSerialNumber)); + if (userId != -1 && localDate.isAfter(cutOffDate)) { + Deque userStats = getOrCreateUserStats( + parsedStats, userId); + userStats.offer( + new AmbientBrightnessDayStats(localDate, + parsedBucketBoundaries, parsedBucketStats)); + } + } + } + mStats = parsedStats; + } catch (NullPointerException | NumberFormatException | XmlPullParserException | + DateTimeParseException | IOException e) { + throw new IOException("Failed to parse brightness stats file.", e); + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for (Map.Entry> entry : mStats.entrySet()) { + for (AmbientBrightnessDayStats dayStats : entry.getValue()) { + builder.append(" "); + builder.append(entry.getKey()).append(" "); + builder.append(dayStats).append("\n"); + } + } + return builder.toString(); + } + + private Deque getOrCreateUserStats( + Map> stats, @UserIdInt int userId) { + if (!stats.containsKey(userId)) { + stats.put(userId, new ArrayDeque<>()); + } + return stats.get(userId); + } + + private AmbientBrightnessDayStats getOrCreateDayStats( + Deque userStats, LocalDate localDate) { + AmbientBrightnessDayStats lastBrightnessStats = userStats.peekLast(); + if (lastBrightnessStats != null && lastBrightnessStats.getLocalDate().equals( + localDate)) { + return lastBrightnessStats; + } else { + AmbientBrightnessDayStats dayStats = new AmbientBrightnessDayStats(localDate, + BUCKET_BOUNDARIES_FOR_NEW_STATS); + if (userStats.size() == MAX_DAYS_TO_TRACK) { + userStats.poll(); + } + userStats.offer(dayStats); + return dayStats; + } + } + } + + @VisibleForTesting + interface Clock { + long elapsedTimeMillis(); + } + + @VisibleForTesting + static class Timer { + + private final Clock clock; + private long startTimeMillis; + private boolean started; + + public Timer(Clock clock) { + this.clock = clock; + } + + public void reset() { + started = false; + } + + public void start() { + if (!started) { + startTimeMillis = clock.elapsedTimeMillis(); + started = true; + } + } + + public boolean isRunning() { + return started; + } + + public float totalDurationSec() { + if (started) { + return (float) ((clock.elapsedTimeMillis() - startTimeMillis) / 1000.0); + } + return 0; + } + } + + @VisibleForTesting + static class Injector { + public long elapsedRealtimeMillis() { + return SystemClock.elapsedRealtime(); + } + + public int getUserSerialNumber(UserManager userManager, int userId) { + return userManager.getUserSerialNumber(userId); + } + + public int getUserId(UserManager userManager, int userSerialNumber) { + return userManager.getUserHandle(userSerialNumber); + } + + public LocalDate getLocalDate() { + return LocalDate.now(); + } + } +} \ No newline at end of file diff --git a/services/core/java/com/android/server/display/BrightnessIdleJob.java b/services/core/java/com/android/server/display/BrightnessIdleJob.java index 876acf45fda..b0a41cb589e 100644 --- a/services/core/java/com/android/server/display/BrightnessIdleJob.java +++ b/services/core/java/com/android/server/display/BrightnessIdleJob.java @@ -70,7 +70,7 @@ public class BrightnessIdleJob extends JobService { Slog.d(BrightnessTracker.TAG, "Scheduled write of brightness events"); } DisplayManagerInternal dmi = LocalServices.getService(DisplayManagerInternal.class); - dmi.persistBrightnessSliderEvents(); + dmi.persistBrightnessTrackerState(); return false; } diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index bcf8bfe6aaa..ac76faef3cc 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -17,6 +17,7 @@ package com.android.server.display; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -28,8 +29,8 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.display.AmbientBrightnessDayStats; import android.hardware.display.BrightnessChangeEvent; -import android.net.Uri; import android.os.BatteryManager; import android.os.Environment; import android.os.Handler; @@ -81,6 +82,7 @@ public class BrightnessTracker { static final boolean DEBUG = false; private static final String EVENTS_FILE = "brightness_events.xml"; + private static final String AMBIENT_BRIGHTNESS_STATS_FILE = "ambient_brightness_stats.xml"; private static final int MAX_EVENTS = 100; // Discard events when reading or writing that are older than this. private static final long MAX_EVENT_AGE = TimeUnit.DAYS.toMillis(30); @@ -113,6 +115,10 @@ public class BrightnessTracker { private final Runnable mEventsWriter = () -> writeEvents(); private volatile boolean mWriteEventsScheduled; + private AmbientBrightnessStatsTracker mAmbientBrightnessStatsTracker; + private final Runnable mAmbientBrightnessStatsWriter = () -> writeAmbientBrightnessStats(); + private volatile boolean mWriteBrightnessStatsScheduled; + private UserManager mUserManager; private final Context mContext; private final ContentResolver mContentResolver; @@ -120,6 +126,7 @@ public class BrightnessTracker { // mBroadcastReceiver and mSensorListener should only be used on the mBgHandler thread. private BroadcastReceiver mBroadcastReceiver; private SensorListener mSensorListener; + private @UserIdInt int mCurrentUserId = UserHandle.USER_NULL; // Lock held while collecting data related to brightness changes. private final Object mDataCollectionLock = new Object(); @@ -157,12 +164,19 @@ public class BrightnessTracker { } mBgHandler = new TrackerHandler(mInjector.getBackgroundHandler().getLooper()); mUserManager = mContext.getSystemService(UserManager.class); - + try { + final ActivityManager.StackInfo focusedStack = mInjector.getFocusedStack(); + mCurrentUserId = focusedStack.userId; + } catch (RemoteException e) { + // Really shouldn't be possible. + return; + } mBgHandler.obtainMessage(MSG_BACKGROUND_START, (Float) initialBrightness).sendToTarget(); } private void backgroundStart(float initialBrightness) { readEvents(); + readAmbientBrightnessStats(); mSensorListener = new SensorListener(); @@ -196,12 +210,20 @@ public class BrightnessTracker { mInjector.unregisterSensorListener(mContext, mSensorListener); mInjector.unregisterReceiver(mContext, mBroadcastReceiver); mInjector.cancelIdleJob(mContext); + mAmbientBrightnessStatsTracker.stop(); synchronized (mDataCollectionLock) { mStarted = false; } } + public void onSwitchUser(@UserIdInt int newUserId) { + if (DEBUG) { + Slog.d(TAG, "Used id updated from " + mCurrentUserId + " to " + newUserId); + } + mCurrentUserId = newUserId; + } + /** * @param userId userId to fetch data for. * @param includePackage if false we will null out BrightnessChangeEvent.packageName @@ -228,8 +250,8 @@ public class BrightnessTracker { return new ParceledListSlice<>(out); } - public void persistEvents() { - scheduleWriteEvents(); + public void persistBrightnessTrackerState() { + scheduleWriteBrightnessTrackerState(); } /** @@ -321,11 +343,15 @@ public class BrightnessTracker { } } - private void scheduleWriteEvents() { + private void scheduleWriteBrightnessTrackerState() { if (!mWriteEventsScheduled) { mBgHandler.post(mEventsWriter); mWriteEventsScheduled = true; } + if (!mWriteBrightnessStatsScheduled) { + mBgHandler.post(mAmbientBrightnessStatsWriter); + mWriteBrightnessStatsScheduled = true; + } } private void writeEvents() { @@ -336,7 +362,7 @@ public class BrightnessTracker { return; } - final AtomicFile writeTo = mInjector.getFile(); + final AtomicFile writeTo = mInjector.getFile(EVENTS_FILE); if (writeTo == null) { return; } @@ -360,12 +386,29 @@ public class BrightnessTracker { } } + private void writeAmbientBrightnessStats() { + mWriteBrightnessStatsScheduled = false; + final AtomicFile writeTo = mInjector.getFile(AMBIENT_BRIGHTNESS_STATS_FILE); + if (writeTo == null) { + return; + } + FileOutputStream output = null; + try { + output = writeTo.startWrite(); + mAmbientBrightnessStatsTracker.writeStats(output); + writeTo.finishWrite(output); + } catch (IOException e) { + writeTo.failWrite(output); + Slog.e(TAG, "Failed to write ambient brightness stats.", e); + } + } + private void readEvents() { synchronized (mEventsLock) { // Read might prune events so mark as dirty. mEventsDirty = true; mEvents.clear(); - final AtomicFile readFrom = mInjector.getFile(); + final AtomicFile readFrom = mInjector.getFile(EVENTS_FILE); if (readFrom != null && readFrom.exists()) { FileInputStream input = null; try { @@ -381,6 +424,23 @@ public class BrightnessTracker { } } + private void readAmbientBrightnessStats() { + mAmbientBrightnessStatsTracker = new AmbientBrightnessStatsTracker(mUserManager, null); + final AtomicFile readFrom = mInjector.getFile(AMBIENT_BRIGHTNESS_STATS_FILE); + if (readFrom != null && readFrom.exists()) { + FileInputStream input = null; + try { + input = readFrom.openRead(); + mAmbientBrightnessStatsTracker.readStats(input); + } catch (IOException e) { + readFrom.delete(); + Slog.e(TAG, "Failed to read ambient brightness stats.", e); + } finally { + IoUtils.closeQuietly(input); + } + } + } + @VisibleForTesting @GuardedBy("mEventsLock") void writeEventsLocked(OutputStream stream) throws IOException { @@ -545,6 +605,13 @@ public class BrightnessTracker { pw.println("}"); } } + if (mAmbientBrightnessStatsTracker != null) { + mAmbientBrightnessStatsTracker.dump(pw); + } + } + + public ParceledListSlice getAmbientBrightnessStats(int userId) { + return new ParceledListSlice<>(mAmbientBrightnessStatsTracker.getUserStats(userId)); } // Not allowed to keep the SensorEvent so used to copy the data we care about. @@ -584,6 +651,10 @@ public class BrightnessTracker { } } + private void recordAmbientBrightnessStats(SensorEvent event) { + mAmbientBrightnessStatsTracker.add(mCurrentUserId, event.values[0]); + } + private void batteryLevelChanged(int level, int scale) { synchronized (mDataCollectionLock) { mLastBatteryLevel = (float) level / (float) scale; @@ -594,6 +665,7 @@ public class BrightnessTracker { @Override public void onSensorChanged(SensorEvent event) { recordSensorEvent(event); + recordAmbientBrightnessStats(event); } @Override @@ -611,7 +683,7 @@ public class BrightnessTracker { String action = intent.getAction(); if (Intent.ACTION_SHUTDOWN.equals(action)) { stop(); - scheduleWriteEvents(); + scheduleWriteBrightnessTrackerState(); } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) { int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0); @@ -619,8 +691,10 @@ public class BrightnessTracker { batteryLevelChanged(level, scale); } } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { + mAmbientBrightnessStatsTracker.stop(); mInjector.unregisterSensorListener(mContext, mSensorListener); } else if (Intent.ACTION_SCREEN_ON.equals(action)) { + mAmbientBrightnessStatsTracker.start(); mInjector.registerSensorListener(mContext, mSensorListener, mInjector.getBackgroundHandler()); } @@ -679,8 +753,8 @@ public class BrightnessTracker { return Settings.Secure.getIntForUser(resolver, setting, defaultValue, userId); } - public AtomicFile getFile() { - return new AtomicFile(new File(Environment.getDataSystemDeDirectory(), EVENTS_FILE)); + public AtomicFile getFile(String filename) { + return new AtomicFile(new File(Environment.getDataSystemDeDirectory(), filename)); } public long currentTimeMillis() { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 0c2ff051961..a5c1fe299e4 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -38,6 +38,7 @@ import android.content.pm.ParceledListSlice; import android.content.res.Resources; import android.graphics.Point; import android.hardware.SensorManager; +import android.hardware.display.AmbientBrightnessDayStats; import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.DisplayManagerGlobal; @@ -359,6 +360,7 @@ public final class DisplayManagerService extends SystemService { mPersistentDataStore.getBrightnessConfiguration(userSerial); mDisplayPowerController.setBrightnessConfiguration(config); } + mDisplayPowerController.onSwitchUser(newUserId); } } @@ -1834,6 +1836,23 @@ public final class DisplayManagerService extends SystemService { } } + @Override // Binder call + public ParceledListSlice getAmbientBrightnessStats() { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS, + "Permission required to to access ambient light stats."); + final int callingUid = Binder.getCallingUid(); + final int userId = UserHandle.getUserId(callingUid); + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + return mDisplayPowerController.getAmbientBrightnessStats(userId); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override // Binder call public void setBrightnessConfigurationForUser( BrightnessConfiguration c, @UserIdInt int userId, String packageName) { @@ -2039,9 +2058,9 @@ public final class DisplayManagerService extends SystemService { } @Override - public void persistBrightnessSliderEvents() { + public void persistBrightnessTrackerState() { synchronized (mSyncRoot) { - mDisplayPowerController.persistBrightnessSliderEvents(); + mDisplayPowerController.persistBrightnessTrackerState(); } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 056c3e641c4..f2a7d8171b6 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -34,6 +34,7 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.display.AmbientBrightnessDayStats; import android.hardware.display.BrightnessChangeEvent; import android.hardware.display.BrightnessConfiguration; import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; @@ -498,11 +499,20 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return mBrightnessTracker.getEvents(userId, includePackage); } + public void onSwitchUser(@UserIdInt int newUserId) { + mBrightnessTracker.onSwitchUser(newUserId); + } + + public ParceledListSlice getAmbientBrightnessStats( + @UserIdInt int userId) { + return mBrightnessTracker.getAmbientBrightnessStats(userId); + } + /** - * Persist the brightness slider events to disk. + * Persist the brightness slider events and ambient brightness stats to disk. */ - public void persistBrightnessSliderEvents() { - mBrightnessTracker.persistEvents(); + public void persistBrightnessTrackerState() { + mBrightnessTracker.persistBrightnessTrackerState(); } /** diff --git a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java new file mode 100644 index 00000000000..8502e69dd06 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java @@ -0,0 +1,443 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.display; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.hardware.display.AmbientBrightnessDayStats; +import android.os.SystemClock; +import android.os.UserManager; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.util.ArrayList; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AmbientBrightnessStatsTrackerTest { + + private TestInjector mTestInjector; + + @Before + public void setUp() { + mTestInjector = new TestInjector(); + } + + @Test + public void testBrightnessStatsTrackerOverSingleDay() { + AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker(); + ArrayList userStats; + float[] expectedStats; + // Test case where no user data + userStats = statsTracker.getUserStats(0); + assertNull(userStats); + // Test after adding some user data + statsTracker.start(); + statsTracker.add(0, 0); + mTestInjector.incrementTime(1000); + statsTracker.stop(); + userStats = statsTracker.getUserStats(0); + assertEquals(1, userStats.size()); + assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate()); + expectedStats = getEmptyStatsArray(); + expectedStats[0] = 1; + assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0); + // Test after adding some more user data + statsTracker.start(); + statsTracker.add(0, 0.05f); + mTestInjector.incrementTime(1000); + statsTracker.add(0, 0.2f); + mTestInjector.incrementTime(1500); + statsTracker.add(0, 50000); + mTestInjector.incrementTime(2500); + statsTracker.stop(); + userStats = statsTracker.getUserStats(0); + assertEquals(1, userStats.size()); + assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate()); + expectedStats = getEmptyStatsArray(); + expectedStats[0] = 2; + expectedStats[1] = 1.5f; + expectedStats[11] = 2.5f; + assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0); + } + + @Test + public void testBrightnessStatsTrackerOverMultipleDays() { + AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker(); + ArrayList userStats; + float[] expectedStats; + // Add data for day 1 + statsTracker.start(); + statsTracker.add(0, 0.05f); + mTestInjector.incrementTime(1000); + statsTracker.add(0, 0.2f); + mTestInjector.incrementTime(1500); + statsTracker.add(0, 1); + mTestInjector.incrementTime(2500); + statsTracker.stop(); + // Add data for day 2 + mTestInjector.incrementDate(1); + statsTracker.start(); + statsTracker.add(0, 0); + mTestInjector.incrementTime(3500); + statsTracker.add(0, 5); + mTestInjector.incrementTime(5000); + statsTracker.stop(); + // Test that the data is tracked as expected + userStats = statsTracker.getUserStats(0); + assertEquals(2, userStats.size()); + assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate()); + expectedStats = getEmptyStatsArray(); + expectedStats[0] = 1; + expectedStats[1] = 1.5f; + expectedStats[3] = 2.5f; + assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0); + assertEquals(mTestInjector.getLocalDate(), userStats.get(1).getLocalDate()); + expectedStats = getEmptyStatsArray(); + expectedStats[0] = 3.5f; + expectedStats[4] = 5; + assertArrayEquals(expectedStats, userStats.get(1).getStats(), 0); + } + + @Test + public void testBrightnessStatsTrackerOverMultipleUsers() { + AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker(); + ArrayList userStats; + float[] expectedStats; + // Add data for user 1 + statsTracker.start(); + statsTracker.add(0, 0.05f); + mTestInjector.incrementTime(1000); + statsTracker.add(0, 0.2f); + mTestInjector.incrementTime(1500); + statsTracker.add(0, 1); + mTestInjector.incrementTime(2500); + statsTracker.stop(); + // Add data for user 2 + mTestInjector.incrementDate(1); + statsTracker.start(); + statsTracker.add(1, 0); + mTestInjector.incrementTime(3500); + statsTracker.add(1, 5); + mTestInjector.incrementTime(5000); + statsTracker.stop(); + // Test that the data is tracked as expected + userStats = statsTracker.getUserStats(0); + assertEquals(1, userStats.size()); + assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate()); + expectedStats = getEmptyStatsArray(); + expectedStats[0] = 1; + expectedStats[1] = 1.5f; + expectedStats[3] = 2.5f; + assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0); + userStats = statsTracker.getUserStats(1); + assertEquals(1, userStats.size()); + assertEquals(mTestInjector.getLocalDate(), userStats.get(0).getLocalDate()); + expectedStats = getEmptyStatsArray(); + expectedStats[0] = 3.5f; + expectedStats[4] = 5; + assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0); + } + + @Test + public void testBrightnessStatsTrackerOverMaxDays() { + AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker(); + ArrayList userStats; + // Add 10 extra days of data over the buffer limit + for (int i = 0; i < AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK + 10; i++) { + mTestInjector.incrementDate(1); + statsTracker.start(); + statsTracker.add(0, 10); + mTestInjector.incrementTime(1000); + statsTracker.add(0, 20); + mTestInjector.incrementTime(1000); + statsTracker.stop(); + } + // Assert that we are only tracking last "MAX_DAYS_TO_TRACK" + userStats = statsTracker.getUserStats(0); + assertEquals(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK, userStats.size()); + LocalDate runningDate = mTestInjector.getLocalDate(); + for (int i = AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK - 1; i >= 0; i--) { + assertEquals(runningDate, userStats.get(i).getLocalDate()); + runningDate = runningDate.minusDays(1); + } + } + + @Test + public void testReadAmbientBrightnessStats() throws IOException { + AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker(); + LocalDate date = mTestInjector.getLocalDate(); + ArrayList userStats; + String statsFile = + "\r\n" + + "\r\n" + // Old stats that shouldn't be read + + "\r\n" + // Valid stats that should get read + + "\r\n" + // Valid stats that should get read + + "\r\n" + + ""; + statsTracker.readStats(getInputStream(statsFile)); + userStats = statsTracker.getUserStats(0); + assertEquals(2, userStats.size()); + assertEquals(new AmbientBrightnessDayStats(date.minusDays(1), + new float[]{0, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000}, + new float[]{1.088f, 0, 0.726f, 0, 25.868f, 0, 0, 0, 0, 0}), userStats.get(0)); + assertEquals(new AmbientBrightnessDayStats(date, + new float[]{0, 1, 3, 10, 30, 100, 300, 1000, 3000, 10000}, + new float[]{0, 0, 0, 0, 4.482f, 0, 0, 0, 0, 0}), userStats.get(1)); + } + + @Test + public void testFailedReadAmbientBrightnessStatsWithException() { + AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker(); + LocalDate date = mTestInjector.getLocalDate(); + String statsFile; + // Test with parse error + statsFile = + "\r\n" + + "\r\n" + // Incorrect since bucket boundaries not parsable + + "\r\n" + + ""; + try { + statsTracker.readStats(getInputStream(statsFile)); + } catch (IOException e) { + // Expected + } + assertNull(statsTracker.getUserStats(0)); + // Test with incorrect data (bucket boundaries length not equal to stats length) + statsFile = + "\r\n" + + "\r\n" + // Correct data + + "\r\n" + // Incorrect data + + "\r\n" + + ""; + try { + statsTracker.readStats(getInputStream(statsFile)); + } catch (Exception e) { + // Expected + } + assertNull(statsTracker.getUserStats(0)); + // Test with missing attribute + statsFile = + "\r\n" + + "\r\n" + + "\r\n" + + ""; + try { + statsTracker.readStats(getInputStream(statsFile)); + } catch (Exception e) { + // Expected + } + assertNull(statsTracker.getUserStats(0)); + } + + @Test + public void testWriteThenReadAmbientBrightnessStats() throws IOException { + AmbientBrightnessStatsTracker statsTracker = getTestStatsTracker(); + ArrayList userStats; + float[] expectedStats; + // Generate some dummy data + // Data: very old which should not be read + statsTracker.start(); + statsTracker.add(0, 0.05f); + mTestInjector.incrementTime(1000); + statsTracker.add(0, 0.2f); + mTestInjector.incrementTime(1500); + statsTracker.add(0, 1); + mTestInjector.incrementTime(2500); + statsTracker.stop(); + // Data: day 1 user 1 + mTestInjector.incrementDate(AmbientBrightnessStatsTracker.MAX_DAYS_TO_TRACK - 1); + statsTracker.start(); + statsTracker.add(0, 0.05f); + mTestInjector.incrementTime(1000); + statsTracker.add(0, 0.2f); + mTestInjector.incrementTime(1500); + statsTracker.add(0, 1); + mTestInjector.incrementTime(2500); + statsTracker.stop(); + // Data: day 1 user 2 + statsTracker.start(); + statsTracker.add(1, 0); + mTestInjector.incrementTime(3500); + statsTracker.add(1, 5); + mTestInjector.incrementTime(5000); + statsTracker.stop(); + // Data: day 2 user 1 + mTestInjector.incrementDate(1); + statsTracker.start(); + statsTracker.add(0, 0); + mTestInjector.incrementTime(3500); + statsTracker.add(0, 50000); + mTestInjector.incrementTime(5000); + statsTracker.stop(); + // Write them + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + statsTracker.writeStats(baos); + baos.flush(); + // Read them back and assert that it's the same + ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray()); + AmbientBrightnessStatsTracker newStatsTracker = getTestStatsTracker(); + newStatsTracker.readStats(input); + userStats = newStatsTracker.getUserStats(0); + assertEquals(2, userStats.size()); + // Check day 1 user 1 + assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate()); + expectedStats = getEmptyStatsArray(); + expectedStats[0] = 1; + expectedStats[1] = 1.5f; + expectedStats[3] = 2.5f; + assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0); + // Check day 2 user 1 + assertEquals(mTestInjector.getLocalDate(), userStats.get(1).getLocalDate()); + expectedStats = getEmptyStatsArray(); + expectedStats[0] = 3.5f; + expectedStats[11] = 5; + assertArrayEquals(expectedStats, userStats.get(1).getStats(), 0); + userStats = newStatsTracker.getUserStats(1); + assertEquals(1, userStats.size()); + // Check day 1 user 2 + assertEquals(mTestInjector.getLocalDate().minusDays(1), userStats.get(0).getLocalDate()); + expectedStats = getEmptyStatsArray(); + expectedStats[0] = 3.5f; + expectedStats[4] = 5; + assertArrayEquals(expectedStats, userStats.get(0).getStats(), 0); + } + + @Test + public void testTimer() { + AmbientBrightnessStatsTracker.Timer timer = new AmbientBrightnessStatsTracker.Timer( + () -> mTestInjector.elapsedRealtimeMillis()); + assertEquals(0, timer.totalDurationSec(), 0); + mTestInjector.incrementTime(1000); + assertEquals(0, timer.totalDurationSec(), 0); + assertFalse(timer.isRunning()); + // Start timer + timer.start(); + assertTrue(timer.isRunning()); + assertEquals(0, timer.totalDurationSec(), 0); + mTestInjector.incrementTime(1000); + assertTrue(timer.isRunning()); + assertEquals(1, timer.totalDurationSec(), 0); + // Reset timer + timer.reset(); + assertEquals(0, timer.totalDurationSec(), 0); + assertFalse(timer.isRunning()); + // Start again + timer.start(); + assertTrue(timer.isRunning()); + assertEquals(0, timer.totalDurationSec(), 0); + mTestInjector.incrementTime(2000); + assertTrue(timer.isRunning()); + assertEquals(2, timer.totalDurationSec(), 0); + // Reset again + timer.reset(); + assertEquals(0, timer.totalDurationSec(), 0); + assertFalse(timer.isRunning()); + } + + private class TestInjector extends AmbientBrightnessStatsTracker.Injector { + + private long mElapsedRealtimeMillis = SystemClock.elapsedRealtime(); + private LocalDate mLocalDate = LocalDate.now(); + + public void incrementTime(long timeMillis) { + mElapsedRealtimeMillis += timeMillis; + } + + public void incrementDate(int numDays) { + mLocalDate = mLocalDate.plusDays(numDays); + } + + @Override + public long elapsedRealtimeMillis() { + return mElapsedRealtimeMillis; + } + + @Override + public int getUserSerialNumber(UserManager userManager, int userId) { + return userId + 10; + } + + @Override + public int getUserId(UserManager userManager, int userSerialNumber) { + return userSerialNumber - 10; + } + + @Override + public LocalDate getLocalDate() { + return LocalDate.from(mLocalDate); + } + } + + private AmbientBrightnessStatsTracker getTestStatsTracker() { + return new AmbientBrightnessStatsTracker( + InstrumentationRegistry.getContext().getSystemService(UserManager.class), + mTestInjector); + } + + private float[] getEmptyStatsArray() { + return new float[AmbientBrightnessStatsTracker.BUCKET_BOUNDARIES_FOR_NEW_STATS.length]; + } + + private InputStream getInputStream(String data) { + return new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java index edc7d74b47c..82771843e6e 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java @@ -639,7 +639,7 @@ public class BrightnessTrackerTest { } @Override - public AtomicFile getFile() { + public AtomicFile getFile(String filename) { // Don't have the test write / read from anywhere. return null; } -- GitLab From fa3afbd0e7a9a0d8fc8c55ceefdb4ddf9d0115af Mon Sep 17 00:00:00 2001 From: Adam Vartanian Date: Wed, 31 Jan 2018 11:05:10 +0000 Subject: [PATCH 161/416] Adjust URI host parsing to stop on \ character. The WHATWG URL parsing algorithm [1] used by browsers says that for "special" URL schemes (which is basically all commonly-used hierarchical schemes, including http, https, ftp, and file), the host portion ends if a \ character is seen, whereas this class previously continued to consider characters part of the hostname. This meant that a malicious URL could be seen as having a "safe" host when viewed by an app but navigate to a different host when passed to a browser. [1] https://url.spec.whatwg.org/#host-state Bug: 71360761 Test: vogar frameworks/base/core/tests/coretests/src/android/net/UriTest.java (on NYC branch) Test: cts -m CtsNetTestCases (on NYC branch) Change-Id: Id53f7054d1be8d59bbcc7e219159e59a2425106e --- core/java/android/net/Uri.java | 8 ++++++++ core/tests/coretests/src/android/net/UriTest.java | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 0f9c03ecb9c..8fb59af3e02 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -714,6 +714,10 @@ public abstract class Uri implements Parcelable, Comparable { LOOP: while (end < length) { switch (uriString.charAt(end)) { case '/': // Start of path + case '\\':// Start of path + // Per http://url.spec.whatwg.org/#host-state, the \ character + // is treated as if it were a / character when encountered in a + // host case '?': // Start of query case '#': // Start of fragment break LOOP; @@ -752,6 +756,10 @@ public abstract class Uri implements Parcelable, Comparable { case '#': // Start of fragment return ""; // Empty path. case '/': // Start of path! + case '\\':// Start of path! + // Per http://url.spec.whatwg.org/#host-state, the \ character + // is treated as if it were a / character when encountered in a + // host break LOOP; } pathStart++; diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index 49a73e05ba2..ec0b96b9a7d 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -192,6 +192,12 @@ public class UriTest extends TestCase { assertEquals("a:a@example.com:a@example2.com", uri.getAuthority()); assertEquals("example2.com", uri.getHost()); assertEquals(-1, uri.getPort()); + assertEquals("/path", uri.getPath()); + + uri = Uri.parse("http://a.foo.com\\.example.com/path"); + assertEquals("a.foo.com", uri.getHost()); + assertEquals(-1, uri.getPort()); + assertEquals("\\.example.com/path", uri.getPath()); } @SmallTest -- GitLab From 3212bdbb3e57022fcc25dd4373e833f613f4d455 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Tue, 30 Jan 2018 13:01:30 +0000 Subject: [PATCH 162/416] Create test-legacy/ for android.test.legacy target The android.test.legacy (and legacy-android-test) target depends on code from both test-base/ and test-runner/ and do not really belong in either folder. Having a separate folder will also provide a convenient place for the artifacts needed to publish android.test.legacy to maven.google.com. Bug: 30188076 Test: make checkbuild (cherry picked from commit 898e7de6c71e00e11f299b67bd62d4af5fd12ca2) Change-Id: I0538281980a55178dd72e5fae16d817cd31aa104 --- test-base/Android.bp | 25 ------------------------- test-legacy/Android.bp | 36 ++++++++++++++++++++++++++++++++++++ test-legacy/Android.mk | 40 ++++++++++++++++++++++++++++++++++++++++ test-runner/Android.mk | 18 ------------------ 4 files changed, 76 insertions(+), 43 deletions(-) create mode 100644 test-legacy/Android.bp create mode 100644 test-legacy/Android.mk diff --git a/test-base/Android.bp b/test-base/Android.bp index ccf57b00a37..62fed61da27 100644 --- a/test-base/Android.bp +++ b/test-base/Android.bp @@ -83,28 +83,3 @@ java_library_static { "junit", ], } - -// Build the legacy-android-test library -// ===================================== -// This contains the android.test classes that were in Android API level 25, -// including those from android.test.runner. -// Also contains the com.android.internal.util.Predicate[s] classes. -java_library_static { - name: "legacy-android-test", - - srcs: [ - "src/android/**/*.java", - "src/com/**/*.java", - ], - - static_libs: [ - "android.test.runner-minus-junit", - "android.test.mock", - ], - - no_framework_libs: true, - libs: [ - "framework", - "junit", - ], -} diff --git a/test-legacy/Android.bp b/test-legacy/Android.bp new file mode 100644 index 00000000000..d2af8a9f1c8 --- /dev/null +++ b/test-legacy/Android.bp @@ -0,0 +1,36 @@ +// +// Copyright (C) 2018 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Build the legacy-android-test library +// ===================================== +// This contains the android.test classes that were in Android API level 25, +// including those from android.test.runner. +// Also contains the com.android.internal.util.Predicate[s] classes. +java_library_static { + name: "legacy-android-test", + + static_libs: [ + "android.test.base-minus-junit", + "android.test.runner-minus-junit", + "android.test.mock", + ], + + no_framework_libs: true, + libs: [ + "framework", + "junit", + ], +} diff --git a/test-legacy/Android.mk b/test-legacy/Android.mk new file mode 100644 index 00000000000..b8c53266b9f --- /dev/null +++ b/test-legacy/Android.mk @@ -0,0 +1,40 @@ +# +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) + +# For unbundled build we'll use the prebuilt jar from prebuilts/sdk. +ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK))) + +# Build the android.test.legacy library +# ===================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := android.test.legacy + +LOCAL_SDK_VERSION := current + +LOCAL_JAVA_LIBRARIES := junit +LOCAL_STATIC_JAVA_LIBRARIES := \ + android.test.base-minus-junit \ + android.test.runner-minus-junit \ + +include $(BUILD_STATIC_JAVA_LIBRARY) + +# Archive a copy of the classes.jar in SDK build. +$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.legacy.jar) + +endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true diff --git a/test-runner/Android.mk b/test-runner/Android.mk index 229a6ac05bb..706f6364ef8 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -117,23 +117,5 @@ update-android-test-runner-api: $(ANDROID_TEST_RUNNER_OUTPUT_API_FILE) | $(ACP) endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true -# Build the android.test.legacy library -# ===================================== -include $(CLEAR_VARS) - -LOCAL_MODULE := android.test.legacy - -LOCAL_SRC_FILES := $(call all-java-files-under, src/android) - -LOCAL_SDK_VERSION := current - -LOCAL_JAVA_LIBRARIES := android.test.mock.stubs junit -LOCAL_STATIC_JAVA_LIBRARIES := android.test.base-minus-junit - -include $(BUILD_STATIC_JAVA_LIBRARY) - -# Archive a copy of the classes.jar in SDK build. -$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.legacy.jar) - # additionally, build unit tests in a separate .apk include $(call all-makefiles-under,$(LOCAL_PATH)) -- GitLab From c79d6b4fdf8f94acd389d1cc0761db32e9d2dc97 Mon Sep 17 00:00:00 2001 From: Hyundo Moon Date: Wed, 31 Jan 2018 19:25:59 +0900 Subject: [PATCH 163/416] MediaSession2: Move MediaController2.PlaybackInfo to updatable Bug: 72716294 Test: Builds successfully Change-Id: I6c1f7afdc49df6fe345db17fc41563a511c665e0 --- .../java/android/media/MediaController2.java | 31 +++++++------------ .../media/update/PlaybackInfoProvider.java | 31 +++++++++++++++++++ 2 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 media/java/android/media/update/PlaybackInfoProvider.java diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java index 47dbde98c61..10aca1ccc0e 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/java/android/media/MediaController2.java @@ -30,6 +30,7 @@ import android.media.MediaSession2.PlaylistParams; import android.media.session.MediaSessionManager; import android.media.update.ApiLoader; import android.media.update.MediaController2Provider; +import android.media.update.PlaybackInfoProvider; import android.net.Uri; import android.os.Bundle; import android.os.ResultReceiver; @@ -160,21 +161,14 @@ public class MediaController2 implements AutoCloseable { */ public static final int PLAYBACK_TYPE_LOCAL = 1; - private final int mVolumeType; - private final int mVolumeControl; - private final int mMaxVolume; - private final int mCurrentVolume; - private final AudioAttributes mAudioAttrs; + private final PlaybackInfoProvider mProvider; /** * @hide */ - public PlaybackInfo(int type, AudioAttributes attrs, int control, int max, int current) { - mVolumeType = type; - mAudioAttrs = attrs; - mVolumeControl = control; - mMaxVolume = max; - mCurrentVolume = current; + @SystemApi + public PlaybackInfo(PlaybackInfoProvider provider) { + mProvider = provider; } /** @@ -187,7 +181,7 @@ public class MediaController2 implements AutoCloseable { * @return The type of playback this session is using. */ public int getPlaybackType() { - return mVolumeType; + return mProvider.getPlaybackType_impl(); } /** @@ -199,7 +193,7 @@ public class MediaController2 implements AutoCloseable { * @return The attributes for this session. */ public AudioAttributes getAudioAttributes() { - return mAudioAttrs; + return mProvider.getAudioAttributes_impl(); } /** @@ -210,11 +204,10 @@ public class MediaController2 implements AutoCloseable { *
  • {@link VolumeProvider#VOLUME_CONTROL_FIXED}
  • * * - * @return The type of volume control that may be used with this - * session. + * @return The type of volume control that may be used with this session. */ - public int getVolumeControl() { - return mVolumeControl; + public int getControlType() { + return mProvider.getControlType_impl(); } /** @@ -223,7 +216,7 @@ public class MediaController2 implements AutoCloseable { * @return The maximum allowed volume where this session is playing. */ public int getMaxVolume() { - return mMaxVolume; + return mProvider.getMaxVolume_impl(); } /** @@ -232,7 +225,7 @@ public class MediaController2 implements AutoCloseable { * @return The current volume where this session is playing. */ public int getCurrentVolume() { - return mCurrentVolume; + return mProvider.getCurrentVolume_impl(); } } diff --git a/media/java/android/media/update/PlaybackInfoProvider.java b/media/java/android/media/update/PlaybackInfoProvider.java new file mode 100644 index 00000000000..36eb58a97df --- /dev/null +++ b/media/java/android/media/update/PlaybackInfoProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.media.update; + +import android.media.AudioAttributes; + +/** + * @hide + */ +// TODO(jaewan): @SystemApi +public interface PlaybackInfoProvider { + int getPlaybackType_impl(); + AudioAttributes getAudioAttributes_impl(); + int getControlType_impl(); + int getMaxVolume_impl(); + int getCurrentVolume_impl(); +} -- GitLab From 6c91900da545d4ac0823d54d7cb5b09278fa2e30 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Wed, 31 Jan 2018 13:02:16 +0100 Subject: [PATCH 164/416] AM: Clear keyguardGoingAway after it has gone away Fixes an issue where the keyguard going away flag was not cleared until the keyguard showed again, which prevented SystemUI from ever controlling the orientation while it is not showing the keyguard, such as during unlocked AOD. Change-Id: I40d4052321fe76c988d673a5ecbc07b74192bec6 Fixes: 69368568 Test: Set up secure keyguard, Turn off "power button locks", open a landscape app, press power button, verify AOD shows in portrait. --- .../java/com/android/server/am/ActivityManagerService.java | 1 + .../core/java/com/android/server/am/KeyguardController.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5eb5b140623..d3ceb95f7db 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -25276,6 +25276,7 @@ public class ActivityManagerService extends IActivityManager.Stub public void notifyAppTransitionFinished() { synchronized (ActivityManagerService.this) { mStackSupervisor.notifyAppTransitionDone(); + mKeyguardController.notifyAppTransitionDone(); } } diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java index 79f3fe3ffd9..05305f300e6 100644 --- a/services/core/java/com/android/server/am/KeyguardController.java +++ b/services/core/java/com/android/server/am/KeyguardController.java @@ -384,4 +384,8 @@ class KeyguardController { proto.write(KEYGUARD_OCCLUDED, mOccluded); proto.end(token); } + + public void notifyAppTransitionDone() { + setKeyguardGoingAway(false); + } } -- GitLab From 396a10c5b028c5e969ff1aee127a108b1ac95570 Mon Sep 17 00:00:00 2001 From: Amith Yamasani Date: Fri, 19 Jan 2018 10:58:07 -0800 Subject: [PATCH 165/416] Delay coming out of doze until keyguard dismissed v2 (earlier attempt was reverted due to deadlock) Don't come out of doze when screen is on, but wait until the keyguard is dismissed as well. Pass along the keyguard dismissed to DeviceIdle Request exitIdle when notifications are clicked. Bug: 63527576 Test: Manual: Turn off screen adb shell dumpsys battery unplug adb shell dumpsys deviceidle step x4 Turn on screen, verify still in doze Unlock, verify out of doze Change-Id: Ic5129611d4f098723b31d62a27db8f50860697d4 --- .../android/server/DeviceIdleController.java | 43 ++++++++++++++++++- .../NotificationManagerService.java | 16 +++++++ .../tests/uiservicestests/AndroidManifest.xml | 1 + 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index a12c85aef85..44974ffd350 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -141,6 +141,8 @@ public class DeviceIdleController extends SystemService private boolean mHasNetworkLocation; private Location mLastGenericLocation; private Location mLastGpsLocation; + // Current locked state of the screen + private boolean mScreenLocked; /** Device is currently active. */ private static final int STATE_ACTIVE = 0; @@ -156,6 +158,7 @@ public class DeviceIdleController extends SystemService private static final int STATE_IDLE = 5; /** Device is in the idle state, but temporarily out of idle to do regular maintenance. */ private static final int STATE_IDLE_MAINTENANCE = 6; + private static String stateToString(int state) { switch (state) { case STATE_ACTIVE: return "ACTIVE"; @@ -547,6 +550,11 @@ public class DeviceIdleController extends SystemService "sms_temp_app_whitelist_duration"; private static final String KEY_NOTIFICATION_WHITELIST_DURATION = "notification_whitelist_duration"; + /** + * Whether to wait for the user to unlock the device before causing screen-on to + * exit doze. Default = true + */ + private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock"; /** * This is the time, after becoming inactive, that we go in to the first @@ -765,6 +773,8 @@ public class DeviceIdleController extends SystemService */ public long NOTIFICATION_WHITELIST_DURATION; + public boolean WAIT_FOR_UNLOCK; + private final ContentResolver mResolver; private final boolean mSmallBatteryDevice; private final KeyValueListParser mParser = new KeyValueListParser(','); @@ -855,6 +865,7 @@ public class DeviceIdleController extends SystemService KEY_SMS_TEMP_APP_WHITELIST_DURATION, 20 * 1000L); NOTIFICATION_WHITELIST_DURATION = mParser.getDurationMillis( KEY_NOTIFICATION_WHITELIST_DURATION, 30 * 1000L); + WAIT_FOR_UNLOCK = mParser.getBoolean(KEY_WAIT_FOR_UNLOCK, false); } } @@ -962,6 +973,9 @@ public class DeviceIdleController extends SystemService pw.print(" "); pw.print(KEY_NOTIFICATION_WHITELIST_DURATION); pw.print("="); TimeUtils.formatDuration(NOTIFICATION_WHITELIST_DURATION, pw); pw.println(); + + pw.print(" "); pw.print(KEY_WAIT_FOR_UNLOCK); pw.print("="); + pw.println(WAIT_FOR_UNLOCK); } } @@ -1340,6 +1354,19 @@ public class DeviceIdleController extends SystemService } } + private ActivityManagerInternal.ScreenObserver mScreenObserver = + new ActivityManagerInternal.ScreenObserver() { + @Override + public void onAwakeStateChanged(boolean isAwake) { } + + @Override + public void onKeyguardStateChanged(boolean isShowing) { + synchronized (DeviceIdleController.this) { + DeviceIdleController.this.keyguardShowingLocked(isShowing); + } + } + }; + public DeviceIdleController(Context context) { super(context); mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml")); @@ -1406,6 +1433,7 @@ public class DeviceIdleController extends SystemService mNetworkConnected = true; mScreenOn = true; + mScreenLocked = false; // Start out assuming we are charging. If we aren't, we will at least get // a battery update the next time the level drops. mCharging = true; @@ -1501,6 +1529,8 @@ public class DeviceIdleController extends SystemService mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); + mLocalActivityManager.registerScreenObserver(mScreenObserver); + passWhiteListToForceAppStandbyTrackerLocked(); updateInteractivityLocked(); } @@ -1976,7 +2006,7 @@ public class DeviceIdleController extends SystemService } } else if (screenOn) { mScreenOn = true; - if (!mForceIdle) { + if (!mForceIdle && (!mScreenLocked || !mConstants.WAIT_FOR_UNLOCK)) { becomeActiveLocked("screen", Process.myUid()); } } @@ -1997,6 +2027,16 @@ public class DeviceIdleController extends SystemService } } + void keyguardShowingLocked(boolean showing) { + if (DEBUG) Slog.i(TAG, "keyguardShowing=" + showing); + if (mScreenLocked != showing) { + mScreenLocked = showing; + if (mScreenOn && !mForceIdle && !mScreenLocked) { + becomeActiveLocked("unlocked", Process.myUid()); + } + } + } + void scheduleReportActiveLocked(String activeReason, int activeUid) { Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason); mHandler.sendMessage(msg); @@ -3308,6 +3348,7 @@ public class DeviceIdleController extends SystemService pw.print(" mForceIdle="); pw.println(mForceIdle); pw.print(" mMotionSensor="); pw.println(mMotionSensor); pw.print(" mScreenOn="); pw.println(mScreenOn); + pw.print(" mScreenLocked="); pw.println(mScreenLocked); pw.print(" mNetworkConnected="); pw.println(mNetworkConnected); pw.print(" mCharging="); pw.println(mCharging); pw.print(" mMotionActive="); pw.println(mMotionListener.active); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 9865e356328..727e7ee2ac1 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -111,6 +111,7 @@ import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.IDeviceIdleController; import android.os.IInterface; import android.os.Looper; import android.os.Message; @@ -286,6 +287,7 @@ public class NotificationManagerService extends SystemService { private AlarmManager mAlarmManager; private ICompanionDeviceManager mCompanionManager; private AccessibilityManager mAccessibilityManager; + private IDeviceIdleController mDeviceIdleController; final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; @@ -659,6 +661,7 @@ public class NotificationManagerService extends SystemService { @Override public void onNotificationClick(int callingUid, int callingPid, String key) { + exitIdle(); synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r == null) { @@ -683,6 +686,7 @@ public class NotificationManagerService extends SystemService { @Override public void onNotificationActionClick(int callingUid, int callingPid, String key, int actionIndex) { + exitIdle(); synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r == null) { @@ -812,6 +816,7 @@ public class NotificationManagerService extends SystemService { @Override public void onNotificationDirectReplied(String key) { + exitIdle(); synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { @@ -1280,6 +1285,8 @@ public class NotificationManagerService extends SystemService { mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); mCompanionManager = companionManager; mActivityManager = activityManager; + mDeviceIdleController = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); mHandler = new WorkerHandler(looper); mRankingThread.start(); @@ -1533,6 +1540,15 @@ public class NotificationManagerService extends SystemService { sendRegisteredOnlyBroadcast(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED); } + private void exitIdle() { + try { + if (mDeviceIdleController != null) { + mDeviceIdleController.exitIdle("notification interaction"); + } + } catch (RemoteException e) { + } + } + private void updateNotificationChannelInt(String pkg, int uid, NotificationChannel channel, boolean fromListener) { if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) { diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml index 34755729142..aabf9eab3c1 100644 --- a/services/tests/uiservicestests/AndroidManifest.xml +++ b/services/tests/uiservicestests/AndroidManifest.xml @@ -26,6 +26,7 @@ + -- GitLab From abb4fc8464cb26b24e6ef046672930a02fadbe52 Mon Sep 17 00:00:00 2001 From: Jan Althaus Date: Wed, 31 Jan 2018 11:51:34 +0100 Subject: [PATCH 166/416] Updating TextClassifier factory model names The smartselection token no longer accurately describes the purpose of the model. This change brings the factory models in line with the path for the updated model. Test: Ran framework core tests and tested manually Change-Id: I7641db313c94b99bb6960cf1efd24f796bb092a2 --- core/java/android/view/textclassifier/TextClassifierImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index b03c70d1919..afa54fc6041 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -73,7 +73,7 @@ public final class TextClassifierImpl implements TextClassifier { private static final String LOG_TAG = DEFAULT_LOG_TAG; private static final String MODEL_DIR = "/etc/textclassifier/"; - private static final String MODEL_FILE_REGEX = "textclassifier\\.smartselection\\.(.*)\\.model"; + private static final String MODEL_FILE_REGEX = "textclassifier\\.(.*)\\.model"; private static final String UPDATED_MODEL_FILE_PATH = "/data/misc/textclassifier/textclassifier.model"; private static final List ENTITY_TYPES_ALL = -- GitLab From fcd2af9ca8ab027bc85d17711ce5c36c7851e1d9 Mon Sep 17 00:00:00 2001 From: Seigo Nonaka Date: Tue, 30 Jan 2018 19:09:28 -0800 Subject: [PATCH 167/416] Stop supporting broken font fallback The supporting broken font is over engineering and it works the same as default font fallback in most cases. Removing fallback logic and return default fallback. Test: bit CtsTextTestCases:* Test: bit CtsGraphicsTestCases:* Test: ./gradlew appcompat-v7:connectedDebugAndroidTest Test: ./gradlew support-compat:connectedDebugAndroidTest Bug: 65024629 Change-Id: Ib3fc0d638c6aee904cdf865082f8d5ae7d31ba48 --- core/jni/android/graphics/FontFamily.cpp | 18 +++++---------- .../java/android/graphics/FontFamily.java | 22 ------------------- graphics/java/android/graphics/Typeface.java | 18 +++++---------- 3 files changed, 11 insertions(+), 47 deletions(-) diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index dd3e6f02e9f..937b3ffb9d6 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -44,11 +44,9 @@ namespace android { struct NativeFamilyBuilder { NativeFamilyBuilder(uint32_t langId, int variant) - : langId(langId), variant(static_cast(variant)), - allowUnsupportedFont(false) {} + : langId(langId), variant(static_cast(variant)) {} uint32_t langId; minikin::FontFamily::Variant variant; - bool allowUnsupportedFont; std::vector fonts; std::vector axes; }; @@ -70,22 +68,17 @@ static jlong FontFamily_create(jlong builderPtr) { } std::unique_ptr builder( reinterpret_cast(builderPtr)); + if (builder->fonts.empty()) { + return 0; + } std::shared_ptr family = std::make_shared( builder->langId, builder->variant, std::move(builder->fonts)); - if (family->getCoverage().length() == 0 && !builder->allowUnsupportedFont) { + if (family->getCoverage().length() == 0) { return 0; } return reinterpret_cast(new FontFamilyWrapper(std::move(family))); } -static void FontFamily_allowUnsupportedFont(jlong builderPtr) { - if (builderPtr == 0) { - return; - } - NativeFamilyBuilder* builder = reinterpret_cast(builderPtr); - builder->allowUnsupportedFont = true; -} - static void FontFamily_abort(jlong builderPtr) { NativeFamilyBuilder* builder = reinterpret_cast(builderPtr); delete builder; @@ -270,7 +263,6 @@ static void FontFamily_addAxisValue(jlong builderPtr, jint tag, jfloat value) { static const JNINativeMethod gFontFamilyMethods[] = { { "nInitBuilder", "(Ljava/lang/String;I)J", (void*)FontFamily_initBuilder }, { "nCreateFamily", "(J)J", (void*)FontFamily_create }, - { "nAllowUnsupportedFont", "(J)V", (void*)FontFamily_allowUnsupportedFont }, { "nAbort", "(J)V", (void*)FontFamily_abort }, { "nUnrefFamily", "(J)V", (void*)FontFamily_unref }, { "nAddFont", "(JLjava/nio/ByteBuffer;III)Z", (void*)FontFamily_addFont }, diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index d77e6012fb4..fe2b5234106 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -160,25 +160,6 @@ public class FontFamily { isItalic); } - /** - * Allow creating unsupported FontFamily. - * - * For compatibility reasons, we still need to create a FontFamily object even if Minikin failed - * to find any usable 'cmap' table for some reasons, e.g. broken 'cmap' table, no 'cmap' table - * encoded with Unicode code points, etc. Without calling this method, the freeze() method will - * return null if Minikin fails to find any usable 'cmap' table. By calling this method, the - * freeze() won't fail and will create an empty FontFamily. This empty FontFamily is placed at - * the top of the fallback chain but is never used. if we don't create this empty FontFamily - * and put it at top, bad things (performance regressions, unexpected glyph selection) will - * happen. - */ - public void allowUnsupportedFont() { - if (mBuilderPtr == 0) { - throw new IllegalStateException("Unable to allow unsupported font."); - } - nAllowUnsupportedFont(mBuilderPtr); - } - // TODO: Remove once internal user stop using private API. private static boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex) { return nAddFont(builderPtr, font, ttcIndex, -1, -1); @@ -189,9 +170,6 @@ public class FontFamily { @CriticalNative private static native long nCreateFamily(long mBuilderPtr); - @CriticalNative - private static native void nAllowUnsupportedFont(long builderPtr); - @CriticalNative private static native void nAbort(long mBuilderPtr); diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index ef415076313..04c5295539a 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -818,12 +818,9 @@ public class Typeface { if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */, 0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, null /* axes */)) { - // Due to backward compatibility, even if the font is not supported by our font - // stack, we need to place the empty font at the first place. The typeface with - // empty font behaves different from default typeface especially in fallback - // font selection. - fontFamily.allowUnsupportedFont(); - fontFamily.freeze(); + if (!fontFamily.freeze()) { + return Typeface.DEFAULT; + } final FontFamily[] families = { fontFamily }; typeface = createFromFamiliesWithDefault(families, DEFAULT_FAMILY, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); @@ -870,12 +867,9 @@ public class Typeface { final FontFamily fontFamily = new FontFamily(); if (fontFamily.addFont(path, 0 /* ttcIndex */, null /* axes */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)) { - // Due to backward compatibility, even if the font is not supported by our font - // stack, we need to place the empty font at the first place. The typeface with - // empty font behaves different from default typeface especially in fallback font - // selection. - fontFamily.allowUnsupportedFont(); - fontFamily.freeze(); + if (!fontFamily.freeze()) { + return Typeface.DEFAULT; + } FontFamily[] families = { fontFamily }; return createFromFamiliesWithDefault(families, DEFAULT_FAMILY, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE); -- GitLab From dd68de50fb4e5f593af10f78f29173e87204aaba Mon Sep 17 00:00:00 2001 From: Jan Althaus Date: Wed, 31 Jan 2018 17:54:48 +0100 Subject: [PATCH 168/416] Fixing generateLinks and java docs Also fixed import order Test: Ran framework core tests Change-Id: I8e99cfc8bab8f7c9f18310634c9565200df43e7f --- .../android/view/textclassifier/TextClassifierImpl.java | 1 + core/java/android/view/textclassifier/TextLinks.java | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index b03c70d1919..7c759e31c7f 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -232,6 +232,7 @@ public final class TextClassifierImpl implements TextClassifier { } builder.addLink(span.getStartIndex(), span.getEndIndex(), entityScores); } + return builder.build(); } catch (Throwable t) { // Avoid throwing from this method. Log the error. Log.e(LOG_TAG, "Error getting links info.", t); diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java index 670efdd286e..ede52119390 100644 --- a/core/java/android/view/textclassifier/TextLinks.java +++ b/core/java/android/view/textclassifier/TextLinks.java @@ -23,13 +23,13 @@ import android.annotation.Nullable; import android.os.LocaleList; import android.os.Parcel; import android.os.Parcelable; -import android.widget.TextView; import android.text.Spannable; import android.text.style.ClickableSpan; import android.text.util.Linkify; import android.text.util.Linkify.LinkifyMask; import android.view.View; import android.view.textclassifier.TextClassifier.EntityType; +import android.widget.TextView; import com.android.internal.util.Preconditions; @@ -351,7 +351,7 @@ public final class TextLinks implements Parcelable { * @throws IllegalArgumentException if applyStrategy is not valid * * @see #APPLY_STRATEGY_IGNORE - * @see #APPLY_STRAGETY_REPLACE + * @see #APPLY_STRATEGY_REPLACE */ public Options setApplyStrategy(@ApplyStrategy int applyStrategy) { checkValidApplyStrategy(applyStrategy); @@ -391,8 +391,8 @@ public final class TextLinks implements Parcelable { * @return the strategy for resolving conflictswhen applying generated links to text that * already have links. * - * @see #APPLY_STATEGY_IGNORE - * @see #APPLY_STRAGETY_REPLACE + * @see #APPLY_STRATEGY_IGNORE + * @see #APPLY_STRATEGY_REPLACE */ @ApplyStrategy public int getApplyStrategy() { -- GitLab From c50928b763c2e580bf313436ff641fd5a5a83cb0 Mon Sep 17 00:00:00 2001 From: Alex Light Date: Wed, 31 Jan 2018 16:47:29 +0000 Subject: [PATCH 169/416] Revert "Revert "Make AndroidRuntime only start the debugger for zygote forked apps."" This reverts commit ff5d60b268dfd848c236b62ae241370a68aae39f. Reason for revert: Fixed issue breaking go/art-build. Bug: 72400560 Test: Build Change-Id: Ie8943068302bec02d149917ccf738c0d935f8fe0 --- core/jni/AndroidRuntime.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index d7f725d03ed..3784d4daa2e 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -761,18 +761,17 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) /* * Enable debugging only for apps forked from zygote. - * Set suspend=y to pause during VM init and use android ADB transport. */ if (zygote) { + // Set the JDWP provider and required arguments. By default let the runtime choose how JDWP is + // implemented. When this is not set the runtime defaults to not allowing JDWP. addOption("-XjdwpOptions:suspend=n,server=y"); + parseRuntimeOption("dalvik.vm.jdwp-provider", + jdwpProviderBuf, + "-XjdwpProvider:", + "default"); } - // Set the JDWP provider. By default let the runtime choose. - parseRuntimeOption("dalvik.vm.jdwp-provider", - jdwpProviderBuf, - "-XjdwpProvider:", - "default"); - parseRuntimeOption("dalvik.vm.lockprof.threshold", lockProfThresholdBuf, "-Xlockprofthreshold:"); -- GitLab From 0cd882fce2ac609015c82b720ba500b5e6149359 Mon Sep 17 00:00:00 2001 From: Lucas Dupin Date: Tue, 30 Jan 2018 12:19:49 -0800 Subject: [PATCH 170/416] Also translate separator when moving shelf icons Shelf separator should also be translated otherwise it won't be horizontally aligned to the clock separator. Test: visual Test: atest packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java Change-Id: I0df8aee288466a680f78b5d70cc42138e54b3540 Fixes: 72527703 --- .../systemui/statusbar/NotificationShelf.java | 4 ---- .../statusbar/phone/NotificationIconContainer.java | 13 ------------- .../statusbar/phone/NotificationPanelView.java | 6 ++---- .../stack/NotificationStackScrollLayout.java | 11 +++++++++-- .../stack/NotificationStackScrollLayoutTest.java | 11 +++++++++++ 5 files changed, 22 insertions(+), 23 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index 8325df7fadd..cad956cd602 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -802,10 +802,6 @@ public class NotificationShelf extends ActivatableNotificationView implements updateRelativeOffset(); } - public void setDarkOffsetX(int offsetX) { - mShelfIcons.setDarkOffsetX(offsetX); - } - private class ShelfState extends ExpandableViewState { private float openedAmount; private boolean hasItemsInStableShelf; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 91cae0af6b1..5cf4c4c7097 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -120,7 +120,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { private boolean mDisallowNextAnimation; private boolean mAnimationsEnabled = true; private ArrayMap> mReplacingIcons; - private int mDarkOffsetX; // Keep track of the last visible icon so collapsed container can report on its location private IconState mLastVisibleIconState; @@ -378,14 +377,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth(); } } - - if (mDark && mDarkOffsetX != 0) { - for (int i = 0; i < childCount; i++) { - View view = getChildAt(i); - IconState iconState = mIconStates.get(view); - iconState.xTranslation += mDarkOffsetX; - } - } } private float getLayoutEnd() { @@ -534,10 +525,6 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout { mAnimationsEnabled = enabled; } - public void setDarkOffsetX(int offsetX) { - mDarkOffsetX = offsetX; - } - public void setReplacingIcons(ArrayMap> replacingIcons) { mReplacingIcons = replacingIcons; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 2111d2ef5d8..52d005cb152 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -47,6 +47,7 @@ import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; + import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.keyguard.KeyguardStatusView; @@ -67,14 +68,11 @@ import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; -import com.android.systemui.statusbar.notification.NotificationUtils; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackStateAnimator; -import java.util.Collection; import java.util.List; public class NotificationPanelView extends PanelView implements @@ -482,7 +480,7 @@ public class NotificationPanelView extends PanelView implements mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment; } mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding); - mNotificationStackScroller.setDarkShelfOffsetX(mClockPositionResult.clockX); + mNotificationStackScroller.setAntiBurnInOffsetX(mClockPositionResult.clockX); mKeyguardBottomArea.setBurnInXOffset(mClockPositionResult.clockX); requestScrollerTopPaddingUpdate(animate); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index b28e1a9cafe..1b55a5b0325 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -403,6 +403,7 @@ public class NotificationStackScrollLayout extends ViewGroup private final int mSeparatorThickness; private final Rect mTmpRect = new Rect(); private int mClockBottom; + private int mAntiBurnInOffsetX; public NotificationStackScrollLayout(Context context) { this(context, null); @@ -3889,9 +3890,14 @@ public class NotificationStackScrollLayout extends ViewGroup applyCurrentBackgroundBounds(); updateWillNotDraw(); updateContentHeight(); + updateAntiBurnInTranslation(); notifyHeightChangeListener(mShelf); } + private void updateAntiBurnInTranslation() { + setTranslationX(mAmbientState.isDark() ? mAntiBurnInOffsetX : 0); + } + /** * Updates whether or not this Layout will perform its own custom drawing (i.e. whether or * not {@link #onDraw(Canvas)} is called). This method should be called whenever the @@ -4473,8 +4479,9 @@ public class NotificationStackScrollLayout extends ViewGroup mHeadsUpGoingAwayAnimationsAllowed = headsUpGoingAwayAnimationsAllowed; } - public void setDarkShelfOffsetX(int shelfOffsetX) { - mShelf.setDarkOffsetX(shelfOffsetX); + public void setAntiBurnInOffsetX(int antiBurnInOffsetX) { + mAntiBurnInOffsetX = antiBurnInOffsetX; + updateAntiBurnInTranslation(); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java index 1c010b66056..d9673d3552d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java @@ -61,4 +61,15 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { Assert.assertFalse(mStackScroller.isDimmed()); } + @Test + public void testAntiBurnInOffset() { + final int burnInOffset = 30; + mStackScroller.setAntiBurnInOffsetX(burnInOffset); + mStackScroller.setDark(false /* dark */, false /* animated */, null /* touch */); + Assert.assertEquals(0 /* expected */, mStackScroller.getTranslationX(), 0.01 /* delta */); + mStackScroller.setDark(true /* dark */, false /* animated */, null /* touch */); + Assert.assertEquals(burnInOffset /* expected */, mStackScroller.getTranslationX(), + 0.01 /* delta */); + } + } -- GitLab From 09d58a4e3b374609601182c1dccb71a297bc97c8 Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Tue, 30 Jan 2018 14:04:03 -0800 Subject: [PATCH 171/416] Changed dataset methods that take a Pattern filter to accept null. In this case, null means the field should not be filterable, which is useful in the cases where it represents a non-authenticated value like a password. Test: atest CtsAutoFillServiceTestCases:LoginActivityTest#filterTextDisabledUsingNullRegex Test: atest CtsAutoFillServiceTestCases:LoginActivityTest Bug: 71359055 Fixes: 72711778 Change-Id: Idc8248f6c95fdb50b934840689616e42ddd6378c --- .../android/service/autofill/Dataset.java | 108 ++++++++++++----- .../android/server/autofill/ui/FillUi.java | 114 +++++++++++++----- 2 files changed, 161 insertions(+), 61 deletions(-) diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 266bcda7797..f32dee1ec9f 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -29,7 +29,6 @@ import android.widget.RemoteViews; import com.android.internal.util.Preconditions; -import java.io.Serializable; import java.util.ArrayList; import java.util.regex.Pattern; @@ -99,7 +98,7 @@ public final class Dataset implements Parcelable { private final ArrayList mFieldIds; private final ArrayList mFieldValues; private final ArrayList mFieldPresentations; - private final ArrayList mFieldFilters; + private final ArrayList mFieldFilters; private final RemoteViews mPresentation; private final IntentSender mAuthentication; @Nullable String mId; @@ -132,7 +131,7 @@ public final class Dataset implements Parcelable { /** @hide */ @Nullable - public Pattern getFilter(int index) { + public DatasetFieldFilter getFilter(int index) { return mFieldFilters.get(index); } @@ -189,7 +188,7 @@ public final class Dataset implements Parcelable { private ArrayList mFieldIds; private ArrayList mFieldValues; private ArrayList mFieldPresentations; - private ArrayList mFieldFilters; + private ArrayList mFieldFilters; private RemoteViews mPresentation; private IntentSender mAuthentication; private boolean mDestroyed; @@ -363,19 +362,21 @@ public final class Dataset implements Parcelable { * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. - * @param filter regex used to determine if the dataset should be shown in the autofill UI. + * @param filter regex used to determine if the dataset should be shown in the autofill UI; + * when {@code null}, it disables filtering on that dataset (this is the recommended + * approach when {@code value} is not {@code null} and field contains sensitive data + * such as passwords). * * @return this builder. * @throws IllegalStateException if the builder was constructed without a * {@link RemoteViews presentation}. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, - @NonNull Pattern filter) { + @Nullable Pattern filter) { throwIfDestroyed(); - Preconditions.checkNotNull(filter, "filter cannot be null"); Preconditions.checkState(mPresentation != null, "Dataset presentation not set on constructor"); - setLifeTheUniverseAndEverything(id, value, null, filter); + setLifeTheUniverseAndEverything(id, value, null, new DatasetFieldFilter(filter)); return this; } @@ -398,23 +399,26 @@ public final class Dataset implements Parcelable { * @param value the value to be autofilled. Pass {@code null} if you do not have the value * but the target view is a logical part of the dataset. For example, if * the dataset needs authentication and you have no access to the value. + * @param filter regex used to determine if the dataset should be shown in the autofill UI; + * when {@code null}, it disables filtering on that dataset (this is the recommended + * approach when {@code value} is not {@code null} and field contains sensitive data + * such as passwords). * @param presentation the presentation used to visualize this field. - * @param filter regex used to determine if the dataset should be shown in the autofill UI. * * @return this builder. */ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value, - @NonNull Pattern filter, @NonNull RemoteViews presentation) { + @Nullable Pattern filter, @NonNull RemoteViews presentation) { throwIfDestroyed(); - Preconditions.checkNotNull(filter, "filter cannot be null"); Preconditions.checkNotNull(presentation, "presentation cannot be null"); - setLifeTheUniverseAndEverything(id, value, presentation, filter); + setLifeTheUniverseAndEverything(id, value, presentation, + new DatasetFieldFilter(filter)); return this; } private void setLifeTheUniverseAndEverything(@NonNull AutofillId id, @Nullable AutofillValue value, @Nullable RemoteViews presentation, - @Nullable Pattern filter) { + @Nullable DatasetFieldFilter filter) { Preconditions.checkNotNull(id, "id cannot be null"); if (mFieldIds != null) { final int existingIdx = mFieldIds.indexOf(id); @@ -477,8 +481,8 @@ public final class Dataset implements Parcelable { parcel.writeParcelable(mPresentation, flags); parcel.writeTypedList(mFieldIds, flags); parcel.writeTypedList(mFieldValues, flags); - parcel.writeParcelableList(mFieldPresentations, flags); - parcel.writeSerializable(mFieldFilters); + parcel.writeTypedList(mFieldPresentations, flags); + parcel.writeTypedList(mFieldFilters, flags); parcel.writeParcelable(mAuthentication, flags); parcel.writeString(mId); } @@ -493,22 +497,19 @@ public final class Dataset implements Parcelable { final Builder builder = (presentation == null) ? new Builder() : new Builder(presentation); - final ArrayList ids = parcel.createTypedArrayList(AutofillId.CREATOR); + final ArrayList ids = + parcel.createTypedArrayList(AutofillId.CREATOR); final ArrayList values = parcel.createTypedArrayList(AutofillValue.CREATOR); - final ArrayList presentations = new ArrayList<>(); - parcel.readParcelableList(presentations, null); - @SuppressWarnings("unchecked") - final ArrayList filters = - (ArrayList) parcel.readSerializable(); - final int idCount = (ids != null) ? ids.size() : 0; - final int valueCount = (values != null) ? values.size() : 0; - for (int i = 0; i < idCount; i++) { + final ArrayList presentations = + parcel.createTypedArrayList(RemoteViews.CREATOR); + final ArrayList filters = + parcel.createTypedArrayList(DatasetFieldFilter.CREATOR); + for (int i = 0; i < ids.size(); i++) { final AutofillId id = ids.get(i); - final AutofillValue value = (valueCount > i) ? values.get(i) : null; - final RemoteViews fieldPresentation = presentations.isEmpty() ? null - : presentations.get(i); - final Pattern filter = (Pattern) filters.get(i); + final AutofillValue value = values.get(i); + final RemoteViews fieldPresentation = presentations.get(i); + final DatasetFieldFilter filter = filters.get(i); builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, filter); } builder.setAuthentication(parcel.readParcelable(null)); @@ -521,4 +522,55 @@ public final class Dataset implements Parcelable { return new Dataset[size]; } }; + + /** + * Helper class used to indicate when the service explicitly set a {@link Pattern} filter for a + * dataset field‐ we cannot use a {@link Pattern} directly because then we wouldn't be + * able to differentiate whether the service explicitly passed a {@code null} filter to disable + * filter, or when it called the methods that does not take a filter {@link Pattern}. + * + * @hide + */ + public static final class DatasetFieldFilter implements Parcelable { + + @Nullable + public final Pattern pattern; + + private DatasetFieldFilter(@Nullable Pattern pattern) { + this.pattern = pattern; + } + + @Override + public String toString() { + if (!sDebug) return super.toString(); + + // Cannot log pattern because it could contain PII + return pattern == null ? "null" : pattern.pattern().length() + "_chars"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeSerializable(pattern); + } + + @SuppressWarnings("hiding") + public static final Creator CREATOR = + new Creator() { + + @Override + public DatasetFieldFilter createFromParcel(Parcel parcel) { + return new DatasetFieldFilter((Pattern) parcel.readSerializable()); + } + + @Override + public DatasetFieldFilter[] newArray(int size) { + return new DatasetFieldFilter[size]; + } + }; + } } diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index 5ee3cbfed3f..5f112c7fc77 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -27,6 +27,7 @@ import android.content.IntentSender; import android.graphics.Point; import android.graphics.Rect; import android.service.autofill.Dataset; +import android.service.autofill.Dataset.DatasetFieldFilter; import android.service.autofill.FillResponse; import android.text.TextUtils; import android.util.Slog; @@ -58,6 +59,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +import java.util.stream.Collectors; final class FillUi { private static final String TAG = "FillUi"; @@ -185,7 +187,7 @@ final class FillUi { final ArrayList items = new ArrayList<>(totalItems); if (header != null) { if (sVerbose) Slog.v(TAG, "adding header"); - items.add(new ViewItem(null, null, null, header)); + items.add(new ViewItem(null, null, false, null, header)); } for (int i = 0; i < datasetCount; i++) { final Dataset dataset = response.getDatasets().get(i); @@ -205,21 +207,32 @@ final class FillUi { Slog.e(TAG, "Error inflating remote views", e); continue; } - final Pattern filter = dataset.getFilter(index); + final DatasetFieldFilter filter = dataset.getFilter(index); + Pattern filterPattern = null; String valueText = null; + boolean filterable = true; if (filter == null) { final AutofillValue value = dataset.getFieldValues().get(index); if (value != null && value.isText()) { valueText = value.getTextValue().toString().toLowerCase(); } + } else { + filterPattern = filter.pattern; + if (filterPattern == null) { + if (sVerbose) { + Slog.v(TAG, "Explicitly disabling filter at id " + focusedViewId + + " for dataset #" + index); + } + filterable = false; + } } - items.add(new ViewItem(dataset, filter, valueText, view)); + items.add(new ViewItem(dataset, filterPattern, filterable, valueText, view)); } } if (footer != null) { if (sVerbose) Slog.v(TAG, "adding footer"); - items.add(new ViewItem(null, null, null, footer)); + items.add(new ViewItem(null, null, false, null, footer)); } mAdapter = new ItemsAdapter(items); @@ -354,7 +367,7 @@ final class FillUi { MeasureSpec.AT_MOST); final int itemCount = mAdapter.getCount(); for (int i = 0; i < itemCount; i++) { - View view = mAdapter.getItem(i).view; + final View view = mAdapter.getItem(i).view; view.measure(widthMeasureSpec, heightMeasureSpec); final int clampedMeasuredWidth = Math.min(view.getMeasuredWidth(), maxSize.x); final int newContentWidth = Math.max(mContentWidth, clampedMeasuredWidth); @@ -400,13 +413,62 @@ final class FillUi { public final @Nullable Dataset dataset; public final @NonNull View view; public final @Nullable Pattern filter; + public final boolean filterable; - ViewItem(@Nullable Dataset dataset, @Nullable Pattern filter, @Nullable String value, - @NonNull View view) { + /** + * Default constructor. + * + * @param dataset dataset associated with the item or {@code null} if it's a header or + * footer (TODO(b/69796626): make @NonNull if header/footer is refactored out of the list) + * @param filter optional filter set by the service to determine how the item should be + * filtered + * @param filterable optional flag set by the service to indicate this item should not be + * filtered (typically used when the dataset has value but it's sensitive, like a password) + * @param value dataset value + * @param view dataset presentation. + */ + ViewItem(@Nullable Dataset dataset, @Nullable Pattern filter, boolean filterable, + @Nullable String value, @NonNull View view) { this.dataset = dataset; this.value = value; this.view = view; this.filter = filter; + this.filterable = filterable; + } + + /** + * Returns whether this item matches the value input by the user so it can be included + * in the filtered datasets. + */ + public boolean matches(CharSequence filterText) { + if (TextUtils.isEmpty(filterText)) { + // Always show item when the user input is empty + return true; + } + if (!filterable) { + // Service explicitly disabled filtering using a null Pattern. + return false; + } + final String constraintLowerCase = filterText.toString().toLowerCase(); + if (filter != null) { + // Uses pattern provided by service + return filter.matcher(constraintLowerCase).matches(); + } else { + // Compares it with dataset value with dataset + return (value == null) + ? (dataset.getAuthentication() == null) + : value.toLowerCase().startsWith(constraintLowerCase); + } + } + + @Override + public String toString() { + return "ViewItem: [dataset=" + (dataset == null ? "null" : dataset.getId()) + + ", value=" + (value == null ? "null" : value.length() + "_chars") + + ", filterable=" + filterable + + ", filter=" + (filter == null ? "null" : filter.pattern().length() + "_chars") + + ", view=" + view.getAutofillId() + + "]"; } } @@ -509,7 +571,7 @@ final class FillUi { public void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null); pw.print(prefix); pw.print("mListView: "); pw.println(mListView); - pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter != null); + pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter); pw.print(prefix); pw.print("mFilterText: "); Helper.printlnRedactedText(pw, mFilterText); pw.print(prefix); pw.print("mContentWidth: "); pw.println(mContentWidth); @@ -556,33 +618,14 @@ final class FillUi { public Filter getFilter() { return new Filter() { @Override - protected FilterResults performFiltering(CharSequence constraint) { + protected FilterResults performFiltering(CharSequence filterText) { // No locking needed as mAllItems is final an immutable + final List filtered = mAllItems.stream() + .filter((item) -> item.matches(filterText)) + .collect(Collectors.toList()); final FilterResults results = new FilterResults(); - if (TextUtils.isEmpty(constraint)) { - results.values = mAllItems; - results.count = mAllItems.size(); - return results; - } - final List filteredItems = new ArrayList<>(); - final String constraintLowerCase = constraint.toString().toLowerCase(); - final int itemCount = mAllItems.size(); - for (int i = 0; i < itemCount; i++) { - final ViewItem item = mAllItems.get(i); - final boolean matches; - if (item.filter != null) { - matches = item.filter.matcher(constraintLowerCase).matches(); - } else { - matches = (item.value == null) - ? (item.dataset.getAuthentication() == null) - : item.value.toLowerCase().startsWith(constraintLowerCase); - } - if (matches) { - filteredItems.add(item); - } - } - results.values = filteredItems; - results.count = filteredItems.size(); + results.values = filtered; + results.count = filtered.size(); return results; } @@ -624,6 +667,11 @@ final class FillUi { public View getView(int position, View convertView, ViewGroup parent) { return getItem(position).view; } + + @Override + public String toString() { + return "ItemsAdapter: [all=" + mAllItems + ", filtered=" + mFilteredItems + "]"; + } } private final class AnnounceFilterResult implements Runnable { -- GitLab From 742ebd45131049a9f231adf37d7bb8710ba8d9b3 Mon Sep 17 00:00:00 2001 From: Jeff Vander Stoep Date: Mon, 29 Jan 2018 12:12:10 -0800 Subject: [PATCH 172/416] reland: pm: Apps with shared UID must also share selinux domain There are two existing cases where apps that share a sharedUserId potentially end up in separate SELinux domains. 1. An app installed in /system/priv-app runs in the priv_app domain. An app installed on the /data partition which shares a sharedUserId with that priv_app would run in the untrusted_app domain (e.g. GTS b/72235911). This issue has existed since Android N. 2. The untrusted_app domain may now deprecate permissions based on targetSdkVersion, so apps with sharedUserId may have different permissions based on which targetSdkVersion they use. This issue has existed since Android O, but is particularly problematic for feature "Deprecate world accessible app data" which puts every app targeting P+ into its own selinux domain. This change fixes #1 and adds a temporary workaround to prevent #2. Updated version considers both SharedUserSetting.isPrivileged() and pkg.isPrivileged() for the case where the app has a shared User. Test: cts-tradefed run cts -m CtsSelinuxTargetSdkCurrentTestCases Test: cts-tradefed run cts -m CtsSelinuxTargetSdk27TestCases Test: cts-tradefed run cts -m CtsSelinuxTargetSdk25TestCases Test: confirm via packages.list that apps end up in the same selinux domain before and after this patch. Bug: 72290969 Change-Id: I974bea88004622b70633fdeb54a1372fd04c6eff --- .../server/pm/PackageManagerService.java | 18 +++++++++++++++--- .../com/android/server/pm/SELinuxMMAC.java | 8 +++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 01c44af586e..b8a866b34b8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -9991,8 +9991,7 @@ Slog.e("TODD", // priv-apps. synchronized (mPackages) { PackageSetting platformPkgSetting = mSettings.mPackages.get("android"); - if (!pkg.packageName.equals("android") - && (compareSignatures(platformPkgSetting.signatures.mSigningDetails.signatures, + if ((compareSignatures(platformPkgSetting.signatures.mSigningDetails.signatures, pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH)) { scanFlags |= SCAN_AS_PRIVILEGED; } @@ -10459,7 +10458,20 @@ Slog.e("TODD", pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; } - SELinuxMMAC.assignSeInfoValue(pkg); + // SELinux sandboxes become more restrictive as targetSdkVersion increases. + // To ensure that apps with sharedUserId are placed in the same selinux domain + // without breaking any assumptions about access, put them into the least + // restrictive targetSdkVersion=25 domain. + // TODO(b/72290969): Base this on the actual targetSdkVersion(s) of the apps within the + // sharedUserSetting, instead of defaulting to the least restrictive domain. + final int targetSdk = (sharedUserSetting != null) ? 25 + : pkg.applicationInfo.targetSdkVersion; + // TODO(b/71593002): isPrivileged for sharedUser and appInfo should never be out of sync. + // They currently can be if the sharedUser apps are signed with the platform key. + final boolean isPrivileged = (sharedUserSetting != null) ? + sharedUserSetting.isPrivileged() | pkg.isPrivileged() : pkg.isPrivileged(); + + SELinuxMMAC.assignSeInfoValue(pkg, isPrivileged, targetSdk); pkg.mExtras = pkgSetting; pkg.applicationInfo.processName = fixProcessName( diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index fe5b3d456b8..a9f15282133 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -315,7 +315,8 @@ public final class SELinuxMMAC { * * @param pkg object representing the package to be labeled. */ - public static void assignSeInfoValue(PackageParser.Package pkg) { + public static void assignSeInfoValue(PackageParser.Package pkg, boolean isPrivileged, + int targetSdkVersion) { synchronized (sPolicies) { if (!sPolicyRead) { if (DEBUG_POLICY) { @@ -335,10 +336,11 @@ public final class SELinuxMMAC { if (pkg.applicationInfo.targetSandboxVersion == 2) pkg.applicationInfo.seInfo += SANDBOX_V2_STR; - if (pkg.applicationInfo.isPrivilegedApp()) + if (isPrivileged) { pkg.applicationInfo.seInfo += PRIVILEGED_APP_STR; + } - pkg.applicationInfo.seInfo += TARGETSDKVERSION_STR + pkg.applicationInfo.targetSdkVersion; + pkg.applicationInfo.seInfo += TARGETSDKVERSION_STR + targetSdkVersion; if (DEBUG_POLICY_INSTALL) { Slog.i(TAG, "package (" + pkg.packageName + ") labeled with " + -- GitLab From 08063d6c1bb64d2b65fe08bfc04d00f3f0fc856f Mon Sep 17 00:00:00 2001 From: Jack Yu Date: Wed, 31 Jan 2018 00:33:20 -0800 Subject: [PATCH 173/416] Added support for the new 1.2 data setup/deactivate API Added support for the 1.2 IRadio APIs that support IWLAN handover. Test: Telephony sanity tests Bug: 64132030 Change-Id: I8c962bb45bc4d42610faa32f0ee36080e8e6cb65 --- .../java/android/telephony/ServiceState.java | 37 ++++++++++++++++++- .../internal/telephony/RILConstants.java | 5 --- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 90a3677d179..1176491907c 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -22,13 +22,13 @@ import android.annotation.SystemApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.telephony.AccessNetworkConstants.AccessNetworkType; import android.text.TextUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; - import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -1339,6 +1339,39 @@ public class ServiceState implements Parcelable { } } + /** @hide */ + public static int rilRadioTechnologyToAccessNetworkType(@RilRadioTechnology int rt) { + switch(rt) { + case RIL_RADIO_TECHNOLOGY_GPRS: + case RIL_RADIO_TECHNOLOGY_EDGE: + case RIL_RADIO_TECHNOLOGY_GSM: + return AccessNetworkType.GERAN; + case RIL_RADIO_TECHNOLOGY_UMTS: + case RIL_RADIO_TECHNOLOGY_HSDPA: + case RIL_RADIO_TECHNOLOGY_HSPAP: + case RIL_RADIO_TECHNOLOGY_HSUPA: + case RIL_RADIO_TECHNOLOGY_HSPA: + case RIL_RADIO_TECHNOLOGY_TD_SCDMA: + return AccessNetworkType.UTRAN; + case RIL_RADIO_TECHNOLOGY_IS95A: + case RIL_RADIO_TECHNOLOGY_IS95B: + case RIL_RADIO_TECHNOLOGY_1xRTT: + case RIL_RADIO_TECHNOLOGY_EVDO_0: + case RIL_RADIO_TECHNOLOGY_EVDO_A: + case RIL_RADIO_TECHNOLOGY_EVDO_B: + case RIL_RADIO_TECHNOLOGY_EHRPD: + return AccessNetworkType.CDMA2000; + case RIL_RADIO_TECHNOLOGY_LTE: + case RIL_RADIO_TECHNOLOGY_LTE_CA: + return AccessNetworkType.EUTRAN; + case RIL_RADIO_TECHNOLOGY_IWLAN: + return AccessNetworkType.IWLAN; + case RIL_RADIO_TECHNOLOGY_UNKNOWN: + default: + return AccessNetworkType.UNKNOWN; + } + } + /** @hide */ public int getDataNetworkType() { return rilRadioTechnologyToNetworkType(mRilDataRadioTechnology); diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index cdee9e6f2d7..a3a30807986 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -220,11 +220,6 @@ public interface RILConstants { String SETUP_DATA_PROTOCOL_IPV6 = "IPV6"; String SETUP_DATA_PROTOCOL_IPV4V6 = "IPV4V6"; - /* Deactivate data call reasons */ - int DEACTIVATE_REASON_NONE = 0; - int DEACTIVATE_REASON_RADIO_OFF = 1; - int DEACTIVATE_REASON_PDP_RESET = 2; - /* NV config radio reset types. */ int NV_CONFIG_RELOAD_RESET = 1; int NV_CONFIG_ERASE_RESET = 2; -- GitLab From 57426f516ea35df9ec073b2f2832b54f5fac35ef Mon Sep 17 00:00:00 2001 From: Tony Mantler Date: Wed, 31 Jan 2018 18:06:28 +0000 Subject: [PATCH 174/416] Revert "Ensure keyguard is considered unlocked on devices that disable it." This reverts commit ef344a3de33eb6979074b8636cc848c64fb5d039. Reason for revert: Patch re-opens b/37221109 and makes devices with restricted profile unusable after rebooting Bug: 71551000 Test: Rebooted device with restricted profile present Change-Id: Ibc5d2843bda7828123e5017a38bab6b6adf67e8b --- .../com/android/systemui/keyguard/KeyguardViewMediator.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 8501519d26a..653e5000f72 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -707,10 +707,6 @@ public class KeyguardViewMediator extends SystemUI { && !mLockPatternUtils.isLockScreenDisabled( KeyguardUpdateMonitor.getCurrentUser()), mSecondaryDisplayShowing, true /* forceCallbacks */); - } else { - // The system's keyguard is disabled or missing. - setShowingLocked(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser()), - mSecondaryDisplayShowing, true); } mStatusBarKeyguardViewManager = -- GitLab From 94a1f0275c9946a26f3619ea9d0e2e745e062970 Mon Sep 17 00:00:00 2001 From: Patrick Baumann Date: Wed, 31 Jan 2018 02:30:36 +0000 Subject: [PATCH 175/416] Revert "Removes EphemrealResolverService and related" This reverts commit 5564f880db3292327872a07df8e230eee78be14b. Reason for revert: Resolve merge conflict for another revert (ag/3537193) Bug: 72710855 Change-Id: Id7c3a3993a45c588ee4668d7486d67d764541b1e (cherry picked from commit 1e0c91968e802d49c26e2e8d6ca6e8d31f451894) --- api/current.txt | 18 +- api/system-removed.txt | 50 ++++ .../android/app/EphemeralResolverService.java | 104 ++++++++ .../app/InstantAppResolverService.java | 10 +- core/java/android/content/Intent.java | 30 +++ .../content/pm/EphemeralIntentFilter.java | 85 +++++++ .../content/pm/EphemeralResolveInfo.java | 224 ++++++++++++++++++ ....java => EphemeralResolverConnection.java} | 43 ++-- .../android/server/pm/InstantAppResolver.java | 28 +-- .../server/pm/PackageManagerService.java | 71 ++++-- 10 files changed, 591 insertions(+), 72 deletions(-) create mode 100644 core/java/android/app/EphemeralResolverService.java create mode 100644 core/java/android/content/pm/EphemeralIntentFilter.java create mode 100644 core/java/android/content/pm/EphemeralResolveInfo.java rename services/core/java/com/android/server/pm/{InstantAppResolverConnection.java => EphemeralResolverConnection.java} (91%) diff --git a/api/current.txt b/api/current.txt index f5da9b05e6b..7f07f810afa 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11613,15 +11613,15 @@ package android.content.res { public final class AssetManager implements java.lang.AutoCloseable { method public void close(); - method public final java.lang.String[] getLocales(); - method public final java.lang.String[] list(java.lang.String) throws java.io.IOException; - method public final java.io.InputStream open(java.lang.String) throws java.io.IOException; - method public final java.io.InputStream open(java.lang.String, int) throws java.io.IOException; - method public final android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException; - method public final android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException; - method public final android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException; - method public final android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException; - method public final android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException; + method public java.lang.String[] getLocales(); + method public java.lang.String[] list(java.lang.String) throws java.io.IOException; + method public java.io.InputStream open(java.lang.String) throws java.io.IOException; + method public java.io.InputStream open(java.lang.String, int) throws java.io.IOException; + method public android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException; + method public android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException; + method public android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException; + method public android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException; + method public android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException; field public static final int ACCESS_BUFFER = 3; // 0x3 field public static final int ACCESS_RANDOM = 1; // 0x1 field public static final int ACCESS_STREAMING = 2; // 0x2 diff --git a/api/system-removed.txt b/api/system-removed.txt index 48f43e0880d..b63703dc003 100644 --- a/api/system-removed.txt +++ b/api/system-removed.txt @@ -1,5 +1,13 @@ package android.app { + public abstract deprecated class EphemeralResolverService extends android.app.InstantAppResolverService { + ctor public EphemeralResolverService(); + method public android.os.Looper getLooper(); + method public abstract deprecated java.util.List onEphemeralResolveInfoList(int[], int); + method public android.content.pm.EphemeralResolveInfo onGetEphemeralIntentFilter(java.lang.String); + method public java.util.List onGetEphemeralResolveInfo(int[]); + } + public class Notification implements android.os.Parcelable { method public static java.lang.Class getNotificationStyleClass(java.lang.String); } @@ -23,7 +31,10 @@ package android.content { public class Intent implements java.lang.Cloneable android.os.Parcelable { field public static final deprecated java.lang.String ACTION_DEVICE_INITIALIZATION_WIZARD = "android.intent.action.DEVICE_INITIALIZATION_WIZARD"; + field public static final deprecated java.lang.String ACTION_EPHEMERAL_RESOLVER_SETTINGS = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS"; + field public static final deprecated java.lang.String ACTION_INSTALL_EPHEMERAL_PACKAGE = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE"; field public static final deprecated java.lang.String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR"; + field public static final deprecated java.lang.String ACTION_RESOLVE_EPHEMERAL_PACKAGE = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE"; field public static final deprecated java.lang.String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE"; field public static final deprecated java.lang.String EXTRA_CDMA_DEFAULT_ROAMING_INDICATOR = "cdmaDefaultRoamingIndicator"; field public static final deprecated java.lang.String EXTRA_CDMA_ROAMING_INDICATOR = "cdmaRoamingIndicator"; @@ -51,6 +62,45 @@ package android.content { } +package android.content.pm { + + public final deprecated class EphemeralIntentFilter implements android.os.Parcelable { + ctor public EphemeralIntentFilter(java.lang.String, java.util.List); + method public int describeContents(); + method public java.util.List getFilters(); + method public java.lang.String getSplitName(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + + public final deprecated class EphemeralResolveInfo implements android.os.Parcelable { + ctor public deprecated EphemeralResolveInfo(android.net.Uri, java.lang.String, java.util.List); + ctor public deprecated EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List); + ctor public EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List, int); + ctor public EphemeralResolveInfo(java.lang.String, java.lang.String, java.util.List); + method public int describeContents(); + method public byte[] getDigestBytes(); + method public int getDigestPrefix(); + method public deprecated java.util.List getFilters(); + method public java.util.List getIntentFilters(); + method public java.lang.String getPackageName(); + method public int getVersionCode(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + field public static final java.lang.String SHA_ALGORITHM = "SHA-256"; + } + + public static final class EphemeralResolveInfo.EphemeralDigest implements android.os.Parcelable { + ctor public EphemeralResolveInfo.EphemeralDigest(java.lang.String); + method public int describeContents(); + method public byte[][] getDigestBytes(); + method public int[] getDigestPrefix(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator CREATOR; + } + +} + package android.media.tv { public final class TvInputManager { diff --git a/core/java/android/app/EphemeralResolverService.java b/core/java/android/app/EphemeralResolverService.java new file mode 100644 index 00000000000..d1c244136ec --- /dev/null +++ b/core/java/android/app/EphemeralResolverService.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.annotation.SystemApi; +import android.content.pm.EphemeralResolveInfo; +import android.content.pm.InstantAppResolveInfo; +import android.os.Build; +import android.os.Looper; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Base class for implementing the resolver service. + * @hide + * @removed + * @deprecated use InstantAppResolverService instead + */ +@Deprecated +@SystemApi +public abstract class EphemeralResolverService extends InstantAppResolverService { + private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; + private static final String TAG = "PackageManager"; + + /** + * Called to retrieve resolve info for ephemeral applications. + * + * @param digestPrefix The hash prefix of the ephemeral's domain. + * @param prefixMask A mask that was applied to each digest prefix. This should + * be used when comparing against the digest prefixes as all bits might + * not be set. + * @deprecated use {@link #onGetEphemeralResolveInfo(int[])} instead + */ + @Deprecated + public abstract List onEphemeralResolveInfoList( + int digestPrefix[], int prefix); + + /** + * Called to retrieve resolve info for ephemeral applications. + * + * @param digestPrefix The hash prefix of the ephemeral's domain. + */ + public List onGetEphemeralResolveInfo(int digestPrefix[]) { + return onEphemeralResolveInfoList(digestPrefix, 0xFFFFF000); + } + + /** + * Called to retrieve intent filters for ephemeral applications. + * + * @param hostName The name of the host to get intent filters for. + */ + public EphemeralResolveInfo onGetEphemeralIntentFilter(String hostName) { + throw new IllegalStateException("Must define"); + } + + @Override + public Looper getLooper() { + return super.getLooper(); + } + + void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, + InstantAppResolutionCallback callback) { + if (DEBUG_EPHEMERAL) { + Log.d(TAG, "Legacy resolver; getInstantAppResolveInfo;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + final List response = onGetEphemeralResolveInfo(digestPrefix); + final int responseSize = response == null ? 0 : response.size(); + final List resultList = new ArrayList<>(responseSize); + for (int i = 0; i < responseSize; i++) { + resultList.add(response.get(i).getInstantAppResolveInfo()); + } + callback.onInstantAppResolveInfo(resultList); + } + + void _onGetInstantAppIntentFilter(int[] digestPrefix, String token, + String hostName, InstantAppResolutionCallback callback) { + if (DEBUG_EPHEMERAL) { + Log.d(TAG, "Legacy resolver; getInstantAppIntentFilter;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + final EphemeralResolveInfo response = onGetEphemeralIntentFilter(hostName); + final List resultList = new ArrayList<>(1); + resultList.add(response.getInstantAppResolveInfo()); + callback.onInstantAppResolveInfo(resultList); + } +} diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java index 76a36820ed8..89aff36f883 100644 --- a/core/java/android/app/InstantAppResolverService.java +++ b/core/java/android/app/InstantAppResolverService.java @@ -43,7 +43,7 @@ import java.util.List; */ @SystemApi public abstract class InstantAppResolverService extends Service { - private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; private static final String TAG = "PackageManager"; /** @hide */ @@ -133,7 +133,7 @@ public abstract class InstantAppResolverService extends Service { @Override public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix, String token, int sequence, IRemoteCallback callback) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Phase1 called; posting"); } final SomeArgs args = SomeArgs.obtain(); @@ -148,7 +148,7 @@ public abstract class InstantAppResolverService extends Service { @Override public void getInstantAppIntentFilterList(Intent sanitizedIntent, int[] digestPrefix, String token, IRemoteCallback callback) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Phase2 called; posting"); } final SomeArgs args = SomeArgs.obtain(); @@ -203,7 +203,7 @@ public abstract class InstantAppResolverService extends Service { final String token = (String) args.arg3; final Intent intent = (Intent) args.arg4; final int sequence = message.arg1; - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "[" + token + "] Phase1 request;" + " prefix: " + Arrays.toString(digestPrefix)); } @@ -217,7 +217,7 @@ public abstract class InstantAppResolverService extends Service { final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; final Intent intent = (Intent) args.arg4; - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "[" + token + "] Phase2 request;" + " prefix: " + Arrays.toString(digestPrefix)); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 908465efa0c..b3c8737a283 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1553,6 +1553,16 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_INSTALL_FAILURE = "android.intent.action.INSTALL_FAILURE"; + /** + * @hide + * @removed + * @deprecated Do not use. This will go away. + * Replace with {@link #ACTION_INSTALL_INSTANT_APP_PACKAGE}. + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE + = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE"; /** * Activity Action: Launch instant application installer. *

    @@ -1566,6 +1576,16 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_INSTALL_INSTANT_APP_PACKAGE = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE"; + /** + * @hide + * @removed + * @deprecated Do not use. This will go away. + * Replace with {@link #ACTION_RESOLVE_INSTANT_APP_PACKAGE}. + */ + @SystemApi + @SdkConstant(SdkConstantType.SERVICE_ACTION) + public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE + = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE"; /** * Service Action: Resolve instant application. *

    @@ -1580,6 +1600,16 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE"; + /** + * @hide + * @removed + * @deprecated Do not use. This will go away. + * Replace with {@link #ACTION_INSTANT_APP_RESOLVER_SETTINGS}. + */ + @SystemApi + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_EPHEMERAL_RESOLVER_SETTINGS + = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS"; /** * Activity Action: Launch instant app settings. * diff --git a/core/java/android/content/pm/EphemeralIntentFilter.java b/core/java/android/content/pm/EphemeralIntentFilter.java new file mode 100644 index 00000000000..1dbbf816ed9 --- /dev/null +++ b/core/java/android/content/pm/EphemeralIntentFilter.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.IntentFilter; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Information about an ephemeral application intent filter. + * @hide + * @removed + */ +@Deprecated +@SystemApi +public final class EphemeralIntentFilter implements Parcelable { + private final InstantAppIntentFilter mInstantAppIntentFilter; + + public EphemeralIntentFilter(@Nullable String splitName, @NonNull List filters) { + mInstantAppIntentFilter = new InstantAppIntentFilter(splitName, filters); + } + + EphemeralIntentFilter(@NonNull InstantAppIntentFilter intentFilter) { + mInstantAppIntentFilter = intentFilter; + } + + EphemeralIntentFilter(Parcel in) { + mInstantAppIntentFilter = in.readParcelable(null /*loader*/); + } + + public String getSplitName() { + return mInstantAppIntentFilter.getSplitName(); + } + + public List getFilters() { + return mInstantAppIntentFilter.getFilters(); + } + + /** @hide */ + InstantAppIntentFilter getInstantAppIntentFilter() { + return mInstantAppIntentFilter; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(mInstantAppIntentFilter, flags); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public EphemeralIntentFilter createFromParcel(Parcel in) { + return new EphemeralIntentFilter(in); + } + @Override + public EphemeralIntentFilter[] newArray(int size) { + return new EphemeralIntentFilter[size]; + } + }; +} diff --git a/core/java/android/content/pm/EphemeralResolveInfo.java b/core/java/android/content/pm/EphemeralResolveInfo.java new file mode 100644 index 00000000000..12131a3ebc9 --- /dev/null +++ b/core/java/android/content/pm/EphemeralResolveInfo.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.content.IntentFilter; +import android.content.pm.InstantAppResolveInfo.InstantAppDigest; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * Information about an ephemeral application. + * @hide + * @removed + */ +@Deprecated +@SystemApi +public final class EphemeralResolveInfo implements Parcelable { + /** Algorithm that will be used to generate the domain digest */ + public static final String SHA_ALGORITHM = "SHA-256"; + + private final InstantAppResolveInfo mInstantAppResolveInfo; + @Deprecated + private final List mLegacyFilters; + + @Deprecated + public EphemeralResolveInfo(@NonNull Uri uri, @NonNull String packageName, + @NonNull List filters) { + if (uri == null || packageName == null || filters == null || filters.isEmpty()) { + throw new IllegalArgumentException(); + } + final List ephemeralFilters = new ArrayList<>(1); + ephemeralFilters.add(new EphemeralIntentFilter(packageName, filters)); + mInstantAppResolveInfo = new InstantAppResolveInfo(uri.getHost(), packageName, + createInstantAppIntentFilterList(ephemeralFilters)); + mLegacyFilters = new ArrayList(filters.size()); + mLegacyFilters.addAll(filters); + } + + @Deprecated + public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName, + @Nullable List filters) { + this(digest, packageName, filters, -1 /*versionCode*/); + } + + public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName, + @Nullable List filters, int versionCode) { + mInstantAppResolveInfo = new InstantAppResolveInfo( + digest.getInstantAppDigest(), packageName, + createInstantAppIntentFilterList(filters), versionCode); + mLegacyFilters = null; + } + + public EphemeralResolveInfo(@NonNull String hostName, @Nullable String packageName, + @Nullable List filters) { + this(new EphemeralDigest(hostName), packageName, filters); + } + + EphemeralResolveInfo(Parcel in) { + mInstantAppResolveInfo = in.readParcelable(null /*loader*/); + mLegacyFilters = new ArrayList(); + in.readList(mLegacyFilters, null /*loader*/); + } + + /** @hide */ + public InstantAppResolveInfo getInstantAppResolveInfo() { + return mInstantAppResolveInfo; + } + + private static List createInstantAppIntentFilterList( + List filters) { + if (filters == null) { + return null; + } + final int filterCount = filters.size(); + final List returnList = new ArrayList<>(filterCount); + for (int i = 0; i < filterCount; i++) { + returnList.add(filters.get(i).getInstantAppIntentFilter()); + } + return returnList; + } + + public byte[] getDigestBytes() { + return mInstantAppResolveInfo.getDigestBytes(); + } + + public int getDigestPrefix() { + return mInstantAppResolveInfo.getDigestPrefix(); + } + + public String getPackageName() { + return mInstantAppResolveInfo.getPackageName(); + } + + public List getIntentFilters() { + final List filters = mInstantAppResolveInfo.getIntentFilters(); + final int filterCount = filters.size(); + final List returnList = new ArrayList<>(filterCount); + for (int i = 0; i < filterCount; i++) { + returnList.add(new EphemeralIntentFilter(filters.get(i))); + } + return returnList; + } + + public int getVersionCode() { + return mInstantAppResolveInfo.getVersionCode(); + } + + @Deprecated + public List getFilters() { + return mLegacyFilters; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(mInstantAppResolveInfo, flags); + out.writeList(mLegacyFilters); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public EphemeralResolveInfo createFromParcel(Parcel in) { + return new EphemeralResolveInfo(in); + } + @Override + public EphemeralResolveInfo[] newArray(int size) { + return new EphemeralResolveInfo[size]; + } + }; + + /** + * Helper class to generate and store each of the digests and prefixes + * sent to the Ephemeral Resolver. + *

    + * Since intent filters may want to handle multiple hosts within a + * domain [eg “*.google.com”], the resolver is presented with multiple + * hash prefixes. For example, "a.b.c.d.e" generates digests for + * "d.e", "c.d.e", "b.c.d.e" and "a.b.c.d.e". + * + * @hide + */ + @SystemApi + public static final class EphemeralDigest implements Parcelable { + private final InstantAppDigest mInstantAppDigest; + + public EphemeralDigest(@NonNull String hostName) { + this(hostName, -1 /*maxDigests*/); + } + + /** @hide */ + public EphemeralDigest(@NonNull String hostName, int maxDigests) { + mInstantAppDigest = new InstantAppDigest(hostName, maxDigests); + } + + EphemeralDigest(Parcel in) { + mInstantAppDigest = in.readParcelable(null /*loader*/); + } + + /** @hide */ + InstantAppDigest getInstantAppDigest() { + return mInstantAppDigest; + } + + public byte[][] getDigestBytes() { + return mInstantAppDigest.getDigestBytes(); + } + + public int[] getDigestPrefix() { + return mInstantAppDigest.getDigestPrefix(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(mInstantAppDigest, flags); + } + + @SuppressWarnings("hiding") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public EphemeralDigest createFromParcel(Parcel in) { + return new EphemeralDigest(in); + } + @Override + public EphemeralDigest[] newArray(int size) { + return new EphemeralDigest[size]; + } + }; + } +} diff --git a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java similarity index 91% rename from services/core/java/com/android/server/pm/InstantAppResolverConnection.java rename to services/core/java/com/android/server/pm/EphemeralResolverConnection.java index a9ee41162ae..6d6c960eed7 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolverConnection.java +++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java @@ -37,29 +37,34 @@ import android.util.Slog; import android.util.TimedRemoteCaller; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.TransferPipe; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.TimeoutException; /** - * Represents a remote instant app resolver. It is responsible for binding to the remote + * Represents a remote ephemeral resolver. It is responsible for binding to the remote * service and handling all interactions in a timely manner. * @hide */ -final class InstantAppResolverConnection implements DeathRecipient { +final class EphemeralResolverConnection implements DeathRecipient { private static final String TAG = "PackageManager"; // This is running in a critical section and the timeout must be sufficiently low private static final long BIND_SERVICE_TIMEOUT_MS = Build.IS_ENG ? 500 : 300; private static final long CALL_SERVICE_TIMEOUT_MS = Build.IS_ENG ? 200 : 100; - private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; private final Object mLock = new Object(); - private final GetInstantAppResolveInfoCaller mGetInstantAppResolveInfoCaller = - new GetInstantAppResolveInfoCaller(); + private final GetEphemeralResolveInfoCaller mGetEphemeralResolveInfoCaller = + new GetEphemeralResolveInfoCaller(); private final ServiceConnection mServiceConnection = new MyServiceConnection(); private final Context mContext; /** Intent used to bind to the service */ @@ -74,7 +79,7 @@ final class InstantAppResolverConnection implements DeathRecipient { @GuardedBy("mLock") private IInstantAppResolver mRemoteInstance; - public InstantAppResolverConnection( + public EphemeralResolverConnection( Context context, ComponentName componentName, String action) { mContext = context; mIntent = new Intent(action).setComponent(componentName); @@ -93,8 +98,8 @@ final class InstantAppResolverConnection implements DeathRecipient { throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED); } try { - return mGetInstantAppResolveInfoCaller - .getInstantAppResolveInfoList(target, sanitizedIntent, hashPrefix, token); + return mGetEphemeralResolveInfoCaller + .getEphemeralResolveInfoList(target, sanitizedIntent, hashPrefix, token); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_CALL); } catch (RemoteException ignore) { @@ -166,7 +171,7 @@ final class InstantAppResolverConnection implements DeathRecipient { if (mBindState == STATE_PENDING) { // there is a pending bind, let's see if we can use it. - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.i(TAG, "[" + token + "] Previous bind timed out; waiting for connection"); } try { @@ -183,7 +188,7 @@ final class InstantAppResolverConnection implements DeathRecipient { if (mBindState == STATE_BINDING) { // someone was binding when we called bind(), or they raced ahead while we were // waiting in the PENDING case; wait for their result instead. Last chance! - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.i(TAG, "[" + token + "] Another thread is binding; waiting for connection"); } waitForBindLocked(token); @@ -201,12 +206,12 @@ final class InstantAppResolverConnection implements DeathRecipient { IInstantAppResolver instance = null; try { if (doUnbind) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.i(TAG, "[" + token + "] Previous connection never established; rebinding"); } mContext.unbindService(mServiceConnection); } - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Binding to instant app resolver"); } final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; @@ -242,7 +247,7 @@ final class InstantAppResolverConnection implements DeathRecipient { @Override public void binderDied() { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Binder to instant app resolver died"); } synchronized (mLock) { @@ -281,7 +286,7 @@ final class InstantAppResolverConnection implements DeathRecipient { private final class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Connected to instant app resolver"); } synchronized (mLock) { @@ -290,7 +295,7 @@ final class InstantAppResolverConnection implements DeathRecipient { mBindState = STATE_IDLE; } try { - service.linkToDeath(InstantAppResolverConnection.this, 0 /*flags*/); + service.linkToDeath(EphemeralResolverConnection.this, 0 /*flags*/); } catch (RemoteException e) { handleBinderDiedLocked(); } @@ -300,7 +305,7 @@ final class InstantAppResolverConnection implements DeathRecipient { @Override public void onServiceDisconnected(ComponentName name) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Disconnected from instant app resolver"); } synchronized (mLock) { @@ -309,11 +314,11 @@ final class InstantAppResolverConnection implements DeathRecipient { } } - private static final class GetInstantAppResolveInfoCaller + private static final class GetEphemeralResolveInfoCaller extends TimedRemoteCaller> { private final IRemoteCallback mCallback; - public GetInstantAppResolveInfoCaller() { + public GetEphemeralResolveInfoCaller() { super(CALL_SERVICE_TIMEOUT_MS); mCallback = new IRemoteCallback.Stub() { @Override @@ -328,7 +333,7 @@ final class InstantAppResolverConnection implements DeathRecipient { }; } - public List getInstantAppResolveInfoList( + public List getEphemeralResolveInfoList( IInstantAppResolver target, Intent sanitizedIntent, int hashPrefix[], String token) throws RemoteException, TimeoutException { final int sequence = onBeforeRemoteCall(); diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index 00fdb9d687d..55212cc6b3d 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -51,8 +51,8 @@ import android.util.Slog; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; -import com.android.server.pm.InstantAppResolverConnection.ConnectionException; -import com.android.server.pm.InstantAppResolverConnection.PhaseTwoCallback; +import com.android.server.pm.EphemeralResolverConnection.ConnectionException; +import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -66,7 +66,7 @@ import java.util.UUID; /** @hide */ public abstract class InstantAppResolver { - private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; private static final String TAG = "PackageManager"; private static final int RESOLUTION_SUCCESS = 0; @@ -117,10 +117,10 @@ public abstract class InstantAppResolver { } public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne( - InstantAppResolverConnection connection, InstantAppRequest requestObj) { + EphemeralResolverConnection connection, InstantAppRequest requestObj) { final long startTime = System.currentTimeMillis(); final String token = UUID.randomUUID().toString(); - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] Phase1; resolving"); } final Intent origIntent = requestObj.origIntent; @@ -152,7 +152,7 @@ public abstract class InstantAppResolver { logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token, resolutionStatus); } - if (DEBUG_INSTANT && resolveInfo == null) { + if (DEBUG_EPHEMERAL && resolveInfo == null) { if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) { Log.d(TAG, "[" + token + "] Phase1; bind timed out"); } else if (resolutionStatus == RESOLUTION_CALL_TIMEOUT) { @@ -173,11 +173,11 @@ public abstract class InstantAppResolver { } public static void doInstantAppResolutionPhaseTwo(Context context, - InstantAppResolverConnection connection, InstantAppRequest requestObj, + EphemeralResolverConnection connection, InstantAppRequest requestObj, ActivityInfo instantAppInstaller, Handler callbackHandler) { final long startTime = System.currentTimeMillis(); final String token = requestObj.responseObj.token; - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] Phase2; resolving"); } final Intent origIntent = requestObj.origIntent; @@ -234,7 +234,7 @@ public abstract class InstantAppResolver { } logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token, resolutionStatus); - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) { Log.d(TAG, "[" + token + "] Phase2; bind timed out"); } else { @@ -444,13 +444,13 @@ public abstract class InstantAppResolver { return null; } // No filters; we need to start phase two - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Log.d(TAG, "No app filters; go to phase 2"); } return Collections.emptyList(); } - final PackageManagerService.InstantAppIntentResolver instantAppResolver = - new PackageManagerService.InstantAppIntentResolver(); + final PackageManagerService.EphemeralIntentResolver instantAppResolver = + new PackageManagerService.EphemeralIntentResolver(); for (int j = instantAppFilters.size() - 1; j >= 0; --j) { final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); final List splitFilters = instantAppFilter.getFilters(); @@ -481,11 +481,11 @@ public abstract class InstantAppResolver { instantAppResolver.queryIntent( origIntent, resolvedType, false /*defaultOnly*/, userId); if (!matchedResolveInfoList.isEmpty()) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList); } return matchedResolveInfoList; - } else if (DEBUG_INSTANT) { + } else if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] No matches found" + " package: " + instantAppInfo.getPackageName() + ", versionCode: " + instantAppInfo.getVersionCode()); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index a83dc7b4047..18c5ffdc2ff 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -424,7 +424,7 @@ public class PackageManagerService extends IPackageManager.Stub public static final boolean DEBUG_DEXOPT = false; private static final boolean DEBUG_ABI_SELECTION = false; - private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; private static final boolean DEBUG_TRIAGED_MISSING = false; private static final boolean DEBUG_APP_DATA = false; @@ -981,7 +981,7 @@ public class PackageManagerService extends IPackageManager.Stub private int mIntentFilterVerificationToken = 0; /** The service connection to the ephemeral resolver */ - final InstantAppResolverConnection mInstantAppResolverConnection; + final EphemeralResolverConnection mInstantAppResolverConnection; /** Component used to show resolver settings for Instant Apps */ final ComponentName mInstantAppResolverSettingsComponent; @@ -3149,10 +3149,10 @@ Slog.e("TODD", final Pair instantAppResolverComponent = getInstantAppResolverLPr(); if (instantAppResolverComponent != null) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Set ephemeral resolver: " + instantAppResolverComponent); } - mInstantAppResolverConnection = new InstantAppResolverConnection( + mInstantAppResolverConnection = new EphemeralResolverConnection( mContext, instantAppResolverComponent.first, instantAppResolverComponent.second); mInstantAppResolverSettingsComponent = @@ -3532,7 +3532,7 @@ Slog.e("TODD", final String[] packageArray = mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage); if (packageArray.length == 0 && !Build.IS_DEBUGGABLE) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Ephemeral resolver NOT found; empty package list"); } return null; @@ -3547,9 +3547,19 @@ Slog.e("TODD", final Intent resolverIntent = new Intent(actionName); List resolvers = queryIntentServicesInternal(resolverIntent, null, resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/); + // temporarily look for the old action + if (resolvers.size() == 0) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral resolver not found with new action; try old one"); + } + actionName = Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE; + resolverIntent.setAction(actionName); + resolvers = queryIntentServicesInternal(resolverIntent, null, + resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/); + } final int N = resolvers.size(); if (N == 0) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Ephemeral resolver NOT found; no matching intent filters"); } return null; @@ -3565,20 +3575,20 @@ Slog.e("TODD", final String packageName = info.serviceInfo.packageName; if (!possiblePackages.contains(packageName) && !Build.IS_DEBUGGABLE) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Ephemeral resolver not in allowed package list;" + " pkg: " + packageName + ", info:" + info); } continue; } - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Ephemeral resolver found;" + " pkg: " + packageName + ", info:" + info); } return new Pair<>(new ComponentName(packageName, info.serviceInfo.name), actionName); } - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Ephemeral resolver NOT found"); } return null; @@ -3588,9 +3598,11 @@ Slog.e("TODD", String[] orderedActions = Build.IS_ENG ? new String[]{ Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE + "_TEST", - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE} + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, + Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE} : new String[]{ - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE}; + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, + Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE}; final int resolveFlags = MATCH_DIRECT_BOOT_AWARE @@ -3606,7 +3618,7 @@ Slog.e("TODD", matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, resolveFlags, UserHandle.USER_SYSTEM); if (matches.isEmpty()) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Instant App installer not found with " + action); } } else { @@ -3644,6 +3656,15 @@ Slog.e("TODD", final int resolveFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; List matches = queryIntentActivitiesInternal(intent, null, resolveFlags, UserHandle.USER_SYSTEM); + // temporarily look for the old action + if (matches.isEmpty()) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral resolver settings not found with new action; try old one"); + } + intent.setAction(Intent.ACTION_EPHEMERAL_RESOLVER_SETTINGS); + matches = queryIntentActivitiesInternal(intent, null, resolveFlags, + UserHandle.USER_SYSTEM); + } if (matches.isEmpty()) { return null; } @@ -5986,7 +6007,7 @@ Slog.e("TODD", final int status = (int) (packedStatus >> 32); if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName + ", status: " + status); } @@ -5994,7 +6015,7 @@ Slog.e("TODD", } } if (ps.getInstantApp(userId)) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "DENY instant app installed;" + " pkg: " + packageName); } @@ -6630,7 +6651,7 @@ Slog.e("TODD", // there's a local instant application installed, but, the user has // chosen to never use it; skip resolution and don't acknowledge // an instant application is even available - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Instant app marked to never run; pkg: " + packageName); } blockResolution = true; @@ -6638,7 +6659,7 @@ Slog.e("TODD", } else { // we have a locally installed instant application; skip resolution // but acknowledge there's an instant application available - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Found installed instant app; pkg: " + packageName); } localInstantApp = info; @@ -6696,7 +6717,7 @@ Slog.e("TODD", // make sure this resolver is the default ephemeralInstaller.isDefault = true; ephemeralInstaller.auxiliaryInfo = auxiliaryResponse; - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } @@ -7583,7 +7604,7 @@ Slog.e("TODD", info.serviceInfo.splitName)) { // requested service is defined in a split that hasn't been installed yet. // add the installer to the resolve list - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } final ResolveInfo installerInfo = new ResolveInfo( @@ -7705,7 +7726,7 @@ Slog.e("TODD", info.providerInfo.splitName)) { // requested provider is defined in a split that hasn't been installed yet. // add the installer to the resolve list - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } final ResolveInfo installerInfo = new ResolveInfo( @@ -11738,14 +11759,14 @@ Slog.e("TODD", private void setUpInstantAppInstallerActivityLP(ActivityInfo installerActivity) { if (installerActivity == null) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Clear ephemeral installer activity"); } mInstantAppInstallerActivity = null; return; } - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.d(TAG, "Set ephemeral installer activity: " + installerActivity.getComponentName()); } @@ -13205,7 +13226,7 @@ Slog.e("TODD", private int mFlags; } - static final class InstantAppIntentResolver + static final class EphemeralIntentResolver extends IntentResolver { /** @@ -13575,7 +13596,7 @@ Slog.e("TODD", IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, String installerPackageName, int installerUid, UserHandle user, PackageParser.SigningDetails signingDetails) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { Slog.d(TAG, "Ephemeral install of " + packageName); } @@ -15059,7 +15080,7 @@ Slog.e("TODD", pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags, packageAbiOverride); - if (DEBUG_INSTANT && ephemeral) { + if (DEBUG_EPHEMERAL && ephemeral) { Slog.v(TAG, "pkgLite for install: " + pkgLite); } @@ -15126,7 +15147,7 @@ Slog.e("TODD", installFlags |= PackageManager.INSTALL_EXTERNAL; installFlags &= ~PackageManager.INSTALL_INTERNAL; } else if (loc == PackageHelper.RECOMMEND_INSTALL_EPHEMERAL) { - if (DEBUG_INSTANT) { + if (DEBUG_EPHEMERAL) { Slog.v(TAG, "...setting INSTALL_EPHEMERAL install flag"); } installFlags |= PackageManager.INSTALL_INSTANT_APP; -- GitLab From a79bb60c783c2b526d59773587d54352c81c3c27 Mon Sep 17 00:00:00 2001 From: Patrick Baumann Date: Wed, 31 Jan 2018 01:33:50 +0000 Subject: [PATCH 176/416] Revert "Adds generic intent Instant App resolution" This reverts commit 3e8bd0f3b5ffab9a07189ed3ebcc6c4437778a0e. Reason for revert: b/72710855 Change-Id: I1378ccb5c5c16256e472e1ff7c3ad2460e091300 Fixes: 72710855 (cherry picked from commit 860b8ba71938e9860a31881c1d1431877f9d01a2) --- api/current.txt | 1 - api/system-current.txt | 11 +- .../android/app/EphemeralResolverService.java | 12 + .../java/android/app/IInstantAppResolver.aidl | 8 +- .../app/InstantAppResolverService.java | 107 +++--- core/java/android/content/Intent.java | 50 +-- .../content/pm/AuxiliaryResolveInfo.java | 99 ++---- .../content/pm/InstantAppResolveInfo.java | 96 +----- .../internal/app/ResolverListController.java | 13 +- .../server/am/ActivityStackSupervisor.java | 10 +- .../android/server/am/ActivityStarter.java | 30 +- .../pm/EphemeralResolverConnection.java | 28 +- .../android/server/pm/InstantAppResolver.java | 322 ++++++------------ .../server/pm/PackageManagerService.java | 182 +++++----- 14 files changed, 324 insertions(+), 645 deletions(-) diff --git a/api/current.txt b/api/current.txt index 7f07f810afa..dda165f1fdc 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10050,7 +10050,6 @@ package android.content { field public static final int FLAG_ACTIVITY_FORWARD_RESULT = 33554432; // 0x2000000 field public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 1048576; // 0x100000 field public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 4096; // 0x1000 - field public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 2048; // 0x800 field public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; // 0x8000000 field public static final int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; // 0x80000 field public static final int FLAG_ACTIVITY_NEW_TASK = 268435456; // 0x10000000 diff --git a/api/system-current.txt b/api/system-current.txt index 08ac295c936..034ee3090fc 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -311,10 +311,8 @@ package android.app { ctor public InstantAppResolverService(); method public final void attachBaseContext(android.content.Context); method public final android.os.IBinder onBind(android.content.Intent); - method public deprecated void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); - method public void onGetInstantAppIntentFilter(android.content.Intent, int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); - method public deprecated void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); - method public void onGetInstantAppResolveInfo(android.content.Intent, int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); + method public void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); + method public void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); } public static final class InstantAppResolverService.InstantAppResolutionCallback { @@ -823,14 +821,12 @@ package android.content { field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST"; field public static final java.lang.String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS"; field public static final java.lang.String EXTRA_FORCE_FACTORY_RESET = "android.intent.extra.FORCE_FACTORY_RESET"; - field public static final java.lang.String EXTRA_INSTANT_APP_BUNDLES = "android.intent.extra.INSTANT_APP_BUNDLES"; field public static final java.lang.String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID"; field public static final java.lang.String EXTRA_PACKAGES = "android.intent.extra.PACKAGES"; field public static final java.lang.String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; field public static final java.lang.String EXTRA_REASON = "android.intent.extra.REASON"; field public static final java.lang.String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK"; field public static final java.lang.String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED"; - field public static final java.lang.String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP"; } public class IntentFilter implements android.os.Parcelable { @@ -873,7 +869,6 @@ package android.content.pm { ctor public InstantAppResolveInfo(android.content.pm.InstantAppResolveInfo.InstantAppDigest, java.lang.String, java.util.List, int); ctor public InstantAppResolveInfo(android.content.pm.InstantAppResolveInfo.InstantAppDigest, java.lang.String, java.util.List, long, android.os.Bundle); ctor public InstantAppResolveInfo(java.lang.String, java.lang.String, java.util.List); - ctor public InstantAppResolveInfo(android.os.Bundle); method public int describeContents(); method public byte[] getDigestBytes(); method public int getDigestPrefix(); @@ -882,7 +877,6 @@ package android.content.pm { method public long getLongVersionCode(); method public java.lang.String getPackageName(); method public deprecated int getVersionCode(); - method public boolean shouldLetInstallerDecide(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -894,7 +888,6 @@ package android.content.pm { method public int[] getDigestPrefix(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; - field public static final android.content.pm.InstantAppResolveInfo.InstantAppDigest UNDEFINED; } public final class IntentFilterVerificationInfo implements android.os.Parcelable { diff --git a/core/java/android/app/EphemeralResolverService.java b/core/java/android/app/EphemeralResolverService.java index d1c244136ec..427a0386e87 100644 --- a/core/java/android/app/EphemeralResolverService.java +++ b/core/java/android/app/EphemeralResolverService.java @@ -17,10 +17,20 @@ package android.app; import android.annotation.SystemApi; +import android.app.Service; +import android.app.InstantAppResolverService.InstantAppResolutionCallback; +import android.content.Context; +import android.content.Intent; import android.content.pm.EphemeralResolveInfo; import android.content.pm.InstantAppResolveInfo; import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; import android.util.Log; import java.util.ArrayList; @@ -75,6 +85,7 @@ public abstract class EphemeralResolverService extends InstantAppResolverService return super.getLooper(); } + @Override void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, InstantAppResolutionCallback callback) { if (DEBUG_EPHEMERAL) { @@ -90,6 +101,7 @@ public abstract class EphemeralResolverService extends InstantAppResolverService callback.onInstantAppResolveInfo(resultList); } + @Override void _onGetInstantAppIntentFilter(int[] digestPrefix, String token, String hostName, InstantAppResolutionCallback callback) { if (DEBUG_EPHEMERAL) { diff --git a/core/java/android/app/IInstantAppResolver.aidl b/core/java/android/app/IInstantAppResolver.aidl index ae200578d82..805d8c057d2 100644 --- a/core/java/android/app/IInstantAppResolver.aidl +++ b/core/java/android/app/IInstantAppResolver.aidl @@ -16,15 +16,13 @@ package android.app; -import android.content.Intent; import android.os.IRemoteCallback; /** @hide */ oneway interface IInstantAppResolver { - void getInstantAppResolveInfoList(in Intent sanitizedIntent, in int[] hostDigestPrefix, + void getInstantAppResolveInfoList(in int[] digestPrefix, String token, int sequence, IRemoteCallback callback); - void getInstantAppIntentFilterList(in Intent sanitizedIntent, in int[] hostDigestPrefix, - String token, IRemoteCallback callback); - + void getInstantAppIntentFilterList(in int[] digestPrefix, + String token, String hostName, IRemoteCallback callback); } diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java index 89aff36f883..c5dc86c79ef 100644 --- a/core/java/android/app/InstantAppResolverService.java +++ b/core/java/android/app/InstantAppResolverService.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.SystemApi; +import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.InstantAppResolveInfo; @@ -34,7 +35,6 @@ import android.util.Slog; import com.android.internal.os.SomeArgs; import java.util.Arrays; -import java.util.Collections; import java.util.List; /** @@ -53,65 +53,23 @@ public abstract class InstantAppResolverService extends Service { Handler mHandler; /** - * Called to retrieve resolve info for instant applications immediately. + * Called to retrieve resolve info for instant applications. * * @param digestPrefix The hash prefix of the instant app's domain. - * @deprecated should implement {@link #onGetInstantAppResolveInfo(Intent, int[], String, - * InstantAppResolutionCallback)} */ - @Deprecated public void onGetInstantAppResolveInfo( int digestPrefix[], String token, InstantAppResolutionCallback callback) { throw new IllegalStateException("Must define"); } /** - * Called to retrieve intent filters for instant applications from potentially expensive - * sources. + * Called to retrieve intent filters for instant applications. * * @param digestPrefix The hash prefix of the instant app's domain. - * @deprecated should implement {@link #onGetInstantAppIntentFilter(Intent, int[], String, - * InstantAppResolutionCallback)} */ - @Deprecated public void onGetInstantAppIntentFilter( int digestPrefix[], String token, InstantAppResolutionCallback callback) { - throw new IllegalStateException("Must define onGetInstantAppIntentFilter"); - } - - /** - * Called to retrieve resolve info for instant applications immediately. - * - * @param sanitizedIntent The sanitized {@link Intent} used for resolution. - * @param hostDigestPrefix The hash prefix of the instant app's domain. - */ - public void onGetInstantAppResolveInfo(Intent sanitizedIntent, int[] hostDigestPrefix, - String token, InstantAppResolutionCallback callback) { - // if not overridden, forward to old methods and filter out non-web intents - if (sanitizedIntent.isBrowsableWebIntent()) { - onGetInstantAppResolveInfo(hostDigestPrefix, token, callback); - } else { - callback.onInstantAppResolveInfo(Collections.emptyList()); - } - } - - /** - * Called to retrieve intent filters for instant applications from potentially expensive - * sources. - * - * @param sanitizedIntent The sanitized {@link Intent} used for resolution. - * @param hostDigestPrefix The hash prefix of the instant app's domain or null if no host is - * defined. - */ - public void onGetInstantAppIntentFilter(Intent sanitizedIntent, int[] hostDigestPrefix, - String token, InstantAppResolutionCallback callback) { - Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden"); - // if not overridden, forward to old methods and filter out non-web intents - if (sanitizedIntent.isBrowsableWebIntent()) { - onGetInstantAppIntentFilter(hostDigestPrefix, token, callback); - } else { - callback.onInstantAppResolveInfo(Collections.emptyList()); - } + throw new IllegalStateException("Must define"); } /** @@ -131,8 +89,8 @@ public abstract class InstantAppResolverService extends Service { public final IBinder onBind(Intent intent) { return new IInstantAppResolver.Stub() { @Override - public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix, - String token, int sequence, IRemoteCallback callback) { + public void getInstantAppResolveInfoList( + int digestPrefix[], String token, int sequence, IRemoteCallback callback) { if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Phase1 called; posting"); } @@ -140,14 +98,14 @@ public abstract class InstantAppResolverService extends Service { args.arg1 = callback; args.arg2 = digestPrefix; args.arg3 = token; - args.arg4 = sanitizedIntent; - mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, - sequence, 0, args).sendToTarget(); + mHandler.obtainMessage( + ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, args) + .sendToTarget(); } @Override - public void getInstantAppIntentFilterList(Intent sanitizedIntent, - int[] digestPrefix, String token, IRemoteCallback callback) { + public void getInstantAppIntentFilterList( + int digestPrefix[], String token, String hostName, IRemoteCallback callback) { if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Phase2 called; posting"); } @@ -155,9 +113,9 @@ public abstract class InstantAppResolverService extends Service { args.arg1 = callback; args.arg2 = digestPrefix; args.arg3 = token; - args.arg4 = sanitizedIntent; - mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, - callback).sendToTarget(); + args.arg4 = hostName; + mHandler.obtainMessage( + ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, callback).sendToTarget(); } }; } @@ -184,9 +142,29 @@ public abstract class InstantAppResolverService extends Service { } } + @Deprecated + void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, + InstantAppResolutionCallback callback) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "[" + token + "] Phase1 request;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + onGetInstantAppResolveInfo(digestPrefix, token, callback); + } + @Deprecated + void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName, + InstantAppResolutionCallback callback) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "[" + token + "] Phase2 request;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + onGetInstantAppIntentFilter(digestPrefix, token, callback); + } + private final class ServiceHandler extends Handler { public static final int MSG_GET_INSTANT_APP_RESOLVE_INFO = 1; public static final int MSG_GET_INSTANT_APP_INTENT_FILTER = 2; + public ServiceHandler(Looper looper) { super(looper, null /*callback*/, true /*async*/); } @@ -201,13 +179,9 @@ public abstract class InstantAppResolverService extends Service { final IRemoteCallback callback = (IRemoteCallback) args.arg1; final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; - final Intent intent = (Intent) args.arg4; final int sequence = message.arg1; - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "[" + token + "] Phase1 request;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - onGetInstantAppResolveInfo(intent, digestPrefix, token, + _onGetInstantAppResolveInfo( + digestPrefix, token, new InstantAppResolutionCallback(sequence, callback)); } break; @@ -216,12 +190,9 @@ public abstract class InstantAppResolverService extends Service { final IRemoteCallback callback = (IRemoteCallback) args.arg1; final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; - final Intent intent = (Intent) args.arg4; - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "[" + token + "] Phase2 request;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - onGetInstantAppIntentFilter(intent, digestPrefix, token, + final String hostName = (String) args.arg4; + _onGetInstantAppIntentFilter( + digestPrefix, token, hostName, new InstantAppResolutionCallback(-1 /*sequence*/, callback)); } break; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index b3c8737a283..e02a2949429 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -50,7 +50,6 @@ import android.provider.DocumentsContract; import android.provider.DocumentsProvider; import android.provider.MediaStore; import android.provider.OpenableColumns; -import android.text.TextUtils; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; @@ -4473,15 +4472,7 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION"; /** - * An array of {@link Bundle}s containing details about resolved instant apps.. - * @hide - */ - @SystemApi - public static final String EXTRA_INSTANT_APP_BUNDLES = - "android.intent.extra.INSTANT_APP_BUNDLES"; - - /** - * A {@link Bundle} of metadata that describes the instant application that needs to be + * A {@link Bundle} of metadata that describes the instanta application that needs to be * installed. This data is populated from the response to * {@link android.content.pm.InstantAppResolveInfo#getExtras()} as provided by the registered * instant application resolver. @@ -4490,16 +4481,6 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_INSTANT_APP_EXTRAS = "android.intent.extra.INSTANT_APP_EXTRAS"; - /** - * A boolean value indicating that the instant app resolver was unable to state with certainty - * that it did or did not have an app for the sanitized {@link Intent} defined at - * {@link #EXTRA_INTENT}. - * @hide - */ - @SystemApi - public static final String EXTRA_UNKNOWN_INSTANT_APP = - "android.intent.extra.UNKNOWN_INSTANT_APP"; - /** * The version code of the app to install components from. * @deprecated Use {@link #EXTRA_LONG_VERSION_CODE). @@ -5048,7 +5029,6 @@ public class Intent implements Parcelable, Cloneable { FLAG_GRANT_PREFIX_URI_PERMISSION, FLAG_DEBUG_TRIAGED_MISSING, FLAG_IGNORE_EPHEMERAL, - FLAG_ACTIVITY_MATCH_EXTERNAL, FLAG_ACTIVITY_NO_HISTORY, FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_NEW_TASK, @@ -5092,7 +5072,6 @@ public class Intent implements Parcelable, Cloneable { FLAG_INCLUDE_STOPPED_PACKAGES, FLAG_DEBUG_TRIAGED_MISSING, FLAG_IGNORE_EPHEMERAL, - FLAG_ACTIVITY_MATCH_EXTERNAL, FLAG_ACTIVITY_NO_HISTORY, FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_NEW_TASK, @@ -5496,14 +5475,6 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 0x00001000; - - /** - * If set, resolution of this intent may take place via an instant app not - * yet on the device if there does not yet exist an app on device to - * resolve it. - */ - public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 0x00000800; - /** * If set, when sending a broadcast only registered receivers will be * called -- no BroadcastReceiver components will be launched. @@ -10057,25 +10028,6 @@ public class Intent implements Parcelable, Cloneable { } } - /** @hide */ - public boolean hasWebURI() { - if (getData() == null) { - return false; - } - final String scheme = getScheme(); - if (TextUtils.isEmpty(scheme)) { - return false; - } - return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS); - } - - /** @hide */ - public boolean isBrowsableWebIntent() { - return ACTION_VIEW.equals(mAction) - && hasCategory(CATEGORY_BROWSABLE) - && hasWebURI(); - } - /** * @hide */ diff --git a/core/java/android/content/pm/AuxiliaryResolveInfo.java b/core/java/android/content/pm/AuxiliaryResolveInfo.java index 202df50dda6..6bdcefbe974 100644 --- a/core/java/android/content/pm/AuxiliaryResolveInfo.java +++ b/core/java/android/content/pm/AuxiliaryResolveInfo.java @@ -21,10 +21,6 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; -import android.os.Bundle; - -import java.util.Collections; -import java.util.List; /** * Auxiliary application resolution response. @@ -35,95 +31,56 @@ import java.util.List; * hasn't been installed. * @hide */ -public final class AuxiliaryResolveInfo { +public final class AuxiliaryResolveInfo extends IntentFilter { + /** Resolved information returned from the external instant resolver */ + public final InstantAppResolveInfo resolveInfo; + /** The resolved package. Copied from {@link #resolveInfo}. */ + public final String packageName; /** The activity to launch if there's an installation failure. */ public final ComponentName installFailureActivity; + /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */ + public final String splitName; /** Whether or not instant resolution needs the second phase */ public final boolean needsPhaseTwo; /** Opaque token to track the instant application resolution */ public final String token; + /** The version code of the package */ + public final long versionCode; /** An intent to start upon failure to install */ public final Intent failureIntent; - /** The matching filters for this resolve info. */ - public final List filters; /** Create a response for installing an instant application. */ - public AuxiliaryResolveInfo(@NonNull String token, + public AuxiliaryResolveInfo(@NonNull InstantAppResolveInfo resolveInfo, + @NonNull IntentFilter orig, + @Nullable String splitName, + @NonNull String token, boolean needsPhase2, - @Nullable Intent failureIntent, - @Nullable List filters) { + @Nullable Intent failureIntent) { + super(orig); + this.resolveInfo = resolveInfo; + this.packageName = resolveInfo.getPackageName(); + this.splitName = splitName; this.token = token; this.needsPhaseTwo = needsPhase2; + this.versionCode = resolveInfo.getVersionCode(); this.failureIntent = failureIntent; - this.filters = filters; this.installFailureActivity = null; } /** Create a response for installing a split on demand. */ - public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity, - @Nullable Intent failureIntent, - @Nullable List filters) { + public AuxiliaryResolveInfo(@NonNull String packageName, + @Nullable String splitName, + @Nullable ComponentName failureActivity, + long versionCode, + @Nullable Intent failureIntent) { super(); + this.packageName = packageName; this.installFailureActivity = failureActivity; - this.filters = filters; + this.splitName = splitName; + this.versionCode = versionCode; + this.resolveInfo = null; this.token = null; this.needsPhaseTwo = false; this.failureIntent = failureIntent; } - - /** Create a response for installing a split on demand. */ - public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity, - String packageName, long versionCode, String splitName) { - this(failureActivity, null, Collections.singletonList( - new AuxiliaryResolveInfo.AuxiliaryFilter(packageName, versionCode, splitName))); - } - - /** @hide */ - public static final class AuxiliaryFilter extends IntentFilter { - /** Resolved information returned from the external instant resolver */ - public final InstantAppResolveInfo resolveInfo; - /** The resolved package. Copied from {@link #resolveInfo}. */ - public final String packageName; - /** The version code of the package */ - public final long versionCode; - /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */ - public final String splitName; - /** The extras to pass on to the installer for this filter. */ - public final Bundle extras; - - public AuxiliaryFilter(IntentFilter orig, InstantAppResolveInfo resolveInfo, - String splitName, Bundle extras) { - super(orig); - this.resolveInfo = resolveInfo; - this.packageName = resolveInfo.getPackageName(); - this.versionCode = resolveInfo.getLongVersionCode(); - this.splitName = splitName; - this.extras = extras; - } - - public AuxiliaryFilter(InstantAppResolveInfo resolveInfo, - String splitName, Bundle extras) { - this.resolveInfo = resolveInfo; - this.packageName = resolveInfo.getPackageName(); - this.versionCode = resolveInfo.getLongVersionCode(); - this.splitName = splitName; - this.extras = extras; - } - - public AuxiliaryFilter(String packageName, long versionCode, String splitName) { - this.resolveInfo = null; - this.packageName = packageName; - this.versionCode = versionCode; - this.splitName = splitName; - this.extras = null; - } - - @Override - public String toString() { - return "AuxiliaryFilter{" - + "packageName='" + packageName + '\'' - + ", versionCode=" + versionCode - + ", splitName='" + splitName + '\'' + '}'; - } - } } \ No newline at end of file diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java index 112c5dae673..19cb9323ba9 100644 --- a/core/java/android/content/pm/InstantAppResolveInfo.java +++ b/core/java/android/content/pm/InstantAppResolveInfo.java @@ -19,7 +19,6 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.content.Intent; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -27,35 +26,11 @@ import android.os.Parcelable; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Locale; /** - * Describes an externally resolvable instant application. There are three states that this class - * can represent:

    - *

      - *
    • - * The first, usable only for non http/s intents, implies that the resolver cannot - * immediately resolve this intent and would prefer that resolution be deferred to the - * instant app installer. Represent this state with {@link #InstantAppResolveInfo(Bundle)}. - * If the {@link android.content.Intent} has the scheme set to http/s and a set of digest - * prefixes were passed into one of the resolve methods in - * {@link android.app.InstantAppResolverService}, this state cannot be used. - *
    • - *
    • - * The second represents a partial match and is constructed with any of the other - * constructors. By setting one or more of the {@link Nullable}arguments to null, you - * communicate to the resolver in response to - * {@link android.app.InstantAppResolverService#onGetInstantAppResolveInfo(Intent, int[], - * String, InstantAppResolverService.InstantAppResolutionCallback)} - * that you need a 2nd round of resolution to complete the request. - *
    • - *
    • - * The third represents a complete match and is constructed with all @Nullable parameters - * populated. - *
    • - *
    + * Information about an instant application. * @hide */ @SystemApi @@ -63,8 +38,6 @@ public final class InstantAppResolveInfo implements Parcelable { /** Algorithm that will be used to generate the domain digest */ private static final String SHA_ALGORITHM = "SHA-256"; - private static final byte[] EMPTY_DIGEST = new byte[0]; - private final InstantAppDigest mDigest; private final String mPackageName; /** The filters used to match domain */ @@ -73,30 +46,15 @@ public final class InstantAppResolveInfo implements Parcelable { private final long mVersionCode; /** Data about the app that should be passed along to the Instant App installer on resolve */ private final Bundle mExtras; - /** - * A flag that indicates that the resolver is aware that an app may match, but would prefer - * that the installer get the sanitized intent to decide. This should not be used for - * resolutions that include a host and will be ignored in such cases. - */ - private final boolean mShouldLetInstallerDecide; - /** Constructor for intent-based InstantApp resolution results. */ public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, @Nullable List filters, int versionCode) { this(digest, packageName, filters, (long) versionCode, null /* extras */); } - /** Constructor for intent-based InstantApp resolution results with extras. */ public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, @Nullable List filters, long versionCode, @Nullable Bundle extras) { - this(digest, packageName, filters, versionCode, extras, false); - } - - /** Constructor for intent-based InstantApp resolution results with extras. */ - private InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, - @Nullable List filters, long versionCode, - @Nullable Bundle extras, boolean shouldLetInstallerDecide) { // validate arguments if ((packageName == null && (filters != null && filters.size() != 0)) || (packageName != null && (filters == null || filters.size() == 0))) { @@ -104,7 +62,7 @@ public final class InstantAppResolveInfo implements Parcelable { } mDigest = digest; if (filters != null) { - mFilters = new ArrayList<>(filters.size()); + mFilters = new ArrayList(filters.size()); mFilters.addAll(filters); } else { mFilters = null; @@ -112,48 +70,25 @@ public final class InstantAppResolveInfo implements Parcelable { mPackageName = packageName; mVersionCode = versionCode; mExtras = extras; - mShouldLetInstallerDecide = shouldLetInstallerDecide; } - /** Constructor for intent-based InstantApp resolution results by hostname. */ public InstantAppResolveInfo(@NonNull String hostName, @Nullable String packageName, @Nullable List filters) { this(new InstantAppDigest(hostName), packageName, filters, -1 /*versionCode*/, null /* extras */); } - /** - * Constructor that creates a "let the installer decide" response with optional included - * extras. - */ - public InstantAppResolveInfo(@Nullable Bundle extras) { - this(InstantAppDigest.UNDEFINED, null, null, -1, extras, true); - } - InstantAppResolveInfo(Parcel in) { - mShouldLetInstallerDecide = in.readBoolean(); + mDigest = in.readParcelable(null /*loader*/); + mPackageName = in.readString(); + mFilters = new ArrayList(); + in.readList(mFilters, null /*loader*/); + mVersionCode = in.readLong(); mExtras = in.readBundle(); - if (mShouldLetInstallerDecide) { - mDigest = InstantAppDigest.UNDEFINED; - mPackageName = null; - mFilters = Collections.emptyList(); - mVersionCode = -1; - } else { - mDigest = in.readParcelable(null /*loader*/); - mPackageName = in.readString(); - mFilters = new ArrayList<>(); - in.readList(mFilters, null /*loader*/); - mVersionCode = in.readLong(); - } - } - - /** Returns true if the installer should be notified that it should query for packages. */ - public boolean shouldLetInstallerDecide() { - return mShouldLetInstallerDecide; } public byte[] getDigestBytes() { - return mDigest.mDigestBytes.length > 0 ? mDigest.getDigestBytes()[0] : EMPTY_DIGEST; + return mDigest.getDigestBytes()[0]; } public int getDigestPrefix() { @@ -192,15 +127,11 @@ public final class InstantAppResolveInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeBoolean(mShouldLetInstallerDecide); - out.writeBundle(mExtras); - if (mShouldLetInstallerDecide) { - return; - } out.writeParcelable(mDigest, flags); out.writeString(mPackageName); out.writeList(mFilters); out.writeLong(mVersionCode); + out.writeBundle(mExtras); } public static final Parcelable.Creator CREATOR @@ -228,9 +159,7 @@ public final class InstantAppResolveInfo implements Parcelable { @SystemApi public static final class InstantAppDigest implements Parcelable { private static final int DIGEST_MASK = 0xfffff000; - - public static final InstantAppDigest UNDEFINED = - new InstantAppDigest(new byte[][]{}, new int[]{}); + private static final int DIGEST_PREFIX_COUNT = 5; /** Full digest of the domain hashes */ private final byte[][] mDigestBytes; /** The first 4 bytes of the domain hashes */ @@ -257,11 +186,6 @@ public final class InstantAppResolveInfo implements Parcelable { } } - private InstantAppDigest(byte[][] digestBytes, int[] prefix) { - this.mDigestPrefix = prefix; - this.mDigestBytes = digestBytes; - } - private static byte[][] generateDigest(String hostName, int maxDigests) { ArrayList digests = new ArrayList<>(); try { diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 1dfff5efcab..2ab2d20ed20 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -103,14 +103,11 @@ public class ResolverListController { List resolvedComponents = null; for (int i = 0, N = intents.size(); i < N; i++) { final Intent intent = intents.get(i); - int flags = PackageManager.MATCH_DEFAULT_ONLY - | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) - | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0); - if (intent.isBrowsableWebIntent() - || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { - flags |= PackageManager.MATCH_INSTANT; - } - final List infos = mpm.queryIntentActivities(intent, flags); + final List infos = mpm.queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY + | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) + | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0) + | PackageManager.MATCH_INSTANT); // Remove any activities that are not exported. int totalSize = infos.size(); for (int j = totalSize - 1; j >= 0 ; j--) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index bf388258769..6beafcb94ea 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1251,14 +1251,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D synchronized (mService) { try { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent"); - int modifiedFlags = flags - | PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS; - if (intent.isBrowsableWebIntent() - || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { - modifiedFlags |= PackageManager.MATCH_INSTANT; - } return mService.getPackageManagerInternalLocked().resolveIntent( - intent, resolvedType, modifiedFlags, userId, true); + intent, resolvedType, PackageManager.MATCH_INSTANT + | PackageManager.MATCH_DEFAULT_ONLY | flags + | ActivityManagerService.STOCK_PM_FLAGS, userId, true); } finally { Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index eab88aa45e4..8fd754af1a0 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -31,7 +31,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.content.Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; @@ -790,7 +789,7 @@ class ActivityStarter { // Instead, launch the ephemeral installer. Once the installer is finished, it // starts either the intent we resolved here [on install error] or the ephemeral // app [on install success]. - if (rInfo != null && rInfo.isInstantAppAvailable) { + if (rInfo != null && rInfo.auxiliaryInfo != null) { intent = createLaunchIntent(rInfo.auxiliaryInfo, ephemeralIntent, callingPackage, verificationBundle, resolvedType, userId); resolvedType = null; @@ -850,27 +849,22 @@ class ActivityStarter { /** * Creates a launch intent for the given auxiliary resolution data. */ - private @NonNull Intent createLaunchIntent(@Nullable AuxiliaryResolveInfo auxiliaryResponse, + private @NonNull Intent createLaunchIntent(@NonNull AuxiliaryResolveInfo auxiliaryResponse, Intent originalIntent, String callingPackage, Bundle verificationBundle, String resolvedType, int userId) { - if (auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo) { + if (auxiliaryResponse.needsPhaseTwo) { // request phase two resolution mService.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo( auxiliaryResponse, originalIntent, resolvedType, callingPackage, verificationBundle, userId); } return InstantAppResolver.buildEphemeralInstallerIntent( - originalIntent, - InstantAppResolver.sanitizeIntent(originalIntent), - auxiliaryResponse == null ? null : auxiliaryResponse.failureIntent, - callingPackage, - verificationBundle, - resolvedType, - userId, - auxiliaryResponse == null ? null : auxiliaryResponse.installFailureActivity, - auxiliaryResponse == null ? null : auxiliaryResponse.token, - auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo, - auxiliaryResponse == null ? null : auxiliaryResponse.filters); + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, originalIntent, + auxiliaryResponse.failureIntent, callingPackage, verificationBundle, + resolvedType, userId, auxiliaryResponse.packageName, auxiliaryResponse.splitName, + auxiliaryResponse.installFailureActivity, auxiliaryResponse.versionCode, + auxiliaryResponse.token, auxiliaryResponse.resolveInfo.getExtras(), + auxiliaryResponse.needsPhaseTwo); } void postStartActivityProcessing(ActivityRecord r, int result, ActivityStack targetStack) { @@ -930,12 +924,12 @@ class ActivityStarter { // Don't modify the client's object! intent = new Intent(intent); if (componentSpecified - && !Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE.equals(intent.getAction()) - && !Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE.equals(intent.getAction()) + && intent.getData() != null + && Intent.ACTION_VIEW.equals(intent.getAction()) && mService.getPackageManagerInternalLocked() .isInstantAppInstallerComponent(intent.getComponent())) { // intercept intents targeted directly to the ephemeral installer the - // ephemeral installer should never be started with a raw Intent; instead + // ephemeral installer should never be started with a raw URL; instead // adjust the intent so it looks like a "normal" instant app launch intent.setComponent(null /*component*/); componentSpecified = false; diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java index 6d6c960eed7..b5ddf8c511f 100644 --- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java +++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java @@ -43,7 +43,6 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.TimeoutException; @@ -85,8 +84,8 @@ final class EphemeralResolverConnection implements DeathRecipient { mIntent = new Intent(action).setComponent(componentName); } - public final List getInstantAppResolveInfoList(Intent sanitizedIntent, - int hashPrefix[], String token) throws ConnectionException { + public final List getInstantAppResolveInfoList(int hashPrefix[], + String token) throws ConnectionException { throwIfCalledOnMainThread(); IInstantAppResolver target = null; try { @@ -99,7 +98,7 @@ final class EphemeralResolverConnection implements DeathRecipient { } try { return mGetEphemeralResolveInfoCaller - .getEphemeralResolveInfoList(target, sanitizedIntent, hashPrefix, token); + .getEphemeralResolveInfoList(target, hashPrefix, token); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_CALL); } catch (RemoteException ignore) { @@ -112,22 +111,26 @@ final class EphemeralResolverConnection implements DeathRecipient { return null; } - public final void getInstantAppIntentFilterList(Intent sanitizedIntent, int hashPrefix[], - String token, PhaseTwoCallback callback, Handler callbackHandler, final long startTime) - throws ConnectionException { + public final void getInstantAppIntentFilterList(int hashPrefix[], String token, + String hostName, PhaseTwoCallback callback, Handler callbackHandler, + final long startTime) throws ConnectionException { final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() { @Override public void sendResult(Bundle data) throws RemoteException { final ArrayList resolveList = data.getParcelableArrayList( InstantAppResolverService.EXTRA_RESOLVE_INFO); - callbackHandler.post(() -> callback.onPhaseTwoResolved(resolveList, startTime)); + callbackHandler.post(new Runnable() { + @Override + public void run() { + callback.onPhaseTwoResolved(resolveList, startTime); + } + }); } }; try { getRemoteInstanceLazy(token) - .getInstantAppIntentFilterList(sanitizedIntent, hashPrefix, token, - remoteCallback); + .getInstantAppIntentFilterList(hashPrefix, token, hostName, remoteCallback); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_BIND); } catch (InterruptedException e) { @@ -334,11 +337,10 @@ final class EphemeralResolverConnection implements DeathRecipient { } public List getEphemeralResolveInfoList( - IInstantAppResolver target, Intent sanitizedIntent, int hashPrefix[], String token) + IInstantAppResolver target, int hashPrefix[], String token) throws RemoteException, TimeoutException { final int sequence = onBeforeRemoteCall(); - target.getInstantAppResolveInfoList(sanitizedIntent, hashPrefix, token, sequence, - mCallback); + target.getInstantAppResolveInfoList(hashPrefix, token, sequence, mCallback); return getResultTimed(sequence); } } diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index 55212cc6b3d..30072d45cca 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -40,14 +40,11 @@ import android.content.pm.InstantAppIntentFilter; import android.content.pm.InstantAppResolveInfo; import android.content.pm.InstantAppResolveInfo.InstantAppDigest; import android.metrics.LogMaker; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; -import android.text.TextUtils; import android.util.Log; -import android.util.Slog; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; @@ -56,12 +53,8 @@ import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; import java.util.List; -import java.util.Set; import java.util.UUID; /** @hide */ @@ -86,7 +79,6 @@ public abstract class InstantAppResolver { public @interface ResolutionStatus {} private static MetricsLogger sMetricsLogger; - private static MetricsLogger getLogger() { if (sMetricsLogger == null) { sMetricsLogger = new MetricsLogger(); @@ -94,49 +86,26 @@ public abstract class InstantAppResolver { return sMetricsLogger; } - /** - * Returns an intent with potential PII removed from the original intent. Fields removed - * include extras and the host + path of the data, if defined. - */ - public static Intent sanitizeIntent(Intent origIntent) { - final Intent sanitizedIntent; - sanitizedIntent = new Intent(origIntent.getAction()); - Set categories = origIntent.getCategories(); - if (categories != null) { - for (String category : categories) { - sanitizedIntent.addCategory(category); - } - } - Uri sanitizedUri = origIntent.getData() == null - ? null - : Uri.fromParts(origIntent.getScheme(), "", ""); - sanitizedIntent.setDataAndType(sanitizedUri, origIntent.getType()); - sanitizedIntent.addFlags(origIntent.getFlags()); - sanitizedIntent.setPackage(origIntent.getPackage()); - return sanitizedIntent; - } - - public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne( + public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(Context context, EphemeralResolverConnection connection, InstantAppRequest requestObj) { final long startTime = System.currentTimeMillis(); final String token = UUID.randomUUID().toString(); if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] Phase1; resolving"); } - final Intent origIntent = requestObj.origIntent; - final Intent sanitizedIntent = sanitizeIntent(origIntent); - - final InstantAppDigest digest = getInstantAppDigest(origIntent); + final Intent intent = requestObj.origIntent; + final InstantAppDigest digest = + new InstantAppDigest(intent.getData().getHost(), 5 /*maxDigests*/); final int[] shaPrefix = digest.getDigestPrefix(); AuxiliaryResolveInfo resolveInfo = null; @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS; try { final List instantAppResolveInfoList = - connection.getInstantAppResolveInfoList(sanitizedIntent, shaPrefix, token); + connection.getInstantAppResolveInfoList(shaPrefix, token); if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) { resolveInfo = InstantAppResolver.filterInstantAppIntent( - instantAppResolveInfoList, origIntent, requestObj.resolvedType, - requestObj.userId, origIntent.getPackage(), digest, token); + instantAppResolveInfoList, intent, requestObj.resolvedType, + requestObj.userId, intent.getPackage(), digest, token); } } catch (ConnectionException e) { if (e.failure == ConnectionException.FAILURE_BIND) { @@ -166,12 +135,6 @@ public abstract class InstantAppResolver { return resolveInfo; } - private static InstantAppDigest getInstantAppDigest(Intent origIntent) { - return origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost()) - ? new InstantAppDigest(origIntent.getData().getHost(), 5 /*maxDigests*/) - : InstantAppDigest.UNDEFINED; - } - public static void doInstantAppResolutionPhaseTwo(Context context, EphemeralResolverConnection connection, InstantAppRequest requestObj, ActivityInfo instantAppInstaller, Handler callbackHandler) { @@ -180,53 +143,73 @@ public abstract class InstantAppResolver { if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] Phase2; resolving"); } - final Intent origIntent = requestObj.origIntent; - final Intent sanitizedIntent = sanitizeIntent(origIntent); - final InstantAppDigest digest = getInstantAppDigest(origIntent); + final Intent intent = requestObj.origIntent; + final String hostName = intent.getData().getHost(); + final InstantAppDigest digest = new InstantAppDigest(hostName, 5 /*maxDigests*/); final int[] shaPrefix = digest.getDigestPrefix(); final PhaseTwoCallback callback = new PhaseTwoCallback() { @Override void onPhaseTwoResolved(List instantAppResolveInfoList, long startTime) { + final String packageName; + final String splitName; + final long versionCode; final Intent failureIntent; + final Bundle extras; if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) { final AuxiliaryResolveInfo instantAppIntentInfo = InstantAppResolver.filterInstantAppIntent( - instantAppResolveInfoList, origIntent, null /*resolvedType*/, - 0 /*userId*/, origIntent.getPackage(), digest, token); - if (instantAppIntentInfo != null) { + instantAppResolveInfoList, intent, null /*resolvedType*/, + 0 /*userId*/, intent.getPackage(), digest, token); + if (instantAppIntentInfo != null + && instantAppIntentInfo.resolveInfo != null) { + packageName = instantAppIntentInfo.resolveInfo.getPackageName(); + splitName = instantAppIntentInfo.splitName; + versionCode = instantAppIntentInfo.resolveInfo.getVersionCode(); failureIntent = instantAppIntentInfo.failureIntent; + extras = instantAppIntentInfo.resolveInfo.getExtras(); } else { + packageName = null; + splitName = null; + versionCode = -1; failureIntent = null; + extras = null; } } else { + packageName = null; + splitName = null; + versionCode = -1; failureIntent = null; + extras = null; } final Intent installerIntent = buildEphemeralInstallerIntent( + Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE, requestObj.origIntent, - sanitizedIntent, failureIntent, requestObj.callingPackage, requestObj.verificationBundle, requestObj.resolvedType, requestObj.userId, + packageName, + splitName, requestObj.responseObj.installFailureActivity, + versionCode, token, - false /*needsPhaseTwo*/, - requestObj.responseObj.filters); + extras, + false /*needsPhaseTwo*/); installerIntent.setComponent(new ComponentName( instantAppInstaller.packageName, instantAppInstaller.name)); logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token, - requestObj.responseObj.filters != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE); + packageName != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE); context.startActivity(installerIntent); } }; try { - connection.getInstantAppIntentFilterList(sanitizedIntent, shaPrefix, token, callback, - callbackHandler, startTime); + connection.getInstantAppIntentFilterList( + shaPrefix, token, hostName, callback, callbackHandler, startTime); } catch (ConnectionException e) { @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE; if (e.failure == ConnectionException.FAILURE_BIND) { @@ -248,20 +231,23 @@ public abstract class InstantAppResolver { * Builds and returns an intent to launch the instant installer. */ public static Intent buildEphemeralInstallerIntent( + @NonNull String action, @NonNull Intent origIntent, - @NonNull Intent sanitizedIntent, - @Nullable Intent failureIntent, + @NonNull Intent failureIntent, @NonNull String callingPackage, @Nullable Bundle verificationBundle, @NonNull String resolvedType, int userId, + @NonNull String instantAppPackageName, + @Nullable String instantAppSplitName, @Nullable ComponentName installFailureActivity, + long versionCode, @Nullable String token, - boolean needsPhaseTwo, - List filters) { + @Nullable Bundle extras, + boolean needsPhaseTwo) { // Construct the intent that launches the instant installer int flags = origIntent.getFlags(); - final Intent intent = new Intent(); + final Intent intent = new Intent(action); intent.setFlags(flags | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK @@ -274,23 +260,20 @@ public abstract class InstantAppResolver { intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost()); } intent.putExtra(Intent.EXTRA_INSTANT_APP_ACTION, origIntent.getAction()); - intent.putExtra(Intent.EXTRA_INTENT, sanitizedIntent); + if (extras != null) { + intent.putExtra(Intent.EXTRA_INSTANT_APP_EXTRAS, extras); + } - if (needsPhaseTwo) { - intent.setAction(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE); - } else { - // We have all of the data we need; just start the installer without a second phase + // We have all of the data we need; just start the installer without a second phase + if (!needsPhaseTwo) { + // Intent that is launched if the package couldn't be installed for any reason. if (failureIntent != null || installFailureActivity != null) { - // Intent that is launched if the package couldn't be installed for any reason. try { final Intent onFailureIntent; if (installFailureActivity != null) { onFailureIntent = new Intent(); onFailureIntent.setComponent(installFailureActivity); - if (filters != null && filters.size() == 1) { - onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME, - filters.get(0).splitName); - } + onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName); onFailureIntent.putExtra(Intent.EXTRA_INTENT, origIntent); } else { onFailureIntent = failureIntent; @@ -326,35 +309,17 @@ public abstract class InstantAppResolver { intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(successIntentTarget)); } catch (RemoteException ignore) { /* ignore; same process */ } + + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, instantAppPackageName); + intent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName); + intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) (versionCode & 0x7fffffff)); + intent.putExtra(Intent.EXTRA_LONG_VERSION_CODE, versionCode); + intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage); if (verificationBundle != null) { intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle); } - intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage); - - if (filters != null) { - Bundle resolvableFilters[] = new Bundle[filters.size()]; - for (int i = 0, max = filters.size(); i < max; i++) { - Bundle resolvableFilter = new Bundle(); - AuxiliaryResolveInfo.AuxiliaryFilter filter = filters.get(i); - resolvableFilter.putBoolean(Intent.EXTRA_UNKNOWN_INSTANT_APP, - filter.resolveInfo != null - && filter.resolveInfo.shouldLetInstallerDecide()); - resolvableFilter.putString(Intent.EXTRA_PACKAGE_NAME, filter.packageName); - resolvableFilter.putString(Intent.EXTRA_SPLIT_NAME, filter.splitName); - resolvableFilter.putLong(Intent.EXTRA_LONG_VERSION_CODE, filter.versionCode); - resolvableFilter.putBundle(Intent.EXTRA_INSTANT_APP_EXTRAS, filter.extras); - resolvableFilters[i] = resolvableFilter; - if (i == 0) { - // for backwards compat, always set the first result on the intent and add - // the int version code - intent.putExtras(resolvableFilter); - intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) filter.versionCode); - } - } - intent.putExtra(Intent.EXTRA_INSTANT_APP_BUNDLES, resolvableFilters); - } - intent.setAction(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE); } + return intent; } @@ -365,131 +330,66 @@ public abstract class InstantAppResolver { final int[] shaPrefix = digest.getDigestPrefix(); final byte[][] digestBytes = digest.getDigestBytes(); final Intent failureIntent = new Intent(origIntent); - boolean requiresSecondPhase = false; failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL); failureIntent.setLaunchToken(token); - ArrayList filters = null; - boolean isWebIntent = origIntent.isBrowsableWebIntent(); - for (InstantAppResolveInfo instantAppResolveInfo : instantAppResolveInfoList) { - if (shaPrefix.length > 0 && instantAppResolveInfo.shouldLetInstallerDecide()) { - Slog.e(TAG, "InstantAppResolveInfo with mShouldLetInstallerDecide=true when digest" - + " provided; ignoring"); - continue; - } - byte[] filterDigestBytes = instantAppResolveInfo.getDigestBytes(); - // Only include matching digests if we have a prefix and we're either dealing with a - // web intent or the resolveInfo specifies digest details. - if (shaPrefix.length > 0 && (isWebIntent || filterDigestBytes.length > 0)) { - boolean matchFound = false; - // Go in reverse order so we match the narrowest scope first. - for (int i = shaPrefix.length - 1; i >= 0; --i) { - if (Arrays.equals(digestBytes[i], filterDigestBytes)) { - matchFound = true; - break; - } - } - if (!matchFound) { + // Go in reverse order so we match the narrowest scope first. + for (int i = shaPrefix.length - 1; i >= 0 ; --i) { + for (InstantAppResolveInfo instantAppInfo : instantAppResolveInfoList) { + if (!Arrays.equals(digestBytes[i], instantAppInfo.getDigestBytes())) { continue; } - } - // We matched a resolve info; resolve the filters to see if anything matches completely. - List matchFilters = computeResolveFilters( - origIntent, resolvedType, userId, packageName, token, instantAppResolveInfo); - if (matchFilters != null) { - if (matchFilters.isEmpty()) { - requiresSecondPhase = true; + if (packageName != null + && !packageName.equals(instantAppInfo.getPackageName())) { + continue; } - if (filters == null) { - filters = new ArrayList<>(matchFilters); - } else { - filters.addAll(matchFilters); + final List instantAppFilters = + instantAppInfo.getIntentFilters(); + // No filters; we need to start phase two + if (instantAppFilters == null || instantAppFilters.isEmpty()) { + if (DEBUG_EPHEMERAL) { + Log.d(TAG, "No app filters; go to phase 2"); + } + return new AuxiliaryResolveInfo(instantAppInfo, + new IntentFilter(Intent.ACTION_VIEW) /*intentFilter*/, + null /*splitName*/, token, true /*needsPhase2*/, + null /*failureIntent*/); } - } - } - if (filters != null && !filters.isEmpty()) { - return new AuxiliaryResolveInfo(token, requiresSecondPhase, failureIntent, filters); - } - // Hash or filter mis-match; no instant apps for this domain. - return null; - } - - /** - * Returns one of three states:

    - *

      - *
    • {@code null} if there are no matches will not be; resolution is unnecessary.
    • - *
    • An empty list signifying that a 2nd phase of resolution is required.
    • - *
    • A populated list meaning that matches were found and should be sent directly to the - * installer
    • - *
    - * - */ - private static List computeResolveFilters( - Intent origIntent, String resolvedType, int userId, String packageName, String token, - InstantAppResolveInfo instantAppInfo) { - if (instantAppInfo.shouldLetInstallerDecide()) { - return Collections.singletonList( - new AuxiliaryResolveInfo.AuxiliaryFilter( - instantAppInfo, null /* splitName */, - instantAppInfo.getExtras())); - } - if (packageName != null - && !packageName.equals(instantAppInfo.getPackageName())) { - return null; - } - final List instantAppFilters = - instantAppInfo.getIntentFilters(); - if (instantAppFilters == null || instantAppFilters.isEmpty()) { - // No filters on web intent; no matches, 2nd phase unnecessary. - if (origIntent.isBrowsableWebIntent()) { - return null; - } - // No filters; we need to start phase two - if (DEBUG_EPHEMERAL) { - Log.d(TAG, "No app filters; go to phase 2"); - } - return Collections.emptyList(); - } - final PackageManagerService.EphemeralIntentResolver instantAppResolver = - new PackageManagerService.EphemeralIntentResolver(); - for (int j = instantAppFilters.size() - 1; j >= 0; --j) { - final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); - final List splitFilters = instantAppFilter.getFilters(); - if (splitFilters == null || splitFilters.isEmpty()) { - continue; - } - for (int k = splitFilters.size() - 1; k >= 0; --k) { - IntentFilter filter = splitFilters.get(k); - Iterator authorities = - filter.authoritiesIterator(); - // ignore http/s-only filters. - if ((authorities == null || !authorities.hasNext()) - && (filter.hasDataScheme("http") || filter.hasDataScheme("https")) - && filter.hasAction(Intent.ACTION_VIEW) - && filter.hasCategory(Intent.CATEGORY_BROWSABLE)) { - continue; + // We have a domain match; resolve the filters to see if anything matches. + final PackageManagerService.EphemeralIntentResolver instantAppResolver = + new PackageManagerService.EphemeralIntentResolver(); + for (int j = instantAppFilters.size() - 1; j >= 0; --j) { + final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); + final List splitFilters = instantAppFilter.getFilters(); + if (splitFilters == null || splitFilters.isEmpty()) { + continue; + } + for (int k = splitFilters.size() - 1; k >= 0; --k) { + final AuxiliaryResolveInfo intentInfo = + new AuxiliaryResolveInfo(instantAppInfo, + splitFilters.get(k), instantAppFilter.getSplitName(), + token, false /*needsPhase2*/, failureIntent); + instantAppResolver.addFilter(intentInfo); + } } - instantAppResolver.addFilter( - new AuxiliaryResolveInfo.AuxiliaryFilter( - filter, - instantAppInfo, - instantAppFilter.getSplitName(), - instantAppInfo.getExtras() - )); - } - } - List matchedResolveInfoList = - instantAppResolver.queryIntent( + List matchedResolveInfoList = instantAppResolver.queryIntent( origIntent, resolvedType, false /*defaultOnly*/, userId); - if (!matchedResolveInfoList.isEmpty()) { - if (DEBUG_EPHEMERAL) { - Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList); + if (!matchedResolveInfoList.isEmpty()) { + if (DEBUG_EPHEMERAL) { + final AuxiliaryResolveInfo info = matchedResolveInfoList.get(0); + Log.d(TAG, "[" + token + "] Found match;" + + " package: " + info.packageName + + ", split: " + info.splitName + + ", versionCode: " + info.versionCode); + } + return matchedResolveInfoList.get(0); + } else if (DEBUG_EPHEMERAL) { + Log.d(TAG, "[" + token + "] No matches found" + + " package: " + instantAppInfo.getPackageName() + + ", versionCode: " + instantAppInfo.getVersionCode()); + } } - return matchedResolveInfoList; - } else if (DEBUG_EPHEMERAL) { - Log.d(TAG, "[" + token + "] No matches found" - + " package: " + instantAppInfo.getPackageName() - + ", versionCode: " + instantAppInfo.getVersionCode()); } + // Hash or filter mis-match; no instant apps for this domain. return null; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 18c5ffdc2ff..c6b55cc1d55 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3595,35 +3595,24 @@ Slog.e("TODD", } private @Nullable ActivityInfo getInstantAppInstallerLPr() { - String[] orderedActions = Build.IS_ENG - ? new String[]{ - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE + "_TEST", - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, - Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE} - : new String[]{ - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, - Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE}; + final Intent intent = new Intent(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE); final int resolveFlags = MATCH_DIRECT_BOOT_AWARE - | MATCH_DIRECT_BOOT_UNAWARE - | Intent.FLAG_IGNORE_EPHEMERAL - | (!Build.IS_ENG ? MATCH_SYSTEM_ONLY : 0); - final Intent intent = new Intent(); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE); - List matches = null; - for (String action : orderedActions) { - intent.setAction(action); + | MATCH_DIRECT_BOOT_UNAWARE + | (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0); + List matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, + resolveFlags, UserHandle.USER_SYSTEM); + // temporarily look for the old action + if (matches.isEmpty()) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Ephemeral installer not found with new action; try old one"); + } + intent.setAction(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE); matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, resolveFlags, UserHandle.USER_SYSTEM); - if (matches.isEmpty()) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "Instant App installer not found with " + action); - } - } else { - break; - } } Iterator iter = matches.iterator(); while (iter.hasNext()) { @@ -3631,8 +3620,7 @@ Slog.e("TODD", final PackageSetting ps = mSettings.mPackages.get(rInfo.activityInfo.packageName); if (ps != null) { final PermissionsState permissionsState = ps.getPermissionsState(); - if (permissionsState.hasPermission(Manifest.permission.INSTALL_PACKAGES, 0) - || Build.IS_ENG) { + if (permissionsState.hasPermission(Manifest.permission.INSTALL_PACKAGES, 0)) { continue; } } @@ -4791,7 +4779,10 @@ Slog.e("TODD", flags |= PackageManager.MATCH_INSTANT; } else { final boolean wantMatchInstant = (flags & PackageManager.MATCH_INSTANT) != 0; - final boolean allowMatchInstant = wantInstantApps + final boolean allowMatchInstant = + (wantInstantApps + && Intent.ACTION_VIEW.equals(intent.getAction()) + && hasWebURI(intent)) || (wantMatchInstant && canViewInstantApps(callingUid, userId)); flags &= ~(PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY | PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY); @@ -5981,14 +5972,8 @@ Slog.e("TODD", if (!skipPackageCheck && intent.getPackage() != null) { return false; } - if (!intent.isBrowsableWebIntent()) { - // for non web intents, we should not resolve externally if an app already exists to - // handle it or if the caller didn't explicitly request it. - if ((resolvedActivities != null && resolvedActivities.size() != 0) - || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) == 0) { - return false; - } - } else if (intent.getData() == null) { + final boolean isWebUri = hasWebURI(intent); + if (!isWebUri || intent.getData().getHost() == null) { return false; } // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. @@ -6386,7 +6371,7 @@ Slog.e("TODD", if (matches.get(i).getTargetUserId() == targetUserId) return true; } } - if (intent.hasWebURI()) { + if (hasWebURI(intent)) { // cross-profile app linking works only towards the parent. final int callingUid = Binder.getCallingUid(); final UserInfo parent = getProfileParent(sourceUserId); @@ -6561,7 +6546,7 @@ Slog.e("TODD", sortResult = true; } } - if (intent.hasWebURI()) { + if (hasWebURI(intent)) { CrossProfileDomainInfo xpDomainInfo = null; final UserInfo parent = getProfileParent(userId); if (parent != null) { @@ -6647,6 +6632,7 @@ Slog.e("TODD", if (ps.getInstantApp(userId)) { final long packedStatus = getDomainVerificationStatusLPr(ps, userId); final int status = (int)(packedStatus >> 32); + final int linkGeneration = (int)(packedStatus & 0xFFFFFFFF); if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { // there's a local instant application installed, but, the user has // chosen to never use it; skip resolution and don't acknowledge @@ -6678,8 +6664,9 @@ Slog.e("TODD", null /*responseObj*/, intent /*origIntent*/, resolvedType, null /*callingPackage*/, userId, null /*verificationBundle*/, resolveForStart); - auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne( - mInstantAppResolverConnection, requestObject); + auxiliaryResponse = + InstantAppResolver.doInstantAppResolutionPhaseOne( + mContext, mInstantAppResolverConnection, requestObject); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } else { // we have an instant application locally, but, we can't admit that since @@ -6688,40 +6675,35 @@ Slog.e("TODD", // instant application available externally. when it comes time to start // the instant application, we'll do the right thing. final ApplicationInfo ai = localInstantApp.activityInfo.applicationInfo; - auxiliaryResponse = new AuxiliaryResolveInfo(null /* failureActivity */, - ai.packageName, ai.versionCode, null /* splitName */); + auxiliaryResponse = new AuxiliaryResolveInfo( + ai.packageName, null /*splitName*/, null /*failureActivity*/, + ai.versionCode, null /*failureIntent*/); } } - if (intent.isBrowsableWebIntent() && auxiliaryResponse == null) { - return result; - } - final PackageSetting ps = mSettings.mPackages.get(mInstantAppInstallerActivity.packageName); - if (ps == null) { - return result; - } - final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo); - ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo( - mInstantAppInstallerActivity, 0, ps.readUserState(userId), userId); - ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART - | IntentFilter.MATCH_ADJUSTMENT_NORMAL; - // add a non-generic filter - ephemeralInstaller.filter = new IntentFilter(); - if (intent.getAction() != null) { - ephemeralInstaller.filter.addAction(intent.getAction()); - } - if (intent.getData() != null && intent.getData().getPath() != null) { - ephemeralInstaller.filter.addDataPath( - intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL); - } - ephemeralInstaller.isInstantAppAvailable = true; - // make sure this resolver is the default - ephemeralInstaller.isDefault = true; - ephemeralInstaller.auxiliaryInfo = auxiliaryResponse; - if (DEBUG_EPHEMERAL) { - Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); + if (auxiliaryResponse != null) { + if (DEBUG_EPHEMERAL) { + Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); + } + final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo); + final PackageSetting ps = + mSettings.mPackages.get(mInstantAppInstallerActivity.packageName); + if (ps != null) { + ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo( + mInstantAppInstallerActivity, 0, ps.readUserState(userId), userId); + ephemeralInstaller.activityInfo.launchToken = auxiliaryResponse.token; + ephemeralInstaller.auxiliaryInfo = auxiliaryResponse; + // make sure this resolver is the default + ephemeralInstaller.isDefault = true; + ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART + | IntentFilter.MATCH_ADJUSTMENT_NORMAL; + // add a non-generic filter + ephemeralInstaller.filter = new IntentFilter(intent.getAction()); + ephemeralInstaller.filter.addDataPath( + intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL); + ephemeralInstaller.isInstantAppAvailable = true; + result.add(ephemeralInstaller); + } } - - result.add(ephemeralInstaller); return result; } @@ -6836,11 +6818,10 @@ Slog.e("TODD", final ResolveInfo info = resolveInfos.get(i); // allow activities that are defined in the provided package if (allowDynamicSplits - && info.activityInfo != null && info.activityInfo.splitName != null && !ArrayUtils.contains(info.activityInfo.applicationInfo.splitNames, info.activityInfo.splitName)) { - if (mInstantAppInstallerActivity == null) { + if (mInstantAppInstallerInfo == null) { if (DEBUG_INSTALL) { Slog.v(TAG, "No installer - not adding it to the ResolveInfo list"); } @@ -6852,15 +6833,14 @@ Slog.e("TODD", if (DEBUG_INSTALL) { Slog.v(TAG, "Adding installer to the ResolveInfo list"); } - final ResolveInfo installerInfo = new ResolveInfo( - mInstantAppInstallerInfo); + final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo); final ComponentName installFailureActivity = findInstallFailureActivity( info.activityInfo.packageName, filterCallingUid, userId); installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( + info.activityInfo.packageName, info.activityInfo.splitName, installFailureActivity, - info.activityInfo.packageName, info.activityInfo.applicationInfo.versionCode, - info.activityInfo.splitName); + null /*failureIntent*/); installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART | IntentFilter.MATCH_ADJUSTMENT_NORMAL; // add a non-generic filter @@ -6877,7 +6857,6 @@ Slog.e("TODD", installerInfo.priority = info.priority; installerInfo.preferredOrder = info.preferredOrder; installerInfo.isDefault = info.isDefault; - installerInfo.isInstantAppAvailable = true; resolveInfos.set(i, installerInfo); continue; } @@ -6935,6 +6914,17 @@ Slog.e("TODD", return resolveInfos.size() > 0 && resolveInfos.get(0).priority >= 0; } + private static boolean hasWebURI(Intent intent) { + if (intent.getData() == null) { + return false; + } + final String scheme = intent.getScheme(); + if (TextUtils.isEmpty(scheme)) { + return false; + } + return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS); + } + private List filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent, int matchFlags, List candidates, CrossProfileDomainInfo xpDomainInfo, int userId) { @@ -7607,13 +7597,11 @@ Slog.e("TODD", if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } - final ResolveInfo installerInfo = new ResolveInfo( - mInstantAppInstallerInfo); + final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo); installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( - null /* installFailureActivity */, - info.serviceInfo.packageName, - info.serviceInfo.applicationInfo.versionCode, - info.serviceInfo.splitName); + info.serviceInfo.packageName, info.serviceInfo.splitName, + null /*failureActivity*/, info.serviceInfo.applicationInfo.versionCode, + null /*failureIntent*/); // make sure this resolver is the default installerInfo.isDefault = true; installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART @@ -7729,13 +7717,11 @@ Slog.e("TODD", if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } - final ResolveInfo installerInfo = new ResolveInfo( - mInstantAppInstallerInfo); + final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo); installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( - null /*failureActivity*/, - info.providerInfo.packageName, - info.providerInfo.applicationInfo.versionCode, - info.providerInfo.splitName); + info.providerInfo.packageName, info.providerInfo.splitName, + null /*failureActivity*/, info.providerInfo.applicationInfo.versionCode, + null /*failureIntent*/); // make sure this resolver is the default installerInfo.isDefault = true; installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART @@ -11777,7 +11763,7 @@ Slog.e("TODD", mInstantAppInstallerActivity.exported = true; mInstantAppInstallerActivity.enabled = true; mInstantAppInstallerInfo.activityInfo = mInstantAppInstallerActivity; - mInstantAppInstallerInfo.priority = 1; + mInstantAppInstallerInfo.priority = 0; mInstantAppInstallerInfo.preferredOrder = 1; mInstantAppInstallerInfo.isDefault = true; mInstantAppInstallerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART @@ -13227,8 +13213,7 @@ Slog.e("TODD", } static final class EphemeralIntentResolver - extends IntentResolver { + extends IntentResolver { /** * The result that has the highest defined order. Ordering applies on a * per-package basis. Mapping is from package name to Pair of order and @@ -13243,19 +13228,18 @@ Slog.e("TODD", final ArrayMap> mOrderResult = new ArrayMap<>(); @Override - protected AuxiliaryResolveInfo.AuxiliaryFilter[] newArray(int size) { - return new AuxiliaryResolveInfo.AuxiliaryFilter[size]; + protected AuxiliaryResolveInfo[] newArray(int size) { + return new AuxiliaryResolveInfo[size]; } @Override - protected boolean isPackageForFilter(String packageName, - AuxiliaryResolveInfo.AuxiliaryFilter responseObj) { + protected boolean isPackageForFilter(String packageName, AuxiliaryResolveInfo responseObj) { return true; } @Override - protected AuxiliaryResolveInfo.AuxiliaryFilter newResult( - AuxiliaryResolveInfo.AuxiliaryFilter responseObj, int match, int userId) { + protected AuxiliaryResolveInfo newResult(AuxiliaryResolveInfo responseObj, int match, + int userId) { if (!sUserManager.exists(userId)) { return null; } @@ -13276,7 +13260,7 @@ Slog.e("TODD", } @Override - protected void filterResults(List results) { + protected void filterResults(List results) { // only do work if ordering is enabled [most of the time it won't be] if (mOrderResult.size() == 0) { return; -- GitLab From 110f9a12b22a5b93f524b2d95f3c90fa04cefaa7 Mon Sep 17 00:00:00 2001 From: Salvador Martinez Date: Wed, 31 Jan 2018 09:57:17 -0800 Subject: [PATCH 177/416] Move showing notification to background thread The new notification logic can result in an IPC so to avoid doing this on the main thread we should just handle all the battery warning logic in the background. Test: SysUI tests still pass Bug: 72622147 Change-Id: I0c1ad9ebde2ea8d0404d9f6fd019e8620419e61d --- .../SystemUI/src/com/android/systemui/power/PowerUI.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index b43e99be828..e661fa7c224 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -44,6 +44,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; +import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SystemUI; @@ -242,7 +243,9 @@ public class PowerUI extends SystemUI { } // Show the correct version of low battery warning if needed - maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket); + ThreadUtils.postOnBackgroundThread(() -> { + maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket); + }); } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { mScreenOffTime = SystemClock.elapsedRealtime(); -- GitLab From 139e1b5c012630d6ec517fd46c6abb18d1f82694 Mon Sep 17 00:00:00 2001 From: Florina Muntenescu Date: Wed, 24 Jan 2018 15:52:00 +0000 Subject: [PATCH 178/416] Updating more spans Test: N/A Bug: 72092996 Change-Id: Iecd3595a03cde2a1d58e4be00b7cf9b6783d4e2a --- .../text/style/DrawableMarginSpan.java | 84 ++++++--- .../text/style/DynamicDrawableSpan.java | 79 ++++++--- .../android/text/style/IconMarginSpan.java | 75 ++++++-- core/java/android/text/style/ImageSpan.java | 161 ++++++++++++++---- .../android/text/style/LineHeightSpan.java | 40 ++++- .../android/text/style/MaskFilterSpan.java | 23 ++- core/java/android/text/style/StyleSpan.java | 44 +++-- core/java/android/text/style/TabStopSpan.java | 55 +++--- .../java/android/text/style/TypefaceSpan.java | 44 +++-- core/java/android/text/style/URLSpan.java | 42 ++++- .../images/text/style/drawablemarginspan.png | Bin 0 -> 18930 bytes .../images/text/style/dynamicdrawablespan.png | Bin 0 -> 17443 bytes .../images/text/style/iconmarginspan.png | Bin 0 -> 22271 bytes .../reference/images/text/style/imagespan.png | Bin 0 -> 33683 bytes .../images/text/style/maskfilterspan.png | Bin 0 -> 6411 bytes .../reference/images/text/style/stylespan.png | Bin 0 -> 6371 bytes .../images/text/style/tabstopspan.png | Bin 0 -> 8963 bytes .../images/text/style/typefacespan.png | Bin 0 -> 7749 bytes .../reference/images/text/style/urlspan.png | Bin 0 -> 9769 bytes 19 files changed, 501 insertions(+), 146 deletions(-) create mode 100644 docs/html/reference/images/text/style/drawablemarginspan.png create mode 100644 docs/html/reference/images/text/style/dynamicdrawablespan.png create mode 100644 docs/html/reference/images/text/style/iconmarginspan.png create mode 100644 docs/html/reference/images/text/style/imagespan.png create mode 100644 docs/html/reference/images/text/style/maskfilterspan.png create mode 100644 docs/html/reference/images/text/style/stylespan.png create mode 100644 docs/html/reference/images/text/style/tabstopspan.png create mode 100644 docs/html/reference/images/text/style/typefacespan.png create mode 100644 docs/html/reference/images/text/style/urlspan.png diff --git a/core/java/android/text/style/DrawableMarginSpan.java b/core/java/android/text/style/DrawableMarginSpan.java index 35241796c3c..cd199b3547c 100644 --- a/core/java/android/text/style/DrawableMarginSpan.java +++ b/core/java/android/text/style/DrawableMarginSpan.java @@ -16,60 +16,100 @@ package android.text.style; +import android.annotation.NonNull; +import android.annotation.Px; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.text.Layout; import android.text.Spanned; -public class DrawableMarginSpan -implements LeadingMarginSpan, LineHeightSpan -{ - public DrawableMarginSpan(Drawable b) { - mDrawable = b; +/** + * A span which adds a drawable and a padding to the paragraph it's attached to. + *

    + * If the height of the drawable is bigger than the height of the line it's attached to then the + * line height is increased to fit the drawable. DrawableMarginSpan allows setting a + * padding between the drawable and the text. The default value is 0. The span must be set from the + * beginning of the text, otherwise either the span won't be rendered or it will be rendered + * incorrectly. + *

    + * For example, a drawable and a padding of 20px can be added like this: + *

    {@code SpannableString string = new SpannableString("Text with a drawable.");
    + * string.setSpan(new DrawableMarginSpan(drawable, 20), 0, string.length(),
    + * Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}
    + * + *
    Text with a drawable and a padding.
    + *

    + * + * @see IconMarginSpan for working with a {@link android.graphics.Bitmap} instead of + * a {@link Drawable}. + */ +public class DrawableMarginSpan implements LeadingMarginSpan, LineHeightSpan { + private static final int STANDARD_PAD_WIDTH = 0; + + @NonNull + private final Drawable mDrawable; + @Px + private final int mPad; + + /** + * Creates a {@link DrawableMarginSpan} from a {@link Drawable}. The pad width will be 0. + * + * @param drawable the drawable to be added + */ + public DrawableMarginSpan(@NonNull Drawable drawable) { + this(drawable, STANDARD_PAD_WIDTH); } - public DrawableMarginSpan(Drawable b, int pad) { - mDrawable = b; + /** + * Creates a {@link DrawableMarginSpan} from a {@link Drawable} and a padding, in pixels. + * + * @param drawable the drawable to be added + * @param pad the distance between the drawable and the text + */ + public DrawableMarginSpan(@NonNull Drawable drawable, int pad) { + mDrawable = drawable; mPad = pad; } + @Override public int getLeadingMargin(boolean first) { return mDrawable.getIntrinsicWidth() + mPad; } - public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, - int top, int baseline, int bottom, - CharSequence text, int start, int end, - boolean first, Layout layout) { + @Override + public void drawLeadingMargin(@NonNull Canvas c, @NonNull Paint p, int x, int dir, + int top, int baseline, int bottom, + @NonNull CharSequence text, int start, int end, + boolean first, @NonNull Layout layout) { int st = ((Spanned) text).getSpanStart(this); - int ix = (int)x; - int itop = (int)layout.getLineTop(layout.getLineForOffset(st)); + int ix = (int) x; + int itop = (int) layout.getLineTop(layout.getLineForOffset(st)); int dw = mDrawable.getIntrinsicWidth(); int dh = mDrawable.getIntrinsicHeight(); // XXX What to do about Paint? - mDrawable.setBounds(ix, itop, ix+dw, itop+dh); + mDrawable.setBounds(ix, itop, ix + dw, itop + dh); mDrawable.draw(c); } - public void chooseHeight(CharSequence text, int start, int end, - int istartv, int v, - Paint.FontMetricsInt fm) { + @Override + public void chooseHeight(@NonNull CharSequence text, int start, int end, + int istartv, int v, + @NonNull Paint.FontMetricsInt fm) { if (end == ((Spanned) text).getSpanEnd(this)) { int ht = mDrawable.getIntrinsicHeight(); int need = ht - (v + fm.descent - fm.ascent - istartv); - if (need > 0) + if (need > 0) { fm.descent += need; + } need = ht - (v + fm.bottom - fm.top - istartv); - if (need > 0) + if (need > 0) { fm.bottom += need; + } } } - - private Drawable mDrawable; - private int mPad; } diff --git a/core/java/android/text/style/DynamicDrawableSpan.java b/core/java/android/text/style/DynamicDrawableSpan.java index 5b8a6dd3bf6..1b16f3345bf 100644 --- a/core/java/android/text/style/DynamicDrawableSpan.java +++ b/core/java/android/text/style/DynamicDrawableSpan.java @@ -16,6 +16,9 @@ package android.text.style; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; @@ -24,32 +27,71 @@ import android.graphics.drawable.Drawable; import java.lang.ref.WeakReference; /** + * Span that replaces the text it's attached to with a {@link Drawable} that can be aligned with + * the bottom or with the baseline of the surrounding text. + *

    + * For an implementation that constructs the drawable from various sources (Bitmap, + * Drawable, resource id or Uri) use {@link ImageSpan}. + *

    + * A simple implementation of DynamicDrawableSpan that uses drawables from resources + * looks like this: + *

    + * class MyDynamicDrawableSpan extends DynamicDrawableSpan {
      *
    + * private final Context mContext;
    + * private final int mResourceId;
    + *
    + * public MyDynamicDrawableSpan(Context context, @DrawableRes int resourceId) {
    + *     mContext = context;
    + *     mResourceId = resourceId;
    + * }
    + *
    + * {@literal @}Override
    + * public Drawable getDrawable() {
    + *      Drawable drawable = mContext.getDrawable(mResourceId);
    + *      drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
    + *      return drawable;
    + * }
    + * }
    + * The class can be used like this: + *
    + * SpannableString string = new SpannableString("Text with a drawable span");
    + * string.setSpan(new MyDynamicDrawableSpan(context, R.mipmap.ic_launcher), 12, 20, Spanned
    + * .SPAN_EXCLUSIVE_EXCLUSIVE);
    + * + *
    Replacing text with a drawable.
    */ public abstract class DynamicDrawableSpan extends ReplacementSpan { - private static final String TAG = "DynamicDrawableSpan"; - + /** * A constant indicating that the bottom of this span should be aligned * with the bottom of the surrounding text, i.e., at the same level as the * lowest descender in the text. */ public static final int ALIGN_BOTTOM = 0; - + /** * A constant indicating that the bottom of this span should be aligned * with the baseline of the surrounding text. */ public static final int ALIGN_BASELINE = 1; - + protected final int mVerticalAlignment; - + + private WeakReference mDrawableRef; + + /** + * Creates a {@link DynamicDrawableSpan}. The default vertical alignment is + * {@link #ALIGN_BOTTOM} + */ public DynamicDrawableSpan() { mVerticalAlignment = ALIGN_BOTTOM; } /** - * @param verticalAlignment one of {@link #ALIGN_BOTTOM} or {@link #ALIGN_BASELINE}. + * Creates a {@link DynamicDrawableSpan} based on a vertical alignment.\ + * + * @param verticalAlignment one of {@link #ALIGN_BOTTOM} or {@link #ALIGN_BASELINE} */ protected DynamicDrawableSpan(int verticalAlignment) { mVerticalAlignment = verticalAlignment; @@ -64,22 +106,22 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { } /** - * Your subclass must implement this method to provide the bitmap + * Your subclass must implement this method to provide the bitmap * to be drawn. The dimensions of the bitmap must be the same * from each call to the next. */ public abstract Drawable getDrawable(); @Override - public int getSize(Paint paint, CharSequence text, - int start, int end, - Paint.FontMetricsInt fm) { + public int getSize(@NonNull Paint paint, CharSequence text, + @IntRange(from = 0) int start, @IntRange(from = 0) int end, + @Nullable Paint.FontMetricsInt fm) { Drawable d = getCachedDrawable(); Rect rect = d.getBounds(); if (fm != null) { - fm.ascent = -rect.bottom; - fm.descent = 0; + fm.ascent = -rect.bottom; + fm.descent = 0; fm.top = fm.ascent; fm.bottom = 0; @@ -89,12 +131,12 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { } @Override - public void draw(Canvas canvas, CharSequence text, - int start, int end, float x, - int top, int y, int bottom, Paint paint) { + public void draw(@NonNull Canvas canvas, CharSequence text, + @IntRange(from = 0) int start, @IntRange(from = 0) int end, float x, + int top, int y, int bottom, @NonNull Paint paint) { Drawable b = getCachedDrawable(); canvas.save(); - + int transY = bottom - b.getBounds().bottom; if (mVerticalAlignment == ALIGN_BASELINE) { transY -= paint.getFontMetricsInt().descent; @@ -109,8 +151,9 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { WeakReference wr = mDrawableRef; Drawable d = null; - if (wr != null) + if (wr != null) { d = wr.get(); + } if (d == null) { d = getDrawable(); @@ -119,7 +162,5 @@ public abstract class DynamicDrawableSpan extends ReplacementSpan { return d; } - - private WeakReference mDrawableRef; } diff --git a/core/java/android/text/style/IconMarginSpan.java b/core/java/android/text/style/IconMarginSpan.java index 304c83f19f0..ad78bd57469 100644 --- a/core/java/android/text/style/IconMarginSpan.java +++ b/core/java/android/text/style/IconMarginSpan.java @@ -16,57 +16,98 @@ package android.text.style; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Px; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.text.Layout; import android.text.Spanned; -public class IconMarginSpan -implements LeadingMarginSpan, LineHeightSpan -{ - public IconMarginSpan(Bitmap b) { - mBitmap = b; +/** + * Paragraph affecting span, that draws a bitmap at the beginning of a text. The span also allows + * setting a padding between the bitmap and the text. The default value of the padding is 0px. The + * span should be attached from the first character of the text. + *

    + * For example, an IconMarginSpan with a bitmap and a padding of 30px can be set + * like this: + *

    + * SpannableString string = new SpannableString("Text with icon and padding");
    + * string.setSpan(new IconMarginSpan(bitmap, 30), 0, string.length(),
    + * Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    + * 
    + * + *
    Text with IconMarginSpan
    + *

    + * + * @see DrawableMarginSpan for working with a {@link android.graphics.drawable.Drawable} instead of + * a {@link Bitmap}. + */ +public class IconMarginSpan implements LeadingMarginSpan, LineHeightSpan { + + @NonNull + private final Bitmap mBitmap; + @Px + private final int mPad; + + /** + * Creates an {@link IconMarginSpan} from a {@link Bitmap}. + * + * @param bitmap bitmap to be rendered at the beginning of the text + */ + public IconMarginSpan(@NonNull Bitmap bitmap) { + this(bitmap, 0); } - public IconMarginSpan(Bitmap b, int pad) { - mBitmap = b; + /** + * Creates an {@link IconMarginSpan} from a {@link Bitmap}. + * + * @param bitmap bitmap to be rendered at the beginning of the text + * @param pad padding width, in pixels, between the bitmap and the text + */ + public IconMarginSpan(@NonNull Bitmap bitmap, @IntRange(from = 0) int pad) { + mBitmap = bitmap; mPad = pad; } + @Override public int getLeadingMargin(boolean first) { return mBitmap.getWidth() + mPad; } + @Override public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, - int top, int baseline, int bottom, - CharSequence text, int start, int end, - boolean first, Layout layout) { + int top, int baseline, int bottom, + CharSequence text, int start, int end, + boolean first, Layout layout) { int st = ((Spanned) text).getSpanStart(this); int itop = layout.getLineTop(layout.getLineForOffset(st)); - if (dir < 0) + if (dir < 0) { x -= mBitmap.getWidth(); + } c.drawBitmap(mBitmap, x, itop, p); } + @Override public void chooseHeight(CharSequence text, int start, int end, - int istartv, int v, - Paint.FontMetricsInt fm) { + int istartv, int v, + Paint.FontMetricsInt fm) { if (end == ((Spanned) text).getSpanEnd(this)) { int ht = mBitmap.getHeight(); int need = ht - (v + fm.descent - fm.ascent - istartv); - if (need > 0) + if (need > 0) { fm.descent += need; + } need = ht - (v + fm.bottom - fm.top - istartv); - if (need > 0) + if (need > 0) { fm.bottom += need; + } } } - private Bitmap mBitmap; - private int mPad; } diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java index b0bff680f39..95f0b43341a 100644 --- a/core/java/android/text/style/ImageSpan.java +++ b/core/java/android/text/style/ImageSpan.java @@ -17,6 +17,8 @@ package android.text.style; import android.annotation.DrawableRes; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -27,18 +29,49 @@ import android.util.Log; import java.io.InputStream; +/** + * Span that replaces the text it's attached to with a {@link Drawable} that can be aligned with + * the bottom or with the baseline of the surrounding text. The drawable can be constructed from + * varied sources: + *

      + *
    • {@link Bitmap} - see {@link #ImageSpan(Context, Bitmap)} and + * {@link #ImageSpan(Context, Bitmap, int)} + *
    • + *
    • {@link Drawable} - see {@link #ImageSpan(Drawable, int)}
    • + *
    • resource id - see {@link #ImageSpan(Context, int, int)}
    • + *
    • {@link Uri} - see {@link #ImageSpan(Context, Uri, int)}
    • + *
    + * The default value for the vertical alignment is {@link DynamicDrawableSpan#ALIGN_BOTTOM} + *

    + * For example, an ImagedSpan can be used like this: + *

    + * SpannableString string = SpannableString("Bottom: span.\nBaseline: span.");
    + * // using the default alignment: ALIGN_BOTTOM
    + * string.setSpan(ImageSpan(this, R.mipmap.ic_launcher), 7, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    + * string.setSpan(ImageSpan(this, R.mipmap.ic_launcher, DynamicDrawableSpan.ALIGN_BASELINE),
    + * 22, 23, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    + * 
    + * + *
    Text with ImageSpans aligned bottom and baseline.
    + */ public class ImageSpan extends DynamicDrawableSpan { + + @Nullable private Drawable mDrawable; + @Nullable private Uri mContentUri; + @DrawableRes private int mResourceId; + @Nullable private Context mContext; + @Nullable private String mSource; /** * @deprecated Use {@link #ImageSpan(Context, Bitmap)} instead. */ @Deprecated - public ImageSpan(Bitmap b) { + public ImageSpan(@NonNull Bitmap b) { this(null, b, ALIGN_BOTTOM); } @@ -46,80 +79,143 @@ public class ImageSpan extends DynamicDrawableSpan { * @deprecated Use {@link #ImageSpan(Context, Bitmap, int)} instead. */ @Deprecated - public ImageSpan(Bitmap b, int verticalAlignment) { + public ImageSpan(@NonNull Bitmap b, int verticalAlignment) { this(null, b, verticalAlignment); } - public ImageSpan(Context context, Bitmap b) { - this(context, b, ALIGN_BOTTOM); + /** + * Constructs an {@link ImageSpan} from a {@link Context} and a {@link Bitmap} with the default + * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM} + * + * @param context context used to create a drawable from {@param bitmap} based on the display + * metrics of the resources + * @param bitmap bitmap to be rendered + */ + public ImageSpan(@NonNull Context context, @NonNull Bitmap bitmap) { + this(context, bitmap, ALIGN_BOTTOM); } /** + * Constructs an {@link ImageSpan} from a {@link Context}, a {@link Bitmap} and a vertical + * alignment. + * + * @param context context used to create a drawable from {@param bitmap} based on + * the display metrics of the resources + * @param bitmap bitmap to be rendered * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or - * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ - public ImageSpan(Context context, Bitmap b, int verticalAlignment) { + public ImageSpan(@NonNull Context context, @NonNull Bitmap bitmap, int verticalAlignment) { super(verticalAlignment); mContext = context; mDrawable = context != null - ? new BitmapDrawable(context.getResources(), b) - : new BitmapDrawable(b); + ? new BitmapDrawable(context.getResources(), bitmap) + : new BitmapDrawable(bitmap); int width = mDrawable.getIntrinsicWidth(); int height = mDrawable.getIntrinsicHeight(); - mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0); + mDrawable.setBounds(0, 0, width > 0 ? width : 0, height > 0 ? height : 0); } - public ImageSpan(Drawable d) { - this(d, ALIGN_BOTTOM); + /** + * Constructs an {@link ImageSpan} from a drawable with the default + * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}. + * + * @param drawable drawable to be rendered + */ + public ImageSpan(@NonNull Drawable drawable) { + this(drawable, ALIGN_BOTTOM); } /** + * Constructs an {@link ImageSpan} from a drawable and a vertical alignment. + * + * @param drawable drawable to be rendered * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or - * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ - public ImageSpan(Drawable d, int verticalAlignment) { + public ImageSpan(@NonNull Drawable drawable, int verticalAlignment) { super(verticalAlignment); - mDrawable = d; + mDrawable = drawable; } - public ImageSpan(Drawable d, String source) { - this(d, source, ALIGN_BOTTOM); + /** + * Constructs an {@link ImageSpan} from a drawable and a source with the default + * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM} + * + * @param drawable drawable to be rendered + * @param source drawable's Uri source + */ + public ImageSpan(@NonNull Drawable drawable, @NonNull String source) { + this(drawable, source, ALIGN_BOTTOM); } /** + * Constructs an {@link ImageSpan} from a drawable, a source and a vertical alignment. + * + * @param drawable drawable to be rendered + * @param source drawable's uri source * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or - * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ - public ImageSpan(Drawable d, String source, int verticalAlignment) { + public ImageSpan(@NonNull Drawable drawable, @NonNull String source, int verticalAlignment) { super(verticalAlignment); - mDrawable = d; + mDrawable = drawable; mSource = source; } - public ImageSpan(Context context, Uri uri) { + /** + * Constructs an {@link ImageSpan} from a {@link Context} and a {@link Uri} with the default + * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM}. The Uri source can be retrieved via + * {@link #getSource()} + * + * @param context context used to create a drawable from {@param bitmap} based on the display + * metrics of the resources + * @param uri {@link Uri} used to construct the drawable that will be rendered + */ + public ImageSpan(@NonNull Context context, @NonNull Uri uri) { this(context, uri, ALIGN_BOTTOM); } /** + * Constructs an {@link ImageSpan} from a {@link Context}, a {@link Uri} and a vertical + * alignment. The Uri source can be retrieved via {@link #getSource()} + * + * @param context context used to create a drawable from {@param bitmap} based on + * the display + * metrics of the resources + * @param uri {@link Uri} used to construct the drawable that will be rendered. * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or - * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ - public ImageSpan(Context context, Uri uri, int verticalAlignment) { + public ImageSpan(@NonNull Context context, @NonNull Uri uri, int verticalAlignment) { super(verticalAlignment); mContext = context; mContentUri = uri; mSource = uri.toString(); } - public ImageSpan(Context context, @DrawableRes int resourceId) { + /** + * Constructs an {@link ImageSpan} from a {@link Context} and a resource id with the default + * alignment {@link DynamicDrawableSpan#ALIGN_BOTTOM} + * + * @param context context used to retrieve the drawable from resources + * @param resourceId drawable resource id based on which the drawable is retrieved + */ + public ImageSpan(@NonNull Context context, @DrawableRes int resourceId) { this(context, resourceId, ALIGN_BOTTOM); } /** + * Constructs an {@link ImageSpan} from a {@link Context}, a resource id and a vertical + * alignment. + * + * @param context context used to retrieve the drawable from resources + * @param resourceId drawable resource id based on which the drawable is retrieved. * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or - * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + * {@link DynamicDrawableSpan#ALIGN_BASELINE} */ - public ImageSpan(Context context, @DrawableRes int resourceId, int verticalAlignment) { + public ImageSpan(@NonNull Context context, @DrawableRes int resourceId, + int verticalAlignment) { super(verticalAlignment); mContext = context; mResourceId = resourceId; @@ -128,10 +224,10 @@ public class ImageSpan extends DynamicDrawableSpan { @Override public Drawable getDrawable() { Drawable drawable = null; - + if (mDrawable != null) { drawable = mDrawable; - } else if (mContentUri != null) { + } else if (mContentUri != null) { Bitmap bitmap = null; try { InputStream is = mContext.getContentResolver().openInputStream( @@ -142,7 +238,7 @@ public class ImageSpan extends DynamicDrawableSpan { drawable.getIntrinsicHeight()); is.close(); } catch (Exception e) { - Log.e("sms", "Failed to loaded content " + mContentUri, e); + Log.e("ImageSpan", "Failed to loaded content " + mContentUri, e); } } else { try { @@ -150,8 +246,8 @@ public class ImageSpan extends DynamicDrawableSpan { drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); } catch (Exception e) { - Log.e("sms", "Unable to find resource: " + mResourceId); - } + Log.e("ImageSpan", "Unable to find resource: " + mResourceId); + } } return drawable; @@ -159,9 +255,12 @@ public class ImageSpan extends DynamicDrawableSpan { /** * Returns the source string that was saved during construction. + * + * @return the source string that was saved during construction + * @see #ImageSpan(Drawable, String) and this{@link #ImageSpan(Context, Uri)} */ + @Nullable public String getSource() { return mSource; } - } diff --git a/core/java/android/text/style/LineHeightSpan.java b/core/java/android/text/style/LineHeightSpan.java index 1ebee82c18a..50ee5f38759 100644 --- a/core/java/android/text/style/LineHeightSpan.java +++ b/core/java/android/text/style/LineHeightSpan.java @@ -19,16 +19,42 @@ package android.text.style; import android.graphics.Paint; import android.text.TextPaint; -public interface LineHeightSpan -extends ParagraphStyle, WrapTogetherSpan -{ +/** + * The classes that affect the height of the line should implement this interface. + */ +public interface LineHeightSpan extends ParagraphStyle, WrapTogetherSpan { + /** + * Classes that implement this should define how the height is being calculated. + * + * @param text the text + * @param start the start of the line + * @param end the end of the line + * @param spanstartv the start of the span + * @param lineHeight the line height + * @param fm font metrics of the paint, in integers + */ public void chooseHeight(CharSequence text, int start, int end, - int spanstartv, int v, - Paint.FontMetricsInt fm); + int spanstartv, int lineHeight, + Paint.FontMetricsInt fm); + /** + * The classes that affect the height of the line with respect to density, should implement this + * interface. + */ public interface WithDensity extends LineHeightSpan { + + /** + * Classes that implement this should define how the height is being calculated. + * + * @param text the text + * @param start the start of the line + * @param end the end of the line + * @param spanstartv the start of the span + * @param lineHeight the line height + * @param paint the paint + */ public void chooseHeight(CharSequence text, int start, int end, - int spanstartv, int v, - Paint.FontMetricsInt fm, TextPaint paint); + int spanstartv, int lineHeight, + Paint.FontMetricsInt fm, TextPaint paint); } } diff --git a/core/java/android/text/style/MaskFilterSpan.java b/core/java/android/text/style/MaskFilterSpan.java index 2ff52a8f70e..d76ef941d42 100644 --- a/core/java/android/text/style/MaskFilterSpan.java +++ b/core/java/android/text/style/MaskFilterSpan.java @@ -18,15 +18,36 @@ package android.text.style; import android.graphics.MaskFilter; import android.text.TextPaint; - +/** + * Span that allows setting a {@link MaskFilter} to the text it's attached to. + *

    + * For example, to blur a text, a {@link android.graphics.BlurMaskFilter} can be used: + *

    + * MaskFilter blurMask = new BlurMaskFilter(5f, BlurMaskFilter.Blur.NORMAL);
    + * SpannableString string = new SpannableString("Text with blur mask");
    + * string.setSpan(new MaskFilterSpan(blurMask), 10, 15, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    + * 
    + * + *
    Text blurred with the MaskFilterSpan.
    + */ public class MaskFilterSpan extends CharacterStyle implements UpdateAppearance { private MaskFilter mFilter; + /** + * Creates a {@link MaskFilterSpan} from a {@link MaskFilter}. + * + * @param filter the filter to be applied to the TextPaint + */ public MaskFilterSpan(MaskFilter filter) { mFilter = filter; } + /** + * Return the mask filter for this span. + * + * @return the mask filter for this span + */ public MaskFilter getMaskFilter() { return mFilter; } diff --git a/core/java/android/text/style/StyleSpan.java b/core/java/android/text/style/StyleSpan.java index f900db502d0..bdfa700215f 100644 --- a/core/java/android/text/style/StyleSpan.java +++ b/core/java/android/text/style/StyleSpan.java @@ -16,6 +16,7 @@ package android.text.style; +import android.annotation.NonNull; import android.graphics.Paint; import android.graphics.Typeface; import android.os.Parcel; @@ -24,55 +25,76 @@ import android.text.TextPaint; import android.text.TextUtils; /** - * - * Describes a style in a span. + * Span that allows setting the style of the text it's attached to. + * Possible styles are: {@link Typeface#NORMAL}, {@link Typeface#BOLD}, {@link Typeface#ITALIC} and + * {@link Typeface#BOLD_ITALIC}. + *

    * Note that styles are cumulative -- if both bold and italic are set in * separate spans, or if the base style is bold and a span calls for italic, * you get bold italic. You can't turn off a style from the base style. - * + *

    + * For example, the StyleSpan can be used like this: + *

    + * SpannableString string = new SpannableString("Bold and italic text");
    + * string.setSpan(new StyleSpan(Typeface.BOLD), 0, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    + * string.setSpan(new StyleSpan(Typeface.ITALIC), 9, 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    + * 
    + * + *
    Text styled bold and italic with the StyleSpan.
    */ public class StyleSpan extends MetricAffectingSpan implements ParcelableSpan { private final int mStyle; /** - * + * Creates a {@link StyleSpan} from a style. + * * @param style An integer constant describing the style for this span. Examples - * include bold, italic, and normal. Values are constants defined - * in {@link android.graphics.Typeface}. + * include bold, italic, and normal. Values are constants defined + * in {@link Typeface}. */ public StyleSpan(int style) { mStyle = style; } - public StyleSpan(Parcel src) { + /** + * Creates a {@link StyleSpan} from a parcel. + * + * @param src the parcel + */ + public StyleSpan(@NonNull Parcel src) { mStyle = src.readInt(); } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.STYLE_SPAN; } - + + @Override public int describeContents() { return 0; } + @Override public void writeToParcel(Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeInt(mStyle); } /** - * Returns the style constant defined in {@link android.graphics.Typeface}. + * Returns the style constant defined in {@link Typeface}. */ public int getStyle() { return mStyle; diff --git a/core/java/android/text/style/TabStopSpan.java b/core/java/android/text/style/TabStopSpan.java index 05664284181..2cceb2c8ced 100644 --- a/core/java/android/text/style/TabStopSpan.java +++ b/core/java/android/text/style/TabStopSpan.java @@ -16,39 +16,54 @@ package android.text.style; +import android.annotation.IntRange; +import android.annotation.Px; + /** - * Represents a single tab stop on a line. + * Paragraph affecting span that changes the position of the tab with respect to + * the leading margin of the line. TabStopSpan will only affect the first tab + * encountered on the first line of the text. */ -public interface TabStopSpan -extends ParagraphStyle -{ +public interface TabStopSpan extends ParagraphStyle { + /** - * Returns the offset of the tab stop from the leading margin of the - * line. - * @return the offset + * Returns the offset of the tab stop from the leading margin of the line, in pixels. + * + * @return the offset, in pixels */ - public int getTabStop(); + int getTabStop(); /** - * The default implementation of TabStopSpan. + * The default implementation of TabStopSpan that allows setting the offset of the tab stop + * from the leading margin of the first line of text. + *

    + * Let's consider that we have the following text: "\tParagraph text beginning with tab." + * and we want to move the tab stop with 100px. This can be achieved like this: + *

    +     * SpannableString string = new SpannableString("\tParagraph text beginning with tab.");
    +     * string.setSpan(new TabStopSpan.Standard(100), 0, string.length(),
    +     * Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    + * + *
    Text with a tab stop and a TabStopSpan
    */ - public static class Standard - implements TabStopSpan - { + class Standard implements TabStopSpan { + + @Px + private int mTabOffset; + /** - * Constructor. + * Constructs a {@link TabStopSpan.Standard} based on an offset. * - * @param where the offset of the tab stop from the leading margin of - * the line + * @param offset the offset of the tab stop from the leading margin of + * the line, in pixels */ - public Standard(int where) { - mTab = where; + public Standard(@IntRange(from = 0) int offset) { + mTabOffset = offset; } + @Override public int getTabStop() { - return mTab; + return mTabOffset; } - - private int mTab; } } diff --git a/core/java/android/text/style/TypefaceSpan.java b/core/java/android/text/style/TypefaceSpan.java index aa622d87c74..16228125020 100644 --- a/core/java/android/text/style/TypefaceSpan.java +++ b/core/java/android/text/style/TypefaceSpan.java @@ -16,6 +16,7 @@ package android.text.style; +import android.annotation.NonNull; import android.graphics.Paint; import android.graphics.Typeface; import android.os.Parcel; @@ -24,42 +25,59 @@ import android.text.TextPaint; import android.text.TextUtils; /** - * Changes the typeface family of the text to which the span is attached. + * Changes the typeface family of the text to which the span is attached. Examples of typeface + * family include "monospace", "serif", and "sans-serif". + *

    + * For example, change the typeface of a text to "monospace" like this: + *

    + * SpannableString string = new SpannableString("Text with typeface span");
    + * string.setSpan(new TypefaceSpan("monospace"), 10, 18, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    + * 
    + * + *
    Text with "monospace" typeface family.
    */ public class TypefaceSpan extends MetricAffectingSpan implements ParcelableSpan { + private final String mFamily; /** - * @param family The font family for this typeface. Examples include - * "monospace", "serif", and "sans-serif". + * Constructs a {@link TypefaceSpan} based on a font family. + * + * @param family The font family for this typeface. Examples include + * "monospace", "serif", and "sans-serif". */ public TypefaceSpan(String family) { mFamily = family; } - public TypefaceSpan(Parcel src) { + public TypefaceSpan(@NonNull Parcel src) { mFamily = src.readString(); } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.TYPEFACE_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeString(mFamily); } @@ -71,16 +89,16 @@ public class TypefaceSpan extends MetricAffectingSpan implements ParcelableSpan } @Override - public void updateDrawState(TextPaint ds) { - apply(ds, mFamily); + public void updateDrawState(@NonNull TextPaint textPaint) { + apply(textPaint, mFamily); } @Override - public void updateMeasureState(TextPaint paint) { - apply(paint, mFamily); + public void updateMeasureState(@NonNull TextPaint textPaint) { + apply(textPaint, mFamily); } - private static void apply(Paint paint, String family) { + private static void apply(@NonNull Paint paint, String family) { int oldStyle; Typeface old = paint.getTypeface(); diff --git a/core/java/android/text/style/URLSpan.java b/core/java/android/text/style/URLSpan.java index 58239efe5c5..eab1ef4f6af 100644 --- a/core/java/android/text/style/URLSpan.java +++ b/core/java/android/text/style/URLSpan.java @@ -16,6 +16,7 @@ package android.text.style; +import android.annotation.NonNull; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; @@ -27,40 +28,71 @@ import android.text.TextUtils; import android.util.Log; import android.view.View; +/** + * Implementation of the {@link ClickableSpan} that allows setting a url string. When + * selecting and clicking on the text to which the span is attached, the URLSpan + * will try to open the url, by launching an an Activity with an {@link Intent#ACTION_VIEW} intent. + *

    + * For example, a URLSpan can be used like this: + *

    + * SpannableString string = new SpannableString("Text with a url span");
    + * string.setSpan(new URLSpan("http://www.developer.android.com"), 12, 15, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    + * 
    + * + *
    Text with URLSpan.
    + */ public class URLSpan extends ClickableSpan implements ParcelableSpan { private final String mURL; + /** + * Constructs a {@link URLSpan} from a url string. + * + * @param url the url string + */ public URLSpan(String url) { mURL = url; } - public URLSpan(Parcel src) { + /** + * Constructs a {@link URLSpan} from a parcel. + */ + public URLSpan(@NonNull Parcel src) { mURL = src.readString(); } - + + @Override public int getSpanTypeId() { return getSpanTypeIdInternal(); } /** @hide */ + @Override public int getSpanTypeIdInternal() { return TextUtils.URL_SPAN; } - + + @Override public int describeContents() { return 0; } - public void writeToParcel(Parcel dest, int flags) { + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { writeToParcelInternal(dest, flags); } /** @hide */ - public void writeToParcelInternal(Parcel dest, int flags) { + @Override + public void writeToParcelInternal(@NonNull Parcel dest, int flags) { dest.writeString(mURL); } + /** + * Get the url string for this span. + * + * @return the url string. + */ public String getURL() { return mURL; } diff --git a/docs/html/reference/images/text/style/drawablemarginspan.png b/docs/html/reference/images/text/style/drawablemarginspan.png new file mode 100644 index 0000000000000000000000000000000000000000..edf926d80130bb830defde1af3bae52411156c07 GIT binary patch literal 18930 zcmeAS@N?(olHy`uVBq!ia0y~yV3KBFV5s9@V_;zDZM14Ba#1H&%{28M2f^E`h#5i(Izty1I5>b=o%D-Fr7~-j;rC^J|Xt>kdCGSYv+Z zU-I?e$KBqnx&F9g?l-Gj^XAztdzx43eOV>?QHPM{M<&h&g^u6*8do{RM$Y{C$Niyg znCPkorhwMwzjj;!Dq8*1^*0;s|7m^l!2^X>2L%BR7AD7e6TVe3Fg1dBaXBL2rq7x+ zYp+~4h|AI>-tqBcA;Y0fiXfGZ3g_ho#?uGSreBBofO1vJwH1T7$YAblCRqYD)hnT4P|?;})){7Z&pP z>?$i0@rs0LDiVi|DuEKp!DVlxH#%}KY8bqTcRc?|X`Nr+qhnuK4((#gd|PbU+~Fa@ zHT!hW*V9Wkw-|iqWpF8xwLQ)KA~Np4+@F$rqQW|-G4Y?97IrAnh|{|r9F5W&o+mZi zUJ+MHtg$d`lIOQ=4Gr8N)vdTFTaxuyT+hV?BHCFZ*OwprF#Ye{ioKh}_FuozcWzPA zjB7fOAmx~JGkBw-VAFTllfJ^yUkhrieSVpgqcjty*?>I}+YO{QhBIUX$vOEQ0)J;L!XxZeC%?ddjDtf?=ght%r_*-(J^C~yNP%0M+xLd}(PO`rS`=2j{_%R@@&ye4 zBA=&(7Qd03|~|9Nvz zSAqeD?_~ytNh${)7C1~=ve)iSt>e9;Zs$KK-IJDexpE;eXKU^*8^#A_vlAz!h(zYx z%wS+}XuQcZ_tKi{3r+U*`EYb?k`RwRp1bYPvJ)DcA~sLrWn22ix^*i- z;#i#gw?>D{CNX=fMDO0cTf?{i)713~_U?^6^H6TWyP7AjW~sF(2z5S4xBo49?D%m< zj>gL`k9^JGNW2C#~j{m3zNzop4u0j{Drz41uxjk(10s z`QC_J+sTy>aH@J$PMc$+)pYBVmo~lpp%L)31d~rc>j$N&ldVbcF zpcW^^HEY&5Oj4O~HtnCA#tAvY_TSw#^Y{-NY}mDnYySD;|JT0$6(|2`pQ3(JbHjsQ zS2X^WUVpB!*?NOi@bx6qP0{gJOIyB5%=qP*EW61SMRwwdy^sRG_>pp(s*rtQm zI!}JItffF8#_TLgUEjVfnTv}nATY4;-@iJEw#G$^ zltMy6)>ymitb2Tz-~Pyl4+WF#cdl9U=D^(GMu&nACuZJIP>ASs`}n^4-Q$0c?r?`Y zD~AgRv}}?1cV4|e;BCRf-cz^c>%~nWpMt{r4=eGQT?@Jb6 zjGZBT(2=9@@WUGw_a{x6veiMe^=3}n>8H28JS-MyWt%-+{Azw&TwLF$PepZeW^*1p z@>76=rRlDcdgUY`#T~}>&HvLMaE3V7FXS$J%GD|GWu49gd)v2e0o_k?&PBu=axTJ*Xqs7pdoW%U8q!v+VGpbOX`5%xe_wO#+lJ(B7o`rj#SyWw zt;;euMCfd|{dPly&Yhy^{@ya(&7AFrYk10b%a-kyHJi=5Nhf{p+AFVT#P+uc97!^i zOIGz1vXSG@F^j(S@l8pbQE#%b(E815>-f!X^H<+EmSpInBXK*@c9U>{NEpc{wb+w zvW=8>zyy~VpreBjfN%V<;fKl5_S9KXDw?KVCp`~wElYc zvu9}~Wo3t6e_h5KH)rnLqsQgz&m?R5s0r`;(5k;++cvYu)4PMZet&-x3~8(X(2@x|9LhC@9r*VWRUA;&)@r1>`htqvE-0Y(d+AC55IY%!=YHPGiFE8 zQ!f5zy>5$l)cv*keIGaD2iANz$i5*$ zXTg*-)8{(EnMFlH{c^U4E?f|(v6DCPZC3Hza5G0kTl?@tW%mpB?w#8{*E=me{o?K0 z-3u2g>geh!3UxLFo%;U%Yt_YDx4OQ4`?g0USgA!ps&~=;{qp_t_Q$?_F>zWbps_SS zgf(xwG%qi&McEq(FK_S7n_SMEKmU09{=d57b-KE`6;G#z8(3L&owxrl^Z4;&j`qV0 z4VRbuKmPRe^y?#M{GPW8G&v|V-2d~;oP(`-!IVXN_Q>S#`N(!At@zkR_GZUFZ}0!p zt+A8mP_(H1Wx^+KH>dEPs^_5t2N?d>F&+5$_;}%;A0M|QceyA@$jBU-tnP1MYI@XF zL}0)Fj=Omw;o*xHFJ}Jw^{c^5pJ=P+J|{MOwlprLXpW!ys@1Ctzq|-E zF*9=toH8dac;}9GIgv6IGZm)`d$%;q=lkkx%%fgXJKy~M!r(7*X-#b+*^Wmo-;XV$XDpsq)cl1xlZQ)ApiLv3n)OpsrEHtq1k!|fL@ zU7A!je@)-9!|nVV_w3>E(!6!+)(_{lyGz>I+YOD3m=xyC{q*bg`p@Uv+S&}PtWL4I zJ+jG|)z;X^$WXX2ZDT~ipC2E2pDT!UKfJa!dPm`7HZgJWpG7&d8yy%nZ{7@QD|8C0 ze|R=K|IpK-w?CepkmLCN?(X5Uv&{|d?dOMG+4$GK_Ls=__xFzV)rKzbeY`efHX*HktGK z`gs3UGm|CQ{?ygqdv-L(EIBce(b?IVp<(7s$vIY~TuYZOm0W-E*WSH*D}FwmUik1(>m1fo z&p*jk3EbFrLxDkQSDQSq?a`17Q*|fh-LvNWX1*q^s=j!s$bLEbW6`0d7Qs;)->;Q^ zv+Vc3-|rXi-aY&NgNP5B#+h!517Gao^U_pPQ~PkQ`n}_{)Gb@Le$>}qDq=R9S6naV zz?CZ^;{3I>wJ+YjJ$iDo`t`T*p`kUuUM`P_h-lC`y<78On6 zo|<9OHF2V#iMe_DoX0CyYPL8j+FfgFW7}7&CZpo%rLETRG)Ou<>ug$G z^5zH~=~@3YmwM*ke6!YjTI#%qhwm*}d{HCs{yyHxCtLLEK6XbJX)aL~>wb8wSNg|~ z$NiP1G27}_U*+)tPS_u{{s|CkyX>VBS%U$lGo zYN-}A|~d@*6iyEMl(09syw`oJ93(#scGxNg^DapjZ4}Mtl#I% zjXB5tTh7eHBq1fGrF+53l`DH*UTjNDsHl**m@(!2(IZD#{?ygGX{TAp96NG^Wq%kq zH+Nuc>|VbNA3wiG&+Y$P9y@ky(z6q3n-Aug9X8;3XT-PM9w>z>k zCiZ&a=V!h;Iywcf=iJPBHZy8t1W#L{!z2}!X2+U8ACDXGFz4_4`E1(VbuZ6Vu6wtT zz4dK3uTw#Oo!Iqdk8Y~JNj%DoR^ENp1o(M%6s+0 zKdx;GuCuw*e{PY`jZXn@w8K@xv}*tSF#Nyw_~U~gA0NMX;R3_-)2$gMT|0MLmXwwz zR#Zq#Qc0|=lnh@V=j!R{IZ1PRP*+zM*Pd^;vVT0)ukVb|VG9ifIs3}>>*~7R90xBi z_y4(1bE$@o&XHDb@dqVVi4ts5Qc@q*=GS)LpX2W4R`B|o?u%V~hYc>=yEjiI=}zAF zX?b!}g5n-0WoCBHK2&QL?^gfo#tn%-b@i8`Uw!ysaOhz{RAZ6^+p=ZLe$3u@HqALV zH#h8>xSE_^?5>sojU$h_^X}|uoH3Xq`@*5|qEM0r?V8_uU2?+^?r6O_b z#plkQyJGFy*0X8Nt5(gJInyybT)g~cfY-+l51mWO$`&nLn0WhNo)d?hZB>i3dEN*1 z?xRTuK~>bniwC(UvobOE^z}V@cX#)UdGp#-JU{&Y`{Tvp{(#uny~&@As|^gd%L<6E z_4W0A@#2L;V4$Fi=b=}xwA|d>E?mCMy#N0GbH65@{ivs-^PxB>B&6ljry>g(zKmII zt5#|4+Z%JfTg4OPw4+ImCQ`0CVuznRQR$Plo>n$zVF1VB!-qW|U4Q;Lv8G1m=+UE@ z&u_o~o}8H27_S@}8tSPlFk{8<2hIGG))YN``qZNImB_1CuO5``-Wk1N`*!iz*w`Ar zs;a7h;Na%Z&(G&uU7xk=>NILuKw6A9C?VLs@4x6y(Ffp#?pbDL^m~|`NcOUOw zk@w(ubM23sysGBEM~*);njM&OHfGVBpas>Hg8pG*g{vz5i$yJ(vS{^cZhm>YC&_D8 ztT+(9?~|yBnb{|E87B@YY3YmCuC;x?SIxh7@7@V2oWjDwFJ8SmbhKN1@-}adgBz2N z88y5H~ZWDY`K3#)$`D4{rx?^epT7Yt-s#==ur~GdFJ(b z_xGLs{`70rFejWtTy3zk|7U2zp_}jYNTRXdiur< z8yaTKnpIkS@E*g>m@1ojyhk0hHt8(hxpU_EXbq7=d#k_y_$K1&n4O({$!znR!PXo9h<1^el4=(M!A&) z&!@*LR=GpJ0#AYJzKE*E-159Ecp5O zcz@xmD=U4uMK>9imXtgIwIa2(y{)G8_3_DA7P0LA_jUcw+v+lWkFTwb&dAFu<4bvL-)gq!pvLjrmitLCOfzUdX5GpY+Mq1Zh^z@dA>)pJhoq3+As%3*g$lC`DC9qd{spw~(U2>7uaT2A)RyLT-1Q zHvMYgS$Mgi*irFc@_A>U_OQ#(1cMo#nz75M35$5%`cT2cWfK*3K+yl=zMi`Y%l6;i zp8wfAul;bNcKEslJ9kR9C4QLA_FJgZMlNPg#l+d+yoVk9{rUao+s#dRm12J5^Yin` zMMXlNKYzC2*s^vlZ*On!g;Lq__xFz8xFJz*oRydN?A>G^{{rt@#4&8qu~1t*;!d;JZiq|v)g^H36$;I zbW^Qbp!VzPc-PR-&`UehLc+rzUs)MEW9H0<^Zot(okOqYPIXP^Uw-puB*RDZ)4zUI z&9N*_yUX0wJFz#htZZ7kZBK9S!z(L;Z`1!~Z*T2&-i`tFWpNLbi1TfNCATa44s zJvcd8J?4p?u5MytBBS5(;=`58rYzdGPcHt~rRf1dL6g4ie(<%b@Z}{{AAkSkx;nW@ zDv3o!Le9?47G-a4Ogrjk_kQ8;xw)OZq1Qh?URW11U*_(GPYR6+B9|Y8FP7l@aVg5o zce8=S_$@=W9Oslx&2HC_$p1HMYqtI6;H5UJA z;^sX27Kj*}Hu|^WtHHe$s~$TsGuWhQ|H%^Bp0-?J`Az+$4$Z3>@{0Ee&Su>8u#T(Jld-*=t{xxJlFHcFjK zOIy3}-5tw4|Ns5IV8UxRpWi(Ho=a+~>a%ChKKy(>|8uRVh)BWrlX>+IHI{1V>be#d z8j1@|o;;bO{cwSWjAz=z&Q4Cb+kqh=Et4h**;IaFX-iDV$lypnH^;L|Lj71a!^aiB zckS9GyZrlGmdI&^&(28dnHU9zhO%}aJ#<`-gJnVHt!$+^rl>KWu@i0IhK

    sLt}RhwLaOB3w{In6Wn1sRKmO>ElA5}DU_iix zEq;r(@-cXMdF@~|D%@vnZM}2%i5#=V3l}CHUSraJxbf4cA|D^04;G@+WH(=|(dcTr zyxhNd;Y7Z}1_5DVZHL?WA3yH5XA?O8L`p{H$iu_!+huCzysv&ymv%bIS&3!w>bg5K z<~da8+NwX^q|os~N@-Wy1TpX3YaDrPq9ffLLLd5hY}7J{{-8d0xxgipKQ%ujd9qUa zRxc01SSJh?n+qeNR<+YajkDMky|uGM{e zJ4ESC+s79dmF2#eSX*~HxAU#FH*a}xq*GWzPR=bnT%6&-%gf7ml)smAb9b*Su`8aR zQ=)uIeN9Y7ZmuqWQglqrkw=e`CPmIo?b*O^s;!O9#KZ*D1>6|H6C4~I5f|sjbtw6x z)T&jVrd+>be&DWKpooZwNRE|y-gfD<%`^9ZTy~A|<|~=-kdOtNHW_JNQ#IvQ6Ky{{ zG3BTSPtRSgGk1>$EoEG=WlPCnkwmkyZ^!muO)lGgSDLH6<{bCtMV52@=3TR~v+J9z z?yqJWF@4&!9o;9=Ha~oFa&m{;;txM-Y<}$AyVo}Jw%z>x=+_Jj)~(Bn-7{&C2B?h^ zueOmhWm$6aR}G1q+sdY=R$lHp?QkdP>YY11prZA`iP z86_CLY;_3SozxUMQBkz zY(`V_Mv1x$-zCo1oX%V;txvs;-aX+n=jjC=Ay3M7%ciEL z&bA9s@pN)@Wb{!J_Mhn8!{BfAPo(_Rp+ihb8zt7o?mos{80dBJ%9SnWYB$|Jv8m_Q zt*9A3YI0xgO-+yfo@^j~>cj~S^PgrWCJQ!h6a-be^XIqUe}DYO4T)2yPye)f!#m-u zdu8R%W&FSHD$K5&mTLKOPTK2bH#6q1+{mQEr!Lg_VZr?)?SiX?H%=|gKXU1kka%5N z;(}efs!r>zeY@e}>`J@mOINJmSh{p+L}aArrW=ObN{TFt7lQ`NSOvBRT)BQdIW3Lt z{5;#o2hT4I=&ru*x{_M!% z#$;y`Gw1O1H@r7u=D(M_m@(zP&JFQDudc41y>IVE2G?homrnWg$mS&%+eVGA#m~<@ z4B!7t)WrAjt5;g_*WZ>sesr|E`rZ1JeFfWZ_Z~g!`cJIl*O$x_o6fv$)X~#pV%Q$A z<8I!SYuA$U^7v*gOU%gNQ1_ejAb+m5wsu5J%o9VouV24bYECGfknp@NGm}$USy@6} z{`j9iHcbZ=OnkkoCSNU`yT9>S(z%F<>eJHRe66ZH^*(O$>&nEW{rmRGou6mR%%G#M zpIlcrul{~W=+O%o1oUmLUbw)}ez@`1uPV92CieFIH*ZFon3^^Qam8M*EE82^0ksEL zt-AH$&xZETSFVVF##?M{&+4kL44WCCG39!@-fFc|Ns%F;t@q#GFM9AX@2>NAtxe*l@)z` zkoZ+bVr4H+{E^DUgXhCmi%#@tiGCv`EnV60_{kF$hJr*V4#&z$%TuRLfqD-G7CjRu z3hwh;wRPRDT~*HFESvl8>?-AUc6R1yb$ZRDE}U5E^@-8jIZgHO%^wlF-iF;cTm7Og z`Si5j>d%`a!WJu7>g`iq%h_lDekcF(lRf#STU8_dK)Ip(__+g0+8iqaYj&>VT<&$M zlI?Wagw7VH&8+LEgg$z@$*X4lR|C7*a}TXQ-u7T)#7F=BIZdahtdUy0t54qU^}+vl z?{B_VqW4|gg7L!UJq=GYM2?0@Zwn|?S#Kcxtjtq))7F{iZ>McuxM$BC@xY{Y9j8v6 zs{NdnxiR9xg$oVy=E+I4H9mcsdgkoegKx{!uPP>(O3r9)y2>396?N#;DK0fNwFh6T zuJ2{6+Iz3Zb!we>!oh;~`~Ta`v#UMj`(l&M;k@mKpB8<*C)yLC)AsS>$5PwEZOlte zGOc%t82v7di}3LX3=~|pY}ta>R>zJXuiQW3pP*fr89%#B-;`nd_He~d~5dgiUsNd|JPa0^|P3JZb?#Uj^2%LpH1&~e$>0|IX(S( z!uEFyfA_9A%2xgOU2J5FU#4G-#!8PC1&Qv5dncwB%syMOcopBYB@%qn|9%VWH#eyK zwG$U$kn0wWUzL00_0+8l8}uYh1cE=Fp6D1eSE6y%bN7r58_%>Kmf-#sn6x=@e~f>p zs8(d*^5o-vxjW?<1-^6VPmw*a$wJPi;y||a28|#7_J2jLUcFlV&MootftnwySFi4H zQJOJlj*GW9x49~F!{wJxHp;9Ki1hX46%!Y)%xyY%&TpUH?Xuk5x9g6uzi}7pmbb0^ z)Dq7O>aQ0*Vwp8-RzO6=3=#E1ayIrq4)P0duoM}!9bq>s=G=4Q^C2UfRjXH5TD|=8 z#bo`ZcHc;t)*RDu?(UxUcjlY6=Lj^3nl?H9 z_lo205s)UAC$}%a$FnWio*oz$c5L(d8KM2L^Uo+9x^yr+JlxRAs%zCMEgwI>qQ;=8 zsIEne7HvuHaZ%b)``he&(P|AYy|_Ic*W;>l5ARqyciNHrJYw%B?bu&3wcb1#lmsWP zSt`kBuuaTk=KI^bTPh5m|Npv0x>mcp+RCxVy7=;sj|uWhyS8cHjn)9o>nm^Z{(YM5 zn%(S&-+xz5lnmRee|vuKK9wm!HWeQj_U_&LXYHbO>-x-Fl0+Tb+uOg#)Xpd!dSnuD%ltJrO9Q)2 zdRE%D9Xn1`FW=zs@kmnY?!tYS1v_0Pb#0DZ8@A)_JKg+ruy=KDHoArV<#=5cR_jvM z#MtH3EqXuKu7&OTz2$!x56ry8_2+TlV~tHWHb^gzP-0PDUH9Wk#=Ym((i*+xB`FuL zuel+ZeQS#DY~9mLD)v902+!jS3kYb?4qx}+&(F_)-ky2>`QhSzyGP5e?QQuwB_Z%( zqXQ^d_V9ZylJJ~!e$gT&xry?fE(Qh$4Ie)i*4>#pb?WO?2LIL_Q2H9F#=raDsZ*!Q zN@b=5O_?&~L%Xs!D=%AfWNi7)eP-pwaa`Qom0o7tWp%&4X!;wc&J69mu3o%!gTk$o zCp~%i`9I4!g{>AfH8stQ6%qf=jHvFe5pfswtRDt$c(i=E-Nqp&~M#$F!Pptrmfy&$^RSv zYW!RBS;Z!eyC>(l!p9$((@w9i-59n=uxCBnx8BslNgEez+&FRn)0eyY)~#E&=9J;) z*(#^+p5Nl5B6JWm=eOBf#q-e0;N=1F@%`t{`EA+0{j>h72M-kT?(f^1+B*NWw0JeM zwvG;qKt1=K>hF5%odcqxW|^J1oV0Pl%9WkxpMxf)3=AfSxNMI%lK<9pVp|A!wbySckxyndZsAue;v z#*Kxsl2zYg6Z8%{C^UeY=(hyq`q`&WohrG%d8Ovs*{mU(s*fEybZFZ1z{@6Q&YsP! zvhO&W^y2mF$4cD-O}B2{%H2Ba{C8WE!kd59yf$siX))#9{7@jLi)nUFQ*7X-UmH(x zt?6Rgz3Hb_Sfty|yPfy5?OMW5uTGfu>2>>UEj7}GUVpwdYZ>w3kHpv6*LTbR`_TU4^=t2H zX3yt#E7z@ic69feFzs`5t&dwzO?&O<`8Q{|;KP^4-j-PHoZ1w0>UG`*E0&i{Hj$p$ zX{*u%zaIEJ<$BDdsGRQ9J#XBFjI|q{U%znSf&jmSUkm5uj~@&FeA~YNY5wt@5}<*N z>T2ux_MxGL&(F!OS+gdk#@U>QnP1+{=T%JAg=eaUtYRJ4Hzh=B=uOwo6%G9sn~{<6 zKs3eO-F>ITtu+c##gVE}6Q@m^cIn(g70-ZB(fKu>IN8|Pw%MHDweIn`xz>9YXYHy? zWZdYJVi+Fi?R|RN`E0xQ3*YRMHgDujKfQX>t$%Ta>-KQ{|Frb_hsg)+euS1f>$AJ=+1KuDcyQ5) zNbSVkNlpRXiJ{$ZyKbvVYb@2!(>vy^zqf_o{?CNpeEIkGoIKrbJ@?!a?m0hq2@et7!owQG--PLDgZ zHT$}Q=F@jmHf`F}V;huXCY=)bjQ{%0n~^{M*4>M<7C&@2$87Psb$!!?(>@=4C9q`m z6pkao?Alsd4?h2#^yh-As;Y*j=E+XcHroX^oSX0N01YGr2OrLV_xmh+j@j(Wd_jde z-Mf3M#Z^^RU%YtHa5d}cM#h5)0_W%19(I?nZF%|f#tRny*%Hl(@uJC-=}jr zBO~L$^ZE7jRNh417oB`^%E#2jYu50{^|$wzCMGh1rq7?Ao*ozyazvNu%+xP8)8{|@ z_xJanL)y3f%p{$4!d~tDe(&(p)6*~BxicrlGiB*f`#&EJFIm*2tE;mAGI3 zvDFkNvB+cgwpCKkPp$rT^hi!cOv{6-o4k6~{`&Y{?r!XB!O1^wNZnyAm~EDSWZDkD zg-4ktt1P~g)xPSrr=(>yry|R8|M|zhd@%tvdGtTLzP>&=A%S7-+O;1lYyw1Dr$2l9 z`t{>yUQQgRPMw;x=;Gzn`2k)#Z>wa!Q{@C{+?;-X$NI^s%VYLb6yDu>c;6kq_Gb1g zzCJ$Sm1nbN&8kR0Ia%F5Z?$peqq>OnuxGbf=UrFyC=X6cTC#E_=ik47JwFGAhflve zCH&d-^v9P>c;#!q2#Se`1%!mKgoa+cb7#+U+lZjd{QTp4tG`!dm&`xk4w?aojg4Kg zYE{$86wt(rnwpw{sp-^fz5=~&#s%f ze=b}Q=-+$t`raJ#XQ~_jpa1crA}G-|%vf!5#)_3I8;?Ks)H?gN#L9fnzKDr?7Q6R< z68p~Z;m611Gw04NTDXw0wYBv^scdm^v4phr;ZL88_OCbCbXFp1UE9l-nSY+B+dC#F zD{p_YVV~>ydA8oKzPU}ADax5V4qd(W2gWrkKVd|S3}e|&MVdqiyP(Z%ll7w+CYyT82iYc0>=hRZLH zEcKpV_DA;QaoZ`m<#ly(t5y|dFFpVK@TX5j=hiH-R(yT^se@1Jv8skOS7+Y%qVB7_ z?TWmYW}o=tnR|4L)6>=E|6jT!v@U-CJhKa4fg*FP%lUM5b^mbB*|9d} z?8>WIPt6l!uP@Z#TJOB!VwFzAl)D9Y^1in@mUO;WTXrV*%9k?+JXfw<0kz-?tmb+a zPq=kM>*M#%m1Q?4z5cQXG%lMR|658t;ms_swfjNSp62%|nB#xny8fd^s;x1>;KXIk za!`J4W zr~ZCxP*~Ws5MwpjH7ffTY5dt??p5h~Xa4D@hd_(-!sBbFa(@kZ&1x;5o16RN+V*{| z)8p%AnlCcuVSfDh@t)5GN}boY98Eg-dj0-B&?s~MxtV^xD@_w?&^|e8- zOCHN_l|A^s;5S(;af1r{(uF<<*PnM=uG>fy6t9;MctntZKnUv zrWu1GbkQQEe+nsS$4ioRwY3j#-~Ttaq_p(m&CTg|t|o4cx_Il>tjS(AXST1`;Nm&_ zz_s6O_s&gv+t(a-2@Jfr>}p`-%WuZ%=N4?;D(YZ!td*5-ne!x-Ikweesi~*pJXwX)y-()hJI2Uq=C#&Il047P&wtK3A>#DUxo6g@cv{r_FwoN0{`lo( zFb^+pVrAvb-!CIKJX+}7UU3^VuJ(~3dRp3^;D?9%-fz7g=Nud?Tvc6N`91isL4w)r zhhM8MUb%9__o3NrUQoCc8n5K%Oq*5zxYxX5O^~PO$zAVH#+~J^*4X#++3Y*12RAJ- z+buS0)~tf3r$qlPyL<8C#IH8uA|emoZolv5?#}+}_isZ>%ahU*BXz{4OqsG^=~C9U zYu6T7&HeEEFKE@;|9{I*KboY%>E18rnwhyLVA185Ew|quTfKfCXay%|k|a4f`NQwO zf2som0vdk(s*lqyZaEVU9(3eN zIDDeK)dRFH^2h3fb}d_OMCDI8JCWny#YI9tu4uIVd40K5V`;_qg{=QyE!AJL_SVcU zKVF+z635dIHy6y%t=PL(cB|a`Wv^3D-}oeCS9Ps@!Kz17f4Rx5UDGzlb&ZQl_rv;K zg`)PeCEDhwiLSXA+rhM%wSU^P#FWBInva%Ve!GcjvdTkt@%wHob<6+#z7n+CI+yv} zy=5Vxt*ci3d3IEbVUzwpw{x_y{7o|HUnx~6`=C{V2dmF~Z zuj+OO@5W2hd3ich zg8qFw`7P6I_Rbwi#k+5Qo$$`;-l;inkL}6%Zsl#W?(H1rwA_R+e=p7aw&Pwk>QNsGS^@el%d``qxe#u$9 zb^9jUG&{fP<@0;36RaPf{PuAA4x5H0Ty>rM&tHwnc^uWPD&BI`N}Y-yso^8-`AG!*}7ZV3nzM9TVL$fcRYt-M?_@vxkZ!KOyggv_3zcY z+#Y>>MxGNao+jEj>-Gy9R6$6_t$D(b6L#q#D7JGZGS7? zZ5EamnXt6tK$70PnnLqN*7$YVr>vF+8<(tcQ`&P`%Q42a?aG}HE}r8d5;`22XP1a} zxGhLs8REOCXT7W)8~?;&EsIB==N(^`58i+BWab)OY0&njh8UY~JYI#HcE}v>+WbD_ z)uxETm3x`PCUee8bTH<*QrZ2{%Q@Ij?a|e?v@7DG&WqVhvJPY!Jq*gWjuFegcKe0S z`U5LpMP)qX*diJqQ}E`$R1u~?ex!LtX`3ufs4IMx|#5g0suvZ3tSHOKz{I%oc2vpc`QwWNxz3kC06 zRb_eK-Z1;jjtduhUOS&pa8bHY(-8B(?;PVjCXt5Nz(=3;e564ZS}aJBJ^3QQXM*Y> z>DG%n_qJtvDR2AnUSm&~bYK3#RR;^A*8KRVz5d~M|NT6j9vf05BWCdmDeky%rz`xG zbYcM4YR`vyv)2|ZTgIpAC%%liPCjV zY14Wuw3ay=Dz3@sc(rWJ>8a7Ftg99sUU%gBMa`pu`JAuj++Mxfk)u(!|LFcQt%Ox~ zSYPPq9xu}1h@9kL%6utHKy9{c+2l#Fue4d$bb%W3Cz4t-=bJ7O=3rrJOwdmGo3eI; z^@SIDE=vz@UF?>_wzOjPF1d5EUl!gtZ*f*^PvHHQrw$2cg01?g)=q?^*(rc%BbemH|dRQ>j@sa$&C;eiCu zG!3J|iq4%6cv=kvIGu_t9nAz~O4bU_4h<_$^%y3qwuH(mIE0udOe$@0QgpbkU@d!q z?XQu8rp_jw_9UmBcNqoOgZBS5HT#5_)lL%)e4TQaar^ZSUWS}aoT7c=(K%DB?{1iV zh9Ppc=BGU}x}l*j4hkelNSh}yKQNdX@V@_%|cBpXT`6nNprJAonOhK%LgPEZUo9gLf>wP*put2ucV zBA5H*>dIUjKQ)Q3oWvPfn-dr-1h^?Y8=sX{xWhcIQF#7hr=zL*=Ai6e+MVazO@yv?@29} z7K=PqaDe^Pw&|N{xekc;I!s$?x|*d@?tqi$p?%j_+%1?+raf(H4&vH=vng%6dxR<2 zRS#|keQ0sw=&SZl2p4fFskIi}A-GgTaku2E^9|2Ful+rTVS@zQqFw)DES5B?bTS@b za#()g$*Ou?iQ>1+a!Yr`6-00}`fw~`vfHOCc44NLNy83@jRH*FYZ9~`^oIvN{asnd z(lVjKezJ<-p=pa_G71D2XBsF8o(R)cILhkPe7e^Nnu5d|bUS&u9(V+DYQ6R2`4v9T z-eh}6j?uxWNVcg08QLeCnicXG6YXDqa|k-hz|iTjpyk;0$=7s5BwN@bvjgSh!@LC8 zQvPle?whzSQbondLFMJn1)ev*Sur}z`#(=U@YOa`SvM1|q>T>KROWn`b}-Q>HMB4U zydCfFnQ1}ZJYIoWn;({V{_H#Z+a)kzoknZWj;))XW*&8L>eDT6%~3F!R8%}azr=YV zqtk(Zw@Yq!UK6yNcjW2ioY_ZM+vR!}oya*j^U^2Ta~r@J?02*7^Nq7qCB?N@Jv2JH zIq1>bLq>m=m?U}sRqSF4cGZ01RDLV5d@RsjrYu8lq%IbD--8a-mtbER1t8Y{e3yG~2=&AD;xj!I6w=I_q+ ziw`W*F!=Dtc#@G@!vU4k6`PmI@f}s!6aY#|ADlL%ZggZ6nWg)qTDLtR_I1)MP2UhD z7T$GCEXjsbZoQRenIa^yR5U_zzDq@>WU85UQd=X_X4RXnj5gvznt79p+L_rHrr%_8 zTGH}$!#&?k-y(P3W$YBNIGgrP&9kIXy+Lmx6~ndA^Y>yuM#-sf{PaMmbX(ssj#$j?TrYnzVJT!T*loA9F1ub z94oZknEv@*jwqeYDVXHIy-sP>>lu7&)ocz-w7ka1B4xTjIHAV$04s;*1_Omc>ukO5 z6^RaNEQcBIf3_0OTqD(aQ^PT~IW*AVMKq&o=C_Fg#}6_EGS6aLezT|Ueo@nDwToFo z@$r*5^vq2bpU4t!TnMV#DiUu@y8$Y4(vFLtT3t|HC&qS3{Qe||2jQVVu4+tM^zhB5 z@Gjd9*2NsUdWr(U*LS-JUGfyx<#Y6H=Hs%DX6H0D3u0kpWMp*>2v!v2{5NA((nXI5 zyF_V`16ew3y;2{JE?N+=KX)6WTAxP4p{;M%uLzXz@Tm1&qrtV{aCA>`sA+YPBB(xb zn4rJy?h6+smi+4`i}r5lxc)5a`{WsaXFlyuF*+C<%UV6L-CSSU=4#1WMj3B=RhAZy z^K0cfC6$FHIxIQzRoKqIAXepVa_()F!rMWg%5LsDne2V1?9q*D|2=9fHD7*Di#%pN zn}6Z4mttYOjSdO|djfKzb0nrLy7cf!xm0xG#HV*ccP$a=y2%rr%KC(<{MMNXrL4Ov z+A@=PSfbfH8E14ZJv`ssLwV-*xgmjHe(rj?ZLi)7+FYKjoVkfE?l-mX?fm&&d%BQ9RISwH%F zvq~DS-hA-?j!TA3d}Zw!&!+5SncE&zzjY5tEAJ=?2bs60;EhL4K!8Z(njh`Cb2T>J zEYABk-A8T4X)lG{v5}FoC)mQ;zZ^Z#@ch~Y-fg|l-}OED%OK6&`+T+!pPcWxu%Ah> z!g1@@i93OJ`|7J$Z_ZJ8=U2IMiOl`XoX4>t#cWGIdM!>>4Y;G1BF4ovR3TP$FxoKff zRCi3&;PP2|=Fr3+jK7tirW-Gu_#<-GFX_(PYAfH(5-~r~=A<0DKt;k_gCi!5``?Qb zoLegPC~1I;J$)TZLFp+$BGI`&*7M1__gM!^!irKUGsP1 zum3IQud&a1^mE&U)(ajRvL-y?bx>ltpOJTb>aAs8Q*W{|H99C%G@h7w|L_74^)By^ z$7R(Ym_>g|f2i@zsmyBPt9u?>9ddL`gkq%6H)~z9W4^@Q+rlH3v*+!TAN?Zcr=lD; zeh%IGWAQoR4O(3V^FgWeL95zwM=_QI)3c;LwCl=#EV;4OEtg?a)dDqBJ?{wfOnIMz z9WtJJouy(OF4``WUaQ{x#dv7eO2uYT<`|z3$Fkqtm)!pM&-6VvZ{2d^WoX~4`Xk?e zPmj@V=NC)O-s z;?plnFQiINyA*UpZ2AILX9kAJD$a{OmUd3bxx2u|j(N|$*2ap<#*s=NOM|MXuF;*H z@pr)jgVRPn*~guOb>z?M%ALQyxMi*KL6ws$3N1dSFF*GvAaJ9A6ez@g%vD&P#N2T_ z-{NfApUvljSCm+6T;e9!v+_g4nj1dZM`lkEG}*x|aJ6)Y?=;@57(t1nsT}9@9b$PG zU3$r~wBc-uSJ&eNr$$c3L#_+~PYWEUajfa$Jh^VVNqxL@ZR=%8asRHSBQtuQ)T(rd zGCbu|&*^Fk+AVxP_w51gr=d<6;9xFqo72s!pl8vwCah-pM&9@R{13yE4@>a=*z~H` ziNS+~BipL&kM?Y_D<(cdAbm!+cNEtk|2+k z00&Fc{me4g9EQS!iBnT+c5mc8`_Ac)yuaMTYj;GF+9n!bbaZqSk@RYmw&LIs3!k_y zK|G`Ayzn*$w}p*M9>u-Xkz(sjpZ90)H{~CB+3YrAt6?cP{rVBM13}S^msOvxC>MR8 ze)unFAm&U<)5nslbKbNxoiKXBB>sTCXNA=N+Fhp>9N48bU)ns>+qC4!bwiET$gqNK zi=^tWR5C61(pK=E1Tj@0Vfs>R`b?hDPvjQb^60}|J@&N-Vr%8Ynrh(`^2Xe zkGCDjx^rlwU&e*fuSEM`gIiA;{>fOuzxQ{|Iw)82B4!t|@J=%JsMDK%VXH@P<%Q~F@#Plc2=ABY-jC;~)GcoZ= z91~Se__3ETn22@jPd~P6<2vIvv(yYVT0N%)J}!BD+IMZm=52zd@0=`r)B?O(O~I8= z6YmZ2q~?Qiw~mTQ?+d%%b6q?8!d8p^`+mI-&;8*^b8>Vk(Ovo=+~%LtG?fDj0#rCN zRXdd&3wJJPcQObO6=HBznxM+rrJ`C=ma5e3q$b3*#%znY=;N=ms`iA1c1wjb`b<)B zX#=->BR8!-!q(i>6m%fh=&_Tl>A7j4NqRC)_fHAUTch)6W<6{1LxrYUZ7bF>FVHZF zPGDG3w!`d9&O$%&q=Nz`rl3U)lUxGw%A157)P&xs8FQ@(GZ7J4bT_(5Sv7Sn zZ44dUN{U9Hf?VOmyjo2&36Ted(xH6nt7QFR)~o@or1P=pnAjkHm{FdsMw=tcuq8<= z#M%9ffq-#I2AgluMur9#4j(niNqWjTFE)6oT@evkbo8-naH60TcXz;ZJ%@!?*=7sP zmrFbfj+}(wZ?ZQ|2gk+Yn-q+g8~aAfGi&#=;(EcaL_!wQ%&qtPT7|A zfBRInw8w9+i}5W`R5}7Vh3DsEhpyg`84(Ls?ian~2=1rYs=tqo2N!GtfAW-%Kul}e zqeL^-q3Xpw{r&I1Mu5XwU{3?bX_%#-{xi?W(V5hnc49pP1A}UbYeY#(Vo9o1a#3nx zNh*VpfuV)2fuXLENr<75m5I5Pp{2Hgft7*5qm3~!C>nC}Q!>*kach|T=E(yF1_lOC LS3j3^P6*7#JE}Fff!FFfhDIU|_JC!N4G1FlSew4FdxMTavfC z3&Vd9T(EcfWCjKX&U`nwVg?2VRt5&fBMd5=;~7AL1s;(g!2|{dh6IKW*-qyf7#P?~ zJbhi+A2CX?@>uu1e6T#fq{Yf2!nF!Z}(PCkO{AQ z{=IT$WP14h@3o6A?$D1@IFRg6bmHO%3C}r_y-TNtEu5MZzG?648=)3&_P^z5KOFa7 zes}iGX-^|tmE4ZY$OK)PAR{L+L8d{SqalNVcfot+Uc1$+Uavdz+v3D~{ z9#_b_ur{o!iJZCK==1M;@4r{?pTDE-?=F{x0U9D)M{8TR-DO_@VzwrIHhfcWVs4)Q zeWQj5SF6*)9lmaEZVaZ);UMm#eJ-4PoIsksD|$T;1BKGv$^`GbM;S;nm@ncbnpFwhvqybiW3?_n$`#8XnQ>n z?P6zP%;6l^=h$uzF>3id>Ef@!5Lbf8*)ijX&m23n^M3kfRDR__vI2%CnaTQ<}6t zZ=N%M?y~Z_w#I@90Y;v8e3HF9H=aJauPaM!vEZX+oKLo%W1heu79?=>vWW=m4yieh z-+b|9xHGHozQKx*KVNFDPROqm{jI$!Q-kZlH4`1v&EXo`SX4i79j)zPXc1VV(|xdD z0)Ku_VOG+Vdp{+cPO>bUvc2Z5Q^S-Q(~de>9gR_P)R}u~&wi<(N5(NaOM=d*3+XJG zGk#ycLcKRBXqD6bT8=2omT ztC$)rytv0z+#^!#)}s5d@k;lCI9fK`p8R-zeCO3$9c%uo>r8&VuyS4dv!BNwzLr_D z<{|rB+c!F^7aV(iJYvpOZIQ0CuAFqWjN^w ze_eY>u7sgwvZvin%SM5w0;7Y%rM`dCZu7nRG@SpAfw0m?~COWhpe?8;0>YB{F`&n-~>Vl(cOp7OLAB?;w zEBlDQ{N2-n&|I}!@tk{|3Zpd|!!_PHwg}9ac652L`Hy=2_y=ds79Kxdm#=t%<%sX( z@5kGBn%E!yZe3lmb?i3l_j{K<}=q(wg9H}0e74;oC-`Wj@R$0nD+g6`}>QL zzh6C1_d3~b?)H-Z{AYDr_KKZbh1Vx)Oy29X(GMI%985oc9FzF}@6huv=guCmwabp@ z_2oFG#QkUebNkc`#S`-1-z>lLI8?2=HAxoiAx0g|%JiI$`uc~(ZsLc^-EQQ&TVKDr zZ9}iAV#l)JKVPrLJrm;m_x4}=l=!%X5$vF}QQcr{uKwfxx%y)+CQW+RR(;YDbV~St zGH36v1_z7WhQ&j>Jq%%+W%s$Fw(mLUi?(=G;mm!Y1 zHoN?E<|ZjlJ@aI}KEw8P!oJIY9zDTuz3KCx>$~}b-hS4e7_JcpDuxQh7jYhRH$V0x z{#>os9g~-us~6at_#ZOio}ui#^SJlKhl}iGROb1KUs-nJ>7p~QQx)cDibs1$?@tVP z`Poolp86aw(JRNYB|<7{c-orJs0*=f{rJM_shqy^LxFhT`z~H5=fAI?(Qte1qSvBb zqTmFPn&DKCnb!u2tz$~uSAP7F-2cOB(X(9n?QDJdwP(#9O|$>&x9#GF?kghKj-9JY z|K6yoYA?UbZSDRJ`+Gb2r!Uj&-{Ul~ee0r95w4?Ri#QLa=p3s5@r%3adD^?%sRi4c z1)7#*ev1F4_sL(G+3OC>T2&5(X?~yo$Lu@cZhmaaj`d9fO#wy+>)-wDTA!A;YG07X zrv;$w6)n(IevGX$_k-58GWpZC4QechxfTDNzgO&YJek$yv9<_5Bt{slS>MLT?K|Ma zcrdGrXO_U6eU|6$$L;UQT$wo0X=1NXMQhS*K?jq$kJ@YFi*+rQpE>(lB{%X_VqiF1$B#L~g=N2tY>YMIe5a2MAeZ0pTk=F)nK@?0TrCX`pLHK+ zs;Vwa&DA-4TTEo?8X_9#eg(Y6z!VJvOF8fKfr9!l)y<%pSe?67I+l2S+oy70dBOCnY4aXqs}uI`?-jGxTed#B2P!!>`g(B2pZ9%i9wW1A zmE!sZUY;wDJ9{r^*|H@}Q%1RR&$fy8-`;cHdHi&a&9U{z+&yNbsqM{WWR3l^tGjQK zkynIRRa2MfdS3Bad(+LU^SVLB(p+}`E9QYuij3trKTn^2t~fIy^x{2tF75nkzs9Lh98{v;U^8Kq_wW1o?}x$3P_7dm1unlY-SN4X zWGoi&@T1;psprN#b(?G1!<&oum|bXBeY)y?@SCEC<}oqz<|~FJY?Pdz81Sa3IJGYA zYLc6a%*iSOf2e{`OwFAv!;_fezU*$1Lsx^5f?w_f;@ z#P{{=&E=}9_0?~MLgVEBuc_wG`+JRN=b^_>&-2@H9mr-5H=KUviK>uyQ|pn{udn>- z(un@@n>{Aa>rS3c)w95f2A5kV&9r)Vm|O9g5U0(ozSbj?iUK<}f9K8feg3`t-9!KH zGHMTsp2~c8(mEO+W3zk5p4?(9XNH9#lO{h_wu!slvCbv#W5XhrxP>>^&fSr@e`Sdd zQ^%4?e~z!;&!XY+Wa$}~bJMH$ywO3XfWXVkT2 zdhy~1(u2)sbh)n7=UOrAV;8#+sF@_DbYjL5`<<4JnopOV(`yyb(dpWw#B0O7m3HKmX3{ zjp0A{@$^N-fSYq zYLH()o$IJ=JE#ES7IZQYjC~N5@vo2NKt{LH|NpW39Gn9ignQSXcKLT-wxVUZRMd^& zIM3w3kIR0nY!#TY(^h8Xy2lQwy!T(fd3fm#PyaQufQKKSUYdwMPp??DFj94Uu+0QejlNBQF^2P;pqJYFh~j5?m(D*= zr?__M_2b-U-+<1EJ zqL-rlGFn}63vW0DO*^{vf{f?sJ&QMc@pde^^k@0{{ZE!N*Os^LQ1zX>f5#8e|4&a{ zSMhz_C*CUPRG=)Dsw5S-^^qvQ3aA>^bz+!U(si&P&oxOCnM~Uv_ zWo6n%XHusrtL>DU6|Powl)2OBqP9qMi$K%Nik_7fB8o?3d*5tpDC%#oFaIDIm-W_3 z{I>nH?=RW@XPsBP#k6$JEQy3J z8^nG)=;^(W=Jrf;P;G21n2=%RyYX~s_zo?RuC?I$r}d1w@Rc9kA0*h)6pbS^H9uEm z>gp-Z@|$R~R`m5w4kM?ClpR0rzO$9tclyX(H{quCtXDs_v&%iy*PolNIMpRuFtTX(QD%yQYYfWbAE!|Sfxqh+3le1hK;6<3=Uigs0ZzukUYQ-q&G^K_Dy*zJh5 z-%K|g-FxHdGym^$&u*`sTfW!xmvryiKQHGwi|oG38mVS>OMhX=q@5{`{B)xNG~PvB z;fY&#gNa45{zHPri4RU4of#~f8(8#PMP_f@e!1lVfAzPI9=?BV1w+&t+_PT&%*kr{ zzx%t)OVMA+dyl5QG26B5;tT!>n|#!g1!7wsw9BbY9BRTD93~oT zPWUh$P0KOMhc zTe0Y*%H)Rv2P4)z`1-C|d-cz_Z1H2V9sVBP-5FL(pTvGVu(0L>*HN*FD-WgRy>huU zX%h2P(?4PE<&KV})295neDw35tLyDklq}94d?xm9xA6TXyO-q!_bq?_Oy&Q>C;C6L za$4Fh8U6dP@o`P|^?RoMzQRrSU)?&o`}(>Mp(Z^Wmsp&AtFTlhQ+J(Rfa8}gb`Fq_ z#S*f;9lal4YmC1Bb8TPVtG|s++x8n(Hvj)Ao^N1a+^S~6mbT3C(Eoq8_cz~vyKilB z=dwwEW{aO&lr7aXZ`u>Sj55K6=m-HyS=qN&Ye@dJUiq`Xv4BMpS#!vzSoubf5d^pmXfkE?w38pVqOn`esy=CI|mH5ag9r z>-u`uUS^rh%*Xt4)^4E-B=(}N3$yH^~Y+P5caO0 zf7|Mlw0YC4#+#X|x|qIPx%M#jx#jk0p#=-}b#5%sSDL5bD-apD@nN!&3Ge=g zwDj?`%8D$HA3y%{MBQFdu3vfS zlHI#!PmiyY{GQ8Jepg#!mfL}+hn$iQwsoI%NDKQ_;A{Jjmq%icp16Bd)cX^bT#7H& ztXlTjjE&cBdiU|DTNTf5rRu1p**I!R99p%?)$3Yl&ap{Cu^e7<6S934o=6LA6n8Q( zGCK6(!-r+vd!|g9HA~9M+FHIx@{VnTKvQcgYu(@1@x~@5E|HOv{r3MXdV71f>wa?L zxb)IwVZe!*N8ObKpKtzMyFi_B`?AYnF{f&b9Csey{V~bdqbnv)Zf)j`cb9i1%VcIG z)tBl%Okq-F(eqtyl$*rbweq9*GrtS13(mYvRGhlx*z54mx|hlOOhhR_-(%kynXxj#rJ?L-`Pu>C;fI^aBi;kad!EdhRj(Wnw`fV*SxR3fB5l7 z9qZ#Y%$u+NN?iH=ucP#houzZN)_>pjJ^A_ae66VYmH*-vE#1;{%Yk#@&6Q_Ll~`6* zG%cCxRJAvwEp9@B_kr{sE57}lazIWtHJH%$W3r8)x(R13h zUc)26qvsKW)AcZ(Ih(uBn*8X_(9=4$_@&kfzmEqtt~%hYsi~*8Y~jMht2d{mMxHr) zc4C~*;gzqgL`6kcpZm3R%9Lr-?iFvgP-HooGHLt$y4j75j4c8zOpJB!zt{EOx^=6% z>;luw`)haZl$?LwT}RCMdTCTt)TeXzOnn#c-#_2{UPW_>Rc~sfjBVAF=kx39cKbhG z=gM@pG3iemx1xh~f$yJ7Aop((o1PbS;NIPi^92|8?AUWob3?PkWfql55!`}b4lFwH z{IzkOu3#4L6rE#QcXoeY=Cn~TOtj+kCX0#>3cJhS#}!K7&i`#*|JmHf*O&3Z_uoGc z^4B?ddY)X>^t@=N+1)SuLytTzTy#-mf}r*3P1}A?$df+3>Bi=C{#nZm&CJ|9Jvo1W zdmC+d;ap$Wc2>Je_D{3!`+IQm{$p6GcfM`uF+RD~U+(N%`9E*@sudiumnSeOBriL^ z$6;B}jENjo3ph5OGIrr`boZ3BJAC7dlNs01yXR)9Rc*gL_qt7XGP_4A_ts^ZBBG*! zF)@3d^?Zw)`!VhJ#P_D!N3wR@&Eu1`I?~9@KIe1POY^9eIV+P?RgUVJ_qsAZIolim zUC(#%mA5wwepk09ZD)7B?CCvo>074gH>p~!qMeIGe}3-T!}4cW_qkG)TY(zy)VZSA z_X`Sc`@3njVC~+Ek5A_9p7%In<=rf%X}Sg5i_b~lJnpvWLilev&8J=S#f22gt^XES z@BjXG(x;EV)%_Khx(~j`C8K znLd*|ROWo=JsdIp_rXm%>TQfhVFHUAlB91Qk3Lbp?yIf0{rPv>-<&t`J$;kUsBv>VcF0?AqAb!(~))H~Yg& zC0cvz(jFOJy2QkyuTGm&1Ge`Kk}EXSwyBKcA!IcgUpJ zHSwa1K1jT8 zFTFI8>u2A8zyJ8-LY*zycjL8frQf{2(f2!iqgDCb1Kf%n?T2Ib)y(vsu4iaz`BKHr zkf+%}!67+Wx%%6ig_}1Uudlyh;_Elx?(BgB4UgsXb90||s?UoEENBrpbMD-yd)4os zt~qa4@dfVO`Y~;#unDk~} zUsw3+OXhRO!VeD|`Q&U~oVoS;_wTvqmri+edwYMoTos2ROZfV@r)OpwM@(xKXbRAH zvSx#T(}nBT-3=sIjQE?UO%t0}^XX)$YaEB8j@aW%OTA5}wAgi>yZif6Jrhv6yqbUhe;z->@87>)zI~gT zlhadR@y17q=kS6ZJ8tasyLJ0^;qJSY>xD0y{QSDU-nYKq-ihPXsZ$jXTgB)6e*Sv> z{n_wJW3UjhOHH?FeI%gXBFxBGEm<_Q^_iV4c@eFB{>+n)XlU2`^8^+S-? zy12ck*yU>ueE49nG9>FJvw;MUk(Je{R&H^Nk{1H^e;hNP#`<|@jNYqPuPjPmi99Tj zP-Jm-cD{1``sVEG>qPVS_}l$#IdA{}PNA-enORVHczbH(m+#+=O-!D2RBiLLo9kEn z_Lk{$85=o%c7C~-=`ZiREi<;Y1$mXhJnznf6BCuMT)DC)gG*Xk`th5ao6q^aF5CU` z-MhHJ0NvA%Zf(tu*-@Z4+2+X8M;8`4pDW~2oH2hsDE?r9Qja_yehXG?Q)_x$|) zb8nPyr`Q`CAO7-X%jej#usv5@a*~po`0f8pI6Yn8+Og{9?ek~P8k(56FMcnRr|fS&zwCwxuRl5@$++_^gOTTljo^Zr_BB=m=qNB z@@wIWRc+b_ck;^be^|EI{MzIzUwTfY*Rvxe`1 z_NLD~HdnCmuicI}W^H|qB}b}W?%SOHK`++*x+#zE@e3s_YSZ3Ecc?bKf1|%ye`5N5 zqp8P~8lQbtFzpT6K6~%eQ0aHczokGv4&VPvG`{w0sNbs8-^C|vrn|Yh&3XMq`u-2L zrAwEdUMH-nnw*>*v!_B(k>%>ut1?z49p~-;&nbR>&hmQVH(LR|c1d~p<=eMUKQ33@ zqdu>KY5n!xt0a~OY3}>+h}-VpkK|>`mVG++=kP;=$&)9?>?~^4-~VUQ^7(bMVt1E4 z-O6-Jxa;(Eec|q-8+5u~y~=v^`gP^^yXD*e{0kL17OVUr$P3i=iq7AA^v<0*%5rUq z4?b4R>Aq&hcs$9_Z@!(c(aa}D#p91W|7#`3o7WWr@dg8fc%8?{PkoB`> z&*o%dI-Pgv;6cZIe?ECX|8F3d;9u?xLQ}NS###Kb^p<%p1wY- z+UT$_x8UH*oA#YBTb_D<&8~HiKUU0XZ%vJ~v=o{)>+{<^3^(omzIi`$_Uy;c?f+XQ zCnxu{9(!73xxdtEZrQKuq@*VE`!$mvJ$lrntE`>K-xpW?*0im?z4+Yi=bs;z?Vek* zx6_5G!9(TH`ue}wP74Lr@A>5A$hbuN;WZPvmFu2AcfM|{_NsSYd*Xw2>lq`|K4pJr zv}D@zBU^v=()B9uPP2Zyc7Jte^}BsLua>>s=ejS~W!-Xx^ZS<0Jm%Fr*)_EGaxo18JrO-JnUnVH5eiv#1TUM}5py|Z?0 z<}5c8si*S)Kgvr=N*>&#QxaPqCO&i7%Z_EsGIexx3O+sY)H(g=NT;wi-;>~#BFp{e zJ__IeYijXdE=8I0$;&dA1ZgI1oH4mSrJ`cS^|)%@%O*GX*Z=SOo48x3BsM)MscHVd zFUxs&d2?U>U9B-^x6^~%&+kEL;m@Bv%5P$0V?njRtXWbevFajRg@1pQ?)&vh`_My! zATJQ()$7-mWp5(R*_FmV-*{XuxuD>JVLLAmPrf#^-GwpLt{AyLjKex>)gz%QDZ+ z`s||Qs5LbyC#Q#*o$txp^%*9o4jgDup6A4oE*2OXI`#W*=Y;~7FJC_Auw>)L!m~?z z%!0gb-M(G<^=kOs_cAgvPtMFVK9{q9`SSL|?fllcUS3{drJX*0eo2vHZHX6d-aNT3 zcK4h=oDU0T?AvEoRb4&T@ywSmCiCn6S@QDoN}sFC*zJ6;>h)TgZlBm#S@%AfgdZem;l&>CtZS%)Gp3C)MZQx$xcjm{0wVRc+TCIK!W%=VyOC=;l)A z%2>AO%FfiRc+WeVOMYED&f2kigRXtb+xezPY`5#)T)(FFwix?Nvx_dX9&hcPvg59_ z+4<;cM_*oE{_^eH(tFz4+K)dxJiKM=R?F)r-@MV;yLT@r;RrtA7S{_14nEu|tRD8a z_VGuH!-o&Q*yR@;Ee&c}tXjpj{kE@(ROyU2eyPjWO?tl5ljT86wR)6M`J2&6{ z{L0|vbF!~J{~HTI7&x(bp{c3q#^UFG zQc_YCk9*DMnBJIQ`%Q9p+1pb)HQu^d`P+UKG0(qOQnPw~%_q*-*jP)Zns;|B*TwC9 z#S!o9%zV6GzWh(du638MUVX}uX(IKsTYq0c%Q*v{UAuP|f7^Lal;zv^X;Y^fT3U8a znKW^tVA4j3y8ZPxb$&lPJNwR_O5^7bC;F&KN=PK!-&cG3OitNGaqE2!*Gr?Kqvzht zQam0W8R;1sdhAK&c>}wjPlVHFpE`AF4&%wF+Js24xb@SQ&#USJm9dK!-P!f5`un@3 zyLQd`e!pJ6*id5i{{8a{EPN^}f3~GFitEQc`Sta6-_k{U_s$KEt88s+V|%`Xan-6- zFJ8S`v}8%kWOe_fS!&BNiyj_g?US+GbVg{)y!*S3g&&z*>Kik2e(cs4KKqi4Pj55j zi77SpPO1>yd&0)3*(Wwn)x9OO zYrEP`-_@hfHE+joXw6$4Zp9QPcQiLA`nzvdU<;n z+uhz>uD`qd{j;yvSNj2|9myE?uD6BdD~~F&##?!T&`M2 zJux&hQ}gWEvs<=q{rFh^|BR0x3r&2Ji;5;?UthPj^2~(7?7ZWk0w!0y;zUnbdwcub z*=gV3-JN4m$h7|YY-PJ|Zz}%(`^&@2TlnmZr23D?udc2>t><++6s-dCb!#T_EzhAy{ zXXV}I2}^4yt(WIIYQWPcZ~u%YnH=>ES1Tg zzkdCanY`-u+h;d6CYzX;==`p;4tO!qUG5>Mk=xsAd2q(%%a>W08hQU-n-KZxn^Up$ z^YrDLn9hDGt6Fx$=YXDB^0RX>o6Xm*P_Z&|EGgrgpBM00TWOl#JQ_iX`Yul=bo6C+I{D{81u7NU0t2C#W=N#a}M3Czjh{`Lp|gCT4@t8(PS@A zmAOjk&*p87Evw$1c0okLk;5^xtUfiGS?qhV=gH#Sxm!f0=T&^mJDzbP%=YxAjQsrN z%aplvNsaV&COrFeznwFv2ml}s@1E*_lA9cfB*dE^LD%6RHk3PcI{a4 zdE4Xn|Np%&-08BRyMdRF?-|S0Tg^XCp7c~uK9HN6YxC)ZawYqxAg@`oW?2+H=}7kV zUVfQ}pTGFYiHWA^H*XjI*SdCm8`qw<+5djMUhlcwSa64{tE;RKIH+1p#L<>lt-=jJ396KT&k;ockxSWs=B(mUb%YJ^7sU=rE{ioOV@ft25nlrWzO+z>7NNnEV-hM z-(Ohe#U5WR-F%gI1zI#o> z?i}Mi(dGr&QgY7jv%HUfyH<9SnfZH?!u)7+vB@{?811}oOnYf@pg?DoV5Q@z>c zBfXzB1oiie-`Z91`d6;&d3B-AIq3lk0ui61K;ihY;?BlzYoFE??u>bJZ*TR{=dvkm zXXnnHTT)(L{OwI-i-3v{=da(t=Q6g}@+@7t)aLUU<8vv=OG7T-G%+)K^s!>jnK=ss zIzqIzW^MUbc6Wc**NtzP>@6*4DsTAu^{a`A$(kz1Tc=N~TD59U&^Gh@dn$7M`qA^0 zmM&Sd=8WN0cO}6^7d7PT{}^7(m;!3i-oAeQ`tupjwo7R)6=sp(Ve?#mxu>V6;KzrD zGLwJ5d7~pNEX?qu#*R@Q&z<*~XmELr#)lNUO@H)k0w+vl{pIW!G|ltf8Qa(QyX!q! z>JGOqd>zAoR``6>e7$MgPJTLF&C9%fU9qV}efG?lYisIXe)l@CT6#yx+Gr;D-CbRCWF)?=sZ2{dw(;H5 z?g{oKF9fQ;zbm~Lo&9!~?1pX6wmvGmeEITYpSCv{e_WIv-Z_5ry#H?h>$|qS{d~r* z?G3}DsW;aB4&P|E%~tTw+1cjBlNOtpm?TKF3AfICKhiFI{$@5@tW zzj~{2p|O#%?qhengj!g#@76xQQ&ii|Ihue}VGAma-t%y~=wrGCs&-ps>Rz{Ea$Gz>Jk|0s>>h*`v zDXII8*QNg4EBpWZq8;9ezc0SceS7}s+V8G;(Yt$d*PXXJ7yUc#&%K)Y&u?D;l+2v{ z^82lSKi(8aiF_|w-ydH7=Ft11yJ6p~qIKTxkW*w478c&~?N;_2`}%nW7ITW<-nhQu z^wUKvR-C954NBZ}LE-wcWoqj04u9MJe7D}__4x-X^=^1(Y+C;|!QjP{dlBb9rfk0Y zef^?>ZFkw_`P%0`6)9`oH2=>Nb=KGvVPRp*?2F=JVqqq;Z#C*PH#hU}@+Qt&w%JY2 zsBXiXiQmfy95kK6ek((xppH%*^f?+ikEeowIFaaZSyf#zsapH8mOS z6PF@=Or-99VVubFr>*<_w({btqB%1wa(pM}E&TXx*KOBKSJ8JrBZD>=`J0=v?9a!Gi`^?fpEaL*|NpJ6+2_2bK6{oXB_%bdvElyv{fk?d|EaTARaHH6 zHuaPGjY#q7Y|m16seG21Gk0!kLBWKH69tzoTQ+CSf}3I*mrdq+9+|u2`n##m&-^_B zD&o`6rInOSnK*G`(=Gk8|M!`cW|;+fojQHG^4H7dKR@=@zhPN@L;0)vyo#jTKA*kM zuiUv)a{cw!OJ2#!%P(KNnA!Seo9u=pezq2YD_5@uMn`+s*V}K5=s9@Mae43C`t83L zf&#qsP;Ya1vZet zbz*l-3=c=vo!`ThGkZ^Q^NTQ%?=p;WQXSh%?(aPPCa-pz%ZkrzOnRBSkL*pTZ&zKY z8F1?Tn>)?r!rI4WZ7KvBK%)|~&GVTg@Bg`RLt^pb#S(3e=btZLwW{mfIluI&=ijE; zoN3?t@7}zFw|7cSyGveUUXwmqeC#oBD6unX~#p^t96Ge=~CI zXXNI^cF&q6)z#HCr?GYKpVe#D(?t+tVpP-k-2OlDJiKq)8|pyZb?bW z!b=Ck!on`yxY3as`J=|p%F1fagWZjdjE_EA*v;?%`Li-z_HEVl<9GWbrX5Y6Uwf?j z{ody+ryY-#3UaV;urOJA_P&`aRVJTjmKXc{_gveWqT;4~5AK{|EPw5`NLO#6Gsl^L zic-e1dt#BHwsWoO$p-VSe2&&D~{hAALM7A78H5vsL5B zo!t1<`TKsVg|CY#tog8O-Q(}SHTCq$KHa*#BuLZE%`I%Mg62ElZMG-$emAbnl&b#r z=H`Xog|C0jdVA@m$^5!snSL8)=9cZQ_?YB3Y1-REeYf(mvWljq`ug}-yp945WI6_B zXRkIEzHZX{?OR!mo&3g#9%=JD17l<7;^NJdZ%Y|5n!b*eE4NR4{^#FQ{rZGt(=cOmvsgethGG#O2GETMj247nkE}|M}&zzoew(!>7~Zmn~kL{CSn2 z-o_xkxs|uIbMDoCkJXFa^~5Ir^{ZE)QNFoz=T4bAb>Zt@n-0F_um9nE=;4QD-)ARP z-wbQq9^9OE`R?7b7ZHDg_YR#|z_jA*q7auF;1TXiyc;m*7w~l2K zW}0rG6~Nmd~6&KfS1E(yd!j>RVSGf4p$-UfI~|a|5etYxi#6l#rO%xNxDO zl(e*@v~)2WtEs7JNJvP|>z}#e-WowhDM|4--l->FAz(|sO1 zITIayXS46`6xnBwc3u@_{W1MFU+`o7TUTyYzdJwqX1=ES}7{Y`<64E&t~M`|N2~RBwm#S#19FM&l=FxclWL)#o#2&z}AB?EOE! zQqt0&-x*t<-S+5TaR+<}4l_9swRU z+4*|~nzn7*7RnuZ=lz*8XDlQSzA4#zv(sS{d)Mo;ZWY!Z3r-Y#bhrM#Y4w^E_ zcw(+zeQ}iW)9iDfIX+6gOfOEqx<>JxHydA4ZejVf!#7_)na-nkXV*dd*Ef$VZ{?kR zFTL!+`oB#vn|EA)y>!#2Nq>KT|9nb&{fAep*INaCpS8@;%IehD*VoP8Rwk|gu;JtH zzm}dGd@h4~u<`c{c;qUd2tK#mc*(o)|E9MSMa%A`{&tdtc33n!z-C_U!4Yt2dwhQdawX z<3>X^Hntga&pxsKUbWZ6+}wTxqiLC#RdwXDDU)8kdX;;YeL}CQ>RZ*+KR4ewhg8>= zdps$PxjOa5QCDxD=RcY5bDh+kzx(g2M|H3BkN@466TkhR{xlx3X-|)ff2he;zyJQ; z=9SmC%;o!b+28)@<9_>RApGa&=hHP;wmIB7{j}`eos$m_w-<+fyCM1L+OK_1eJ8h_ zxp_UWvT|pz->r2M4ULT-%gnz0_S{Ogl@%XuTvOjWw|?)YO+p7|ddBao+4-RM%-OSR ztzO4v&Dt*i%DrC>G#2pqc)#`H2QEqnR|YSi_O#-DU){$F8_?j)z6T2zDlU(Y(G!=I zl|6m!#x3KZ-S&qc8qBZx)EVrzZO@*Xvno#8tM8Osdr#lLG-#!%@WdmJ3zze}o9pIv zGWe_SpYC~ofApU1Khkz|W=*JQl|q4c!}OnbxwU1OErnmL*&iN}HeLCackRdR-{wlM z*?z=k{|x{C9=^8!-!`^u#wy=D<>sLxwCDf7-+TW3dJV?E-|e=1_HoZ&Ef!N#Q_Js5 zW1~t{uSNY$es3<hY^-;>%BMJ9g7MY4i8JVFH`qHg9ic6a6lJTkpd*|I_;l zgI0>j*ZQuRXbGGH}(~sVK{ZKad`?_^{d@>df zW~jgY^(1@dtIhpq<9IjBY;JnsK3Uvv2bw;JPWySG^}%jg>z{Sc{Yn() zs92P3l#D#leP&ky_ovyZyt0c|F$sQnt~TBAiOS-Wzc>B8Uu5Xn_cwnU%E6k^5n$|n{!jO z!z?g`fLRKYjG#;$l#v z##QRvTx<90>gszds(!m|YQ4U1;<01LGV=1ujyx`FJ?D2kIx_O*n#Aq*^K){}oP0FB zcirnP+qZwdnLht1$GW-C>(*YG`TF4V&kJAgGTt$B&YUwV-Rrk^?wtZE;!mAEJy*r^ z^5x5*ImVjLv+pliwCGT!#O-J2E-&~0{B(N!GoByQ&P_4U;e@kyWaJ6?4BYt|NQZLd#mTBE8GzwdKIrDhP{VP|ttXU=V;K#O2wYlp4^HQp+W_5LOO**L( z8ymZQtIS!wD;F+ZI&|WMhu@Q&^mKK({`8|sHfPVCoics;^Cv&}S(s+;vhnls3=I`^ zb#*=0oL*8g1vH!w>R(K~^+}ZBmfo8`H-CSBU+(qEY1xkpPoAi}efxIG)Tx$RAGEi( z&zwEGIPJGtS<^)8PF2;Xa{B+z-aEX<^3M4OP8|)WeZ9;hwGYL4c^-IrC|!ajChyMb zt%W-`zPjtOY+0e|q>2*e{ijL4z1YJ0l_@5=u&@eEj%v z&$6E`N`>z18@kpe-)5C7a9gtfqxAlRxAXVERT1H2Yo23S%y!sd&djNE(=P72@%EKx zJD=<+$<}k{&li7sa&k_?-ox$u&-XrT5sAW=k0!<`6w$cF8-W_?b&PZYU$l?*4P%`J(FTr zcE7x|)UuLQ=k%iW>-+cZvs-fJO62|h_4|c6O3W8zO7YwOF%T9Oj@eOg@a1u7^E?aV znQv0vzd0=1vuDn~e|5)=46UtS$F!GoX{s*OZ;@E=uWT`if{Z4A+y@c6>{PdD}VJ-=46>aXjcv~d3R>{st2=BIzJRSOu6&1F6>UE+0m6dwj4O<=2q{-!6##J;QPMsw%^{}ot@0IIsNKQW$)>y zUBko0*YEqK)o=63qi_D{Z;x;PTV7)Ic8b}V=lq*}i;nlpFW<4_hGqNa^z%jk{`^ce zpOhMDVs8HV$z*>a4wj;{OEC-CS4X@xx#Q&0 zXV2d}U4Gp++v9z*$;bO-&oOIM+iSnQy-DYJXokJWCYenaYDLAxKR@c$|McT=zxA~_ zOE;ICh)K3CfA{2Sc>L3g?((H_yQZZY8W~LrzVO^nabieSZO!(*vxRweIgDIG!~So6 zQ6B#%*rQQuUk@41WIVK~$;wG5bNqvr0~Z(Frm z7A#uSB&_cDVeb2y_FK22p5N+oQ92iNY3bjMcfYSss=f1#=U(3ryRtVkf*+oF{WY&* zZE1D2b!llS4=?Y<`}fZuInolWaPV7gczF2bd-wdp!>0$g96iRTr>(F5eCP9d#rN{F zvx{pEYtMDh&eqo7_d_W-IJht2rqa%mgaw&WpeEV2ZPP$A{g03FUV3TLmKYEhH}CqE z{S)`;YHL5fnLfYN=jhtCYqO_iObNPl^XAFV=k5KCX1ZK2Eqi}2_QH2l6O#{jiqBh` zCskMfUUmEWwQHa5zOS1OGVtrG(Bo5He64!Q$C{TB54X z=|zhdH;czr9Q;|EfBkAgwEVuWYx6f&f6x2%>z9PIw6auh(kwN({`Oa|vdZ4va4au3 zZ**t~&^Yq>y#4zbCU2K^L^rCcn%dd_KYY+#vux+jn-4-Kl^>{DAZhjQlUL8q%nilY zyRUK|=Xmq=vrgxs?YEz-D?UGWjecy}^sSeVeiJfcEN$;ETC=e7^shU|=85kAa>*OC zX5jVp^~Ppq&r;TJ7Cs7ES99ZrM|!$?Z*TAFYeDh5{yQifILvSFdvfBl!34-<+#%WfR_kCQ2`DI^HL{ zc<0WWmTJ}q41Qa@pY}UE(Bjdrudjs)>hAPTntSVW8S|P=zCkyq?#=nU{rKa;yaiMB zrYt?`$R%s3m%IJiP7Xzn<(JO|nJBV=7R8>uB>ggaH~;6Wt#PaGUJ_->-nuzm@?*uF znP*?@nm75p?R~?w&ttAt`@DV`eS3LcwDp_pGv5l+&NrC7mb!moBbTGa)F=7t;vTJE zAD5yhc;0&UREM3n56|n;+BajD;~A6dXS&rJlcsk&himb*V(#oT#oI3Xz1pn$^9mNMYSaE~%o6O`a$(c9js5NmI1er^?fo;`xxZk;uDsi> z2j}tLKCklo`*joD=kwyVW54y+|4BYK&-V3^l{No=-%o#kZ|}LS+rG?vUbJ%#npZ4sLejo2z_ogX#_Os^uXWC=873lYdZMBbo{oAa{M%r`Y z>ep3$Kl3*0-|L-o+U+ijbf>`f{pp#v9@f|$T5Gk;Y2$CR`y2J=9?bdfd~(vHhKJAM zzr0zzc*m6eJ8Jq)f8Kl`DzW^v^9w7tNX|5YH5Ey&h3ED3cboja`%3cF4$+3`%L{r` z7v6aLZ0fnZe--Z)rMkM_M{wOL{rdmW*9T?0r$-i;J^mH(a%Q>p_ueT&hYdbGJ9FY* zy5jj6yFdO5U2`s0FlD>c?Y{wg@An0NJ-D~_jZmi|%l(|Z#jCgU$bXA{BCh;bXGzfP z*R!vcez_zhkhsRBX^-S9v7Ms1_1X@3Yrkx^T=vtoaQpR#?^msO(*I5F$!y)ZKg>3M zdA;l(U-^d_?~d$yKlOF=#+`S*g0_nnO=K_%ZhTj7^FxC_{QAek`_}KYv7cQzA%D^Z z?pSwy&9)5@b51(vwOLJDvLZJ6POtI39RK#t>B+ro%ipevUAW^qc!TEQ8{Us@b$S0Z|K0b&Cz-qc zdVTBqW%+4AxAP8_r=)#iiqE?je6 zYQ8xwrgOK`MoZ9Uo_EYTnipFi6ol&9rcRBSG567(HzLjt1(Gka>Qt*9VEe9b!n@a^ zVW+^!j+$>icmHMg9x|M{^|R3SybGam2S3YNSFBn&^_hbvhnz-Y_|quSXwZhpM|OuM z3C&;kIm6eMT|3(%>hb~h$A{<4kw{dKQJLkJJ~j8eP?Dy2_wkgj6AG$DEA`_qU+C^K zV2wC=ChCQcmej<_JjWeV?rl_gG0Sc1`SZ?Od2O!WI=Qh;?UapOi|!l#3Fj*J#&vaf z&wX0zot>*8GW+PVwV*=)%H?k*neI9nF=g}R$u(xB&G&vvE_+;<^fJV4o_P8V#=}SV zozgmY)<-Z}hU1BIlvtWc*G!K`hCJUwLJCvsrk!{cq!GP3c9-AAwkD%szS?xEn7;wh1E6R)tlAI?r~bUOFR=YVG<$t_m4n)>#tN<93Lr#knPzGi$Hivd4yL!ko{93WLJ(CJ8kvJopw67vi&Y z*<~?}gRhG&1_gPgN3w?6+1E{%-oDJzv4MY5eXZ`41=`!VK>IG3-Ig8jd3N9KE@iHgCn<=t3Y0>uEH~*VGz4q*BxQ0mfVwSjt0Xl~#zGK%u5W8^4`WsWKK7bBr2+(+^ z2l5tIt5cyoXj5N+hDg_M0ab|Popw9nO2Eg!*dq@K+u^Sp00i_>zopr0A!3CzW@LL literal 0 HcmV?d00001 diff --git a/docs/html/reference/images/text/style/iconmarginspan.png b/docs/html/reference/images/text/style/iconmarginspan.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec39be039f6f98f355ef9666b218896c06a3e7d GIT binary patch literal 22271 zcmeAS@N?(olHy`uVBq!ia0y~yVEn*7#JE}Fff!FFfhDIU|_JC!N4G1FlSew4FdxMTavfC z3&Vd9T(EcfWCjKX&U`nwVg?2VW(EevO$^=rNx2}w0*^?LU;+aJLjuExY^U=K3=Hfg zp1!W^j~J!6bh-6EcU%UkD=TnJXJBAbU|?WA!k}Tu%F4hHdc@PkF{I+w+r8f>%eYT_ z{_|^x$6Bv!T}F{DN(@a5jzTP<{L6P+?ycT!wmI9_I=ei3TUvE?S=#r@ckK)658S$Q zD7$g69pE{+NSfz^LfBf?j)4G2C$5$_T?FkC%3+fg%JIb*!43sjO?w;!7{68Sea*V z{4jX*-ARchNKEW!*|%N+CawfCkt6+|jbD7%b!0ra^;MP3IsGQjX|1opLAtw7R;dpP|dm%sZ=?}9~b){tVJY0mn zfs)MVY?Bs+AKs7;lDdnz!yf=jwa%{SR-S3@p(*_t49g z_ui^CuKP~vtf>U2a;~^t2fSMn6lD(eb_(8^C37@L$}I_MB1h`0FLurrKHQPs5d}yj|RPmgUD!EC=dD4$|4^=GJ9EcmBx zh4}Y$bw-9uv-B!wc^GY;)6(`glAmkViBjkAzSFCUBRmtPzYTI!n(6mQJ#ebp=fY1# z1`ov-Hy23sSWF4up**kq?ViAu9fmhbKl~OxT`E%aMKW_%+q!jnK7M{p0!)$93V(b^ zoHAv~o-e->cCQQk@Y&_ENl95*QdN}{L)n*m=S**dqIX|}h`-n~fzVJBtIreHo(s7t z(~%|BGilCM!zoOmW!52H3k5V^C!fkSJCUO_@u}g`FzKG4Z+`qTxRO)?C#E9 z_vfR#$0U};7dtLp3Ys!)+P*LC56WT}KX~4`EVJU<8_Bv~FV*Yvr%##k=EUB)*+qIE zQf?OgJshC#w5n@^l)Hi4>8J;XS9Psel~&Kgdg!A?NY|SFg(4isLr<`998Hc)y|VE1 z^NT5*2PWV38wLD8j*gTtjIMkTzQjE11m~32nUgh+R2))4F_s%rf7UZxg|JsxHXa8Tt8L6!5 z-SOwn1Y9;bbLLD!VWHq9HXHt%J*`)>Tur2WmwdXL%pdmbTI%0>{MmUe0y=tni#Bf- zepn#EU|?g@bMWB7o^@`%mw&wHRJ7ddUUu4)i^V>@NbLiQLrCyPe`V&3`7Sd(5YaS$zx<2F(+TVN*obo09 zV}wpy_4jv)Wo2Rxhm?z7UQ*o=vo7}U%)BR3tWEQ+Eq|v4Fx1bHJAVD~;eeO-{1Z0I zdYr!ab^qJ<2mjZ6km%C6mRmg|$Scuo_Qzw=`3gdvDK+wnEX>TzFW$X-c6{r$ZI2E# zGG8#!eO%bJ=*`Kk{!ScXVqza2_uDJU^*diSnc=g{R{z74peIkCKK%3ZbH#@Tj7b|M zX3d^`@y?w+ujG_l6y*9BFJI37_wQeYmIDt99(;LuxnO6Eb*-l5PPayf3CfMrIUJ8a zs!6(QD|h?zyK`?{lvw7ztNio0!2aK!p8{t#?a96UqQ_17+&tUe&#Gsb9$|^QY;xt= zwMUnhdIyA-o=yIq#bR9SZan=m=Okb4!=Iw_?S;YsG6*TR;C>Vk4uu^iHkr zjp!4bdYYP;7=D*aKR*9ZinS?bLuzAxw$wf;d8htr$Byc00t|_!edpbN2fi;@c<|+| z^2*nfufGTleOkS|@vH9tC$;B#a@3?}_IdxST~^Vu_T5@P+j6tqoM&@ZKKGQJ{^UBR z;;!q#@!qYGU)IP=OSWG*Z++8wQnvs3sOvM{7M>K2*w(gxYTdFpdj`4Qy%)&XlQ_!d2Qd>82R7zpG@=8-dx+&UR*1`);#U^TwTsRE*$gp&ODrbGBD#t?AP1n z)+2V0s{qCtzJEG z`t<4Fyxlk&Z@)cu<%&qXZ=lGPD_0IAZA|$Sv}tSUv^7i~HVN6;*@ArSmDQ6}5|fgg zuAh7KNNLxuT|f5A^08}aYnwmWHgC?HmfLUHCR8|thlH$=+;B|k(DLQW9W`5zCOP{0 zo}G33yovAO8#g4F8X4rL-?|kw!$*ywll{hx8xlu*rAst>#QX&oA`dOWM^WO=ylr>qc>&h)P=it zNrh$SJ@Pe*U3@Vk=Cg$p2U8=1nwpw{wRQK>rK&B56Hjd7X+M1M{pmk{YVN9}+un#c zckWzLQW8_%_SyAzWxHirSy@-CS>sYzXvo8Mc-LFDX2+i1-altdeEG<UdNACv3hM5D?HXYnGIVxOo1G_cxt594%yyefeUtY}v90A1eenSQI>k za?GSZefks-7}z*z(xlSq-Wp41%$U(|{4pbg1P|Ml>(>t_7&wGpopaB_&k<7UsQ+SzRmkE=ses zzIQLJs7UDh`}@bc#q}S2{wdIZe6rs30|^ETHf#`3_n&uUrg8eEy2Rv-5_xxbwcflL zxzFy;wdnkZSHt7a8uWDPe+rMUZGC=z{^!oajm+!;;oYFy@RT! zQ+~ca8ynk?Y<7ORBkuCGTh?W$%&$*KU@*_S^WcBo^SR{(4-d8W@GJXX{_x?U^Oo(~ znHfHP`t(7({-e0$_hMlu0UoxGN5$hmc=}XUTK;>a|Boqcvm}E+uUp~gXTCi>Js%G2 z-2dnF{+63LZYn|t&(1bCG&5T!T>9<1hf0(F{y&rY=blVC+9=lQT|S5 z)oNq?lY&m5B-C&JPhw#J$CMzDUHN-HvQ<@86}-P^+tc5#EW`Ku*D?*RPoF*&e0t(J z$G%?9Y__ht(s{MlfB*gk6_0LiZUS7bVMm{7bTQ37+ji`j8%U9RdlCrW#$9kn}UWZyG zZ3Ly=!oR;tef<3&@BMzS`S8OFJ6pTEy9>X)i3CM=huh)>Qy%^M`#U2i$EC8e@>Y9z zdb)a5Rh2;Z(UObT!d8oZtcdv@;9n|u>~Z0dq|PU?4Q#N&GzZoGw$Yd zQxS615P3AO^NK3dw%biRovw3jQfWyjDiV78_N|13#EBgt0(|Ws|NJaIbMD-u!~FI~ z4mPuI*RjivP0o^UI;a3j)9w87pEmln26aW~wB5WJd1qHC_u|EiE8g$@9uO8bZR7e? zt3Z|Nj@sX5ck;^J8UsQ_<@(wC&;NhFqrc7A&>2`=ZT;`Zar;B>zptVB z#0idjRj+mDSQI9m-Pq@%G(m;)VSz+*b92R?ABE@MDSK(ov#D%)eSQ6FyV+0P7u(3S z-+p`S?(T9!JG*n!KYf#)EmYyek=Dd`XP$(s&*e=5KekRj6u=<0G`jrb`uWvqPivzs zSflmh>+ApjQlG?eaKiQG-Is-bY}l5$T6u%AfYXJG7aPyp{pN{VKmGr@1q&ENMMVoN zWI9}wX3U;__{$fQMHe+fLXS>X_fOb<`{TFU`LA0h2s9-h?@P?c;OLjPZ&UFEDg68U z`^SUq@@uNz|M_FH@BhE*BT0t)uGRnhQ+eg;)xwSWxw%i{ljfg49{YGokcfy#!ReVn zr|y2fX{VsMR6|?aIWbZ3*zx0z90#vl5ph}=5G8J|$YN}4JY)X+=lmNYblMg!RFtu= zvspa5E-S06Tcp@mny0Pj^_!C$o^o{`W%8V~V3pRgWy?y|y<4?P>s^wm1kZ-sZzYc_ z$n`(Ix3~Jw!Slx^s5;D=KmYilLrn4Ru`w}6%KqIZSVK}=KKEc z?&YJ6BBG)T*R0_w+dY@tS~}WGGj2}>qnBpg-`DZV`uh60@$&X{J?r=V;yRcxp}RCL zPVUs{(-Y^<=l9YS6Bjo$H9fjtxVyXi$MgDs=^m3>gw_4lROe1oS-5^ZzpSh*N2`;; z?6Vqrde2UC*v;oRHa6y9VTyS%-=?x@)hewy_Vsm_A9Jg?{Jyuh8kFef`<&d=bLmo0 zNNA{{zJ7oI-5WO~&QII9VMD{~>+6;E_4#{ydnMIZziF?WHbWr)tDREIg7xe9<7>Z) z+Q{+$mz(XQHf73`4;w%K{ySst+}88Y4_~-&Vc)5^>1UR)#4Q#nPggUL`{I1gKtuav z)-`?U;Dk*N0vr_|&1x%%adqiUkIYJoyj*%TZBxVf7h%6<9d+EZe!AA1j%=x2V$UCT zyI!}bQ@#E->iGqS!tyGn=U!R?X=i(@-=;cp96WM_CGXCTgZrO!EqZWeW$=!IhfJS7 ze_p7;W#a2>A>$SvE}nOH7i<4>J$U2CO1Pc=?bUjF!v(7F9bb!h0(zrVkO0yq5ysE93o#-rldv@8=;-sI-$HXL#Od~&k-k1v<~1^bWx z-1k5G`ns2LYotBr9WwfT^~#k3@g2t>AN=yAt@!L&b3(WeGlaoLEtcg*W)akZZ zLSFv4Q|k8Hfw8f3ukLlyY`vIqWNY^IAHNSjG^qQ&`#vb4Gb9+z{BW=O{mW1J>$j$@ zS*7)-&i+iAvEN*)R^#+@3ikH$n{=Eno3Jo3{`v8^KO;Z?`L$a|jYr&nYW*3eWrH`+pUjLO^no;)jag?v^ zDIK$nDUaHBX5NvIcMAzMj*H)TlWVn7b3@9PAQd%D7Up}ZQzF+$|Me)F0?O_C|If3R z;8d)5IyL;mx7+!LbIgig2Te=8@wRM^ZME3O2%h-5pQ<6Dt_lJN{O$j?w6(F_J={=h zs2{hd9lW< z+@y0j$L#f;tJ1BJC+z=xU{2a7ap<9en!5VMOP3Z^%Xf=QNJ%v<%d{%rdHe0LBS%=c zxVXY>cQ`GakS7!z7S^_B&z?2E-8mleo0s*zVLV}HzWCya*nc;Bnop)2lFr}L$Stl{ zaC*LMveC?fr>8`J)Y#e7{Fu;v{>BZ7K6(3nYx#qp&Dvl8-){Sc)W~B?y{8wvyrin< z_hzrzY+iN$c`aA7j>`Z0z%Iw|?aRwx8T+~z^Ov12N*Ve2#~(dX+TZs0c>m*Oe!GUN zSywBAebj`rva&urY?o*9@ClfCeC6J~vitu$)epbh{c^?X)raT*dpUp6qD4;T$Jm+~ zb#-+EL|E(pf3H8Ueq;0I%>}#fZiq7fuG4w^@rxHP9w?pMcR74r%)`k_WpmvY3- zwOC5})~Jj3?(Ngx&YG{a`{?7Jkv*(69hq86nRSbz7A5#h$?DMkZ+-Xop)8rR>uhB7 zG@2YIB}_^+c`Ewg?JBD$v(Ip>{k_N?|NL&tu% z#S&6dQCq*AmAmCrq2l@I;%Di%`DWMWzqJ50PM@Ej|NJz|$>Rp=4>mHhS4^>I(9zK; znEt-(;5$vRj^&wBuU@|{e0Rrk?hf_up_ffe%*`LaxVU&v@6x5JJ$-$zY^;-$lV7}l z|NOY9sOZD3*W;p9-<^DGCBql9uO{;O%*v0CSk=_juH9MbsM&h@>7lLJ*8>6rCzkcx zEbkMoP0D-!g!6>kaXqnaMG>xr8#YYP-z;leW%BsO#%lYU%7Q#>m+O{ap4k1nW9{Yg z($bF%ww{wft==nFuP)7{N3)h9PT zGIYMT^vv-92j+n11&`LQvI@Bsw#JExt2gLO8t(O8h>Pgiu9f}$$k}C?QonxvDq*x}=a=_;ofNmzkE{J~2 zFD3l%pFcJsp+^t5^IyDkr|03r#Ccg$gSw6#b9*58ENVX`wsA8?aQ->HjDP2zelanzlDKty z_Snc+7Nwkh&lw$e`uzFlr)7S3@l90 zV?T86C2d@=Z{Ixi{`@(p7qeyi_Rpmy z&u00H9-H2IGQ}x2R(AWTJ#qT=4-Awha9yP(P@RAT9lv z|G=R`hh*1>&dsfg-h6XSbXDR>w{4r_9~MXm3wJh!pZq9zGpBm>s#Tzz@!{R>_mB2| zzxQ}u?6tg4fAh>hgIsg2T5LGU{CQV^m!X;2G{a5yKX-hcpu#EE?OHAyJ=0~Q1poBY ztkX}kwjW;TojK>-?=>1-c{>$v_U^y8v#3yhxY_BuN#@w@#UOo>d(@ui<;pY z(fd3lTSk#3_#nf-`Qmebymmew@cII~%3Mu7W`;l8=cUi!WDT1;If#q5!G&RSlFQPw zeMYNgThokvCNa%c-1yC(ktNB{Ws8N_p+q0{zZ)cZI*wd*Nt9W0?v1Oa>)gYw_2nNV z_(Vn4?wYwv{Q1ZKUrP^5_ddzF=FZ_b|9Y?Bet!1PTxEZIC41dAZrNgFX)M9>p=$4o z*RP|^Exn3bv>A5X%{!8`vFG+Fzj-#D3l}QdRDa`P__2wh+F{S9Q`#%mufPA%F!`hf zQzOHoiyCTbY8S3uJNElk*=|EiOHKvB9U;-t-J3QUO_??=DItMjVF1VB!-q5S@}3=M zX?A>fY5A0ACWU8CE0`X>Unbn^_A|<0f=A4g607UBeL-Ba&$e}o>%Xcy{!6CU?cv9Y zirsqCwO?C(IU}|G_S={%)2B@n=yj`8@}efs}5uSHUHKePzwv>d9~w`D@`>q!hR%FaK|ub%hlzHChV zlbg=xG)`+6MPGb$>iEk^Mj?#HaDuC66HlkW-Ij*PPl2JX9=(UfyOZubKiXe> z`NdbABtsdukl@PSqGs0rUOl+j^XT7pTYtkxpP!hpGcZ2rcD?v?-f_loM?Uw;YK~T? zJG|~3jrZRlKQ3QC=jz6svom|kRfHN>t;$s1K67iy&rhj7etxg6eVdW}@6FB4){kcS zzfCVJH0BSUg>GDrJin~waHl$QM2Nw>G2Ni&v`macqo_re7Mr-cG*!nC*h zOfs&&dhOb$xtvW`S(q5_&Z}E}b=FkXGbc>e)$N~u|9y_p1m6iVhciqLj%Gekop;h6sLqxkqA*{uCroSEMqL9qZ*HTaNO}W^*6WIWre++a{*0tjy4`bEjp+-na-IF*%pxYkGeDs?vM( z_3PIS5jq@f%{B{pqvxkqZ(%&}vgFj+on>z(q8Fj$_fkw zFPY??4LWDd+5PSRx7+y_Z{FPbs(P))sa1u(=LCLkO5|*s^n3rrlh^B1vm5&}rP%g( zcVxZV#xdK4W7l2oAIA^I{qaBB9xx|J`>&6uWDXm1$f|`mr}B4C`tiX`>Zi}m`sw_J z9zv(qa9;m+^HR|lJ>5eZCo{A)qDtS)xZhnb_3CiP{oT1Ye{(P{b7s_Oao%ui?ry8v z+^m#U2NN33rrrKY*j%CWq$}3i{eq6}s6LISF>Fa0t z{}-!Yopfj2##^V>bwAnn&g{mggUjdFb&2c8eYk%!$1Ef~eDSVbQhv*Y)h2sxKl^$4 z{5q%f^yht3<<5OII$Eco-Kfsc^wFZN_FnE=9!I4~fB2jAKlqh0zmn05eHEs+%koE( z>fu|PwAtpw#0g!9{?8Kot};em_DGDyma~inEC(xilI!HN-kgigJ*eHaX!Vznu&}m% zyI&b+7a3%4-FmG`a;b>@-!Hp`9)9pZ;oMy7_5h8RnKLEzqSjQL@r!?+r*?Hy z+)IpT0@B!pT6M?~p8K`c~fD%>pV%w%p#^aI!)E|Hl)aBC~U2o^1cUaN36t z%ROs%Z0Y#F`!}Zs!)LZ@pUk?wG>S9fJ{7q4BU z_W1E*Q2$hN`I$81M;|R7eYCKVNJ z^TANpglkHZRWml>x&%DRwo-XW{;YAyL}%-GUSAZi|>E;=T8l2Y|LiAi`Ds~ zc{|kChH1~gvGLCj>$}V6s%3wj!NVpH_P8R_HSutk%)*MA=h+u79r<$ThfvF0M~0rC zwH!wuTi5-}PBfZX()8+URprvnSC(kr{If*1JN5dXxz^$#I+PJp)rO?|Y)@AC2J7ad0em{BgWa)OrcjcRee(YG5XeH9{%Sb1KL7mI{ zgZwSOHC)?nb80k*h1>mm{4m%eGUQiK%rlk-YpG*37F)9O{hn=Fvuc&smrBdIS^ob1 z*RBN>iLKgt?(>h2$?s+@pR#D(I=<)U<{nnk!dzvGdC%WMpI2cplDn5RGkM{rn{=aYk&)NGA*8kN1&w9W9|6aWXo40M7_MS7{^37&Wo$jMa z6(1k5et+OGX-oF>=%#}T=Pxrfh-*aU-zmSOdM+s1xVc%h=9Jm&dwIWkijMALI`1{h zkM(bj!nMDL*BGry>5YERzxqO%!hVMOYZVNo=l0B%ljdU$cw6u&oOi87PykP-%BNH5 z=6pJg{AcUlFt9u}^kMlE{pRO4rPD@HQBfs}F5kS_`S9VxQhAP?(>L~3e=l(j54wNY zYtkm84Q8`>*YEu%CBw&l>eQ(Zr?l69$cTHkDt`5rRI6*7eat|kj@!3A{%8@mr{duD zh|ti`rMgbar;T2{eqHHx?%+Ykd3jTxp1XIVY<=&9?uLzbn^ZhAva%kPUNrT6Tzc&K zE`1YU=lp#A`#%LZSRRyEl_uZ2b7#%xsM6BX#GIU*v%lwx<|i^{>lg+HUw*en?xdPQ z&(WhtO)qnA7TH%@TWh_`_uSK>k1cL%KAf4X?tg7g!$z;ElP6EM&XdStk6e53xCt+V zWAEM03A5(jo*~fWHJkU~4o1I4?%%NTcJ-e+`}@uF?;Ky-!+l%N+`@u`;m+!X8eCFRQVAwfm!Ija zU$x4~(Xmm_RCmILU)Q}NcLhdVwRf|TYrp^gx#*8=R&3Ut%Q9E2TICcREIiMy_LTP5 zzaRVSk6c|HUdmc3F-7gvk$*oP_y5^?=#bOAlYM*Jm(JN+{$8%%Y=7-u&OQ3}_V&_~<$UsXJ)b^(iZwqrMU89AjI(Jjiv#2G7wz6Ht?obX%F|g1^OiL> zGS>WjI(^rB&Ft5yNhVT-UtR=$`SRt%`*V+v_Xh?CHy>{2U%s}~(A>Oz{r-Pe+1~Z* zH|{$i%;aw}Y3qi3w%#BaO`T)wrK292ytA7w+8o3+{bEo3#S#H|{%?jM)>sxZI-Sv(&Kkb@7Yt|#n8}4pyA3#eTb`(5h0=2xZtX-A~YB0Zg^++$Cp3Ozu1}^qg4+d zK3wtZi{`A^vwu3Dep!;2xw~J*bH&P)jVDtc;DLr?UwahF1#^fJTdP z`nd%=cFgFWdjGw9c(}NnO~rxffg-LC-+Rjyy9QS5UK8PypRZr{|M&gJhg!K;tXz3< zYPYPMoLhDE?sXr0OG{19&9y$h-Q%5F#2R;BUtTLKs~;}Ee|>%Zp~5C3H@Efh!-i#< zGv?2CkB^t1XH$9T_>7Ihfp6BBltf2M%h^^PVOF&N{|7XOvuN$wUiJN)?T0~wmd)(^ z23A(5*8cl@Y!{P1!kwk*=Y+C%%QFCEc; zH|c!-dMk!iP^+tH)+{NTsxKY84XmxK3`|UpaCgV-tr7*T{19#ImTnE|0!?x5D&f4o zKHgnL$k9gb_+t0|4}X3ZKY9NA^IQqJes*tf?_HOUE%%>)Ond#FOhlv46B#F|Bqk&AgC z>eTDY_xDr^^UK*hV3Oxyd-(Y$s7rR=dEo^={@QaVy&lwV)Ck!%DJJD^C7-^*sX5WSAAZ+y1-Nd-J6kB~KqOJ{I=J_q#BQk?U^Z7hPvlSf%xuek2vh`oq<%SDuI7Ucv(599va+}w4mHi3JNM|BGiOdcRqb^yEHu1V@t9Y}wkl-0 zfwBnK!`<)qJvM!2>g(+7&8`1*X4fJO(7Y}i8=HZl;lvxaZd`8%m4IKb$6t?plym;) zOyhI|Tif0M4HnNy0-Y`uPdCIosZ76Ck(#QyY}vAk#J;w+HdE%+5e;#1adP+5o)=hz zu|8e<)iUjKWRTazOP8A3+SvB(VN_8%XDi1q;u@LSn7a9O;@Y~WO}#OTRXi=Kzv-Cx z9-eDm{zGcd{rA^hkE>UE<(NswRX!Ds*;S(Xn5Dn>u73Sb@0!0~uY(4sUQUag+xTSL z%+iSH=(TSz&IhX#Nvwr{J_4@tK=2`k)e(?QwY4l%>r3N#7EJ|OA zTTP_rl_4dw{6Ro4<^e( zLqm5JEtok$r8Ar{S@fV0>(h;&Yd#!g-*7YM#oM<}#d=gc4QBf6GOcprSQDoG>C-0% zj>d}_XS75$6kVpf&4>(`ox1q?2^;&VQ>Xs;cwFAl#Dr!4{q^2;i#52`Ta^F$l4)XU zx^!*rJTGr=ZZ0ma4?jLW?)kp^Vunj%q9UKHRme(rvMzs;VleNCFMWtXrq| zw%Ujm`-XgBR%*-s}5pvM0-G{?Y^4;<4!iPInXzkgUam}&J_goFjqd*^)KdbvL zYPj|u?)?AuhW3gr>-TT-;BfTmJuJ@WtS0m(t=RASGE=XI{VfZ#q-4^Jc|QG$FL2^e ztBLy)-~TRAWzMo&vJGCdWzCLu)UCIEt+2bLJFZR1^M}1(j6>teHK$)p&sLh)p|{|4 zj@f6AcfE(welPS9JwMdx(n(gUHPQ?Ey+ zN9as5l`qTNE}fj5Y{PIWX(MQ5!P2EmEd_r@=(GiB9Fgxbo2|Qg>)!&4HPYr`er38E zOLr~NyE&(S?S6khzofJ@wv7=z>(5`ma^=Of2b1l^&B9*Wa@^cI;myB$K30#{#wfqe z*AZ(5&m(SG_i4u2H0Q`jNiJ?~^O#zluXXV;F;Da=x8={%6-}MYqbAq?d6A~{&o{yQ z55Kw^_fER)c=LOI?X{eoVD$w3*LF!CO}e|gXUv(? za{v8vrnrcRj!m14?y*VRKg&J2%zwA!G$Cv4#{Y){`bAf7?;QG;>|ljni)~mu;E;a^Ia->HDVswhh0&eXih} zn|;w{we>f@UrhV8?|SSPp9h&unCA#IrB8nRW4HXf!@{0#j?ep0m0&va!+QU{lcr98Fm;twjq!zU zaYlO{v+Q@rUaGcTd-|*G-YMqd)zMet!tA$yu8RA1BURXAyYT$P?J8ylxkWIfj zrl_=bXO;iD{@ds3-llE3#XjZr)^gWLAF?V{KW#3s6?PKnal2qUmm_W>N28AChZd(7 z^XHy?R`U9ixjD1H1xw^41wl>`mRRFpj$?}=^p{Ks>S9{W8~$px0{h{B=7&1xciH{U zzuX2|fgUFo$+_o<)4}2fH_cOP%jW3moqxyRl^7x-qW_*TE%IScS5wk%W%XLEQj0_* zm2HaxC*9x*Pbxp4q8+*Wn1ATi1&1Cw+xuJ4R=!- zk0u;hc&TVl{RfU7uP|+iPK~_Tvg%r^9ZkD-*v}C7sk^I2#4|2V{8s%sWzd509Q{bm z8G)0Eg*N&zMu@QHvbs(%3y7N#)Wy8?>5u#C_x3+*t(+mkJgdt)!Fbj-nHo379cR-1 zeEoU-L)B%*BU>!8K63}AY|hW{byH$FeoE_O>3wdNWWyDW2}$2lZtt0XQ04T5?}=rq zV5{t3bJ?8k)!WRZ9hs7xJZpAL4x7rvt?OmDQZ|`W{j`=q zr<>3rp9Uu`i%y=a4^O@7y4m7&)g(jJpQ#6ID zU#))Qr*X;bdE8w^o=rWs{xlu=D#)Os_~)X7K$gm`#D#ih(Fu2!eOdrsB7Z+6@nGEN(^HdZ19@I$iEbu^j?9Lu0!KUUvnQE48%bR+xx6{a zIPBS!{UHb578b_mRU_jc|w_i;2wN=Zk z&vb1v;ydtZMZkps789}P@;43~XS$k#R{QRF4h|j7YvmW24Nh`tt^KxkZfDSp6v?vY z!ua>UHc9$!km}K%ej?41v)*<~083QKT&t5)4&@mvxRiDOm7g`p1%G@_>P?nq^wJ5F z*uJ4`o(6Ari<8Qm=+@<(7R;*;a%|%`@3sHau8j*W9LnytVGYRIcref4&Har+94t%+ zw@nHav0du0^D5iygAGSLI9}@?T7F!?bHdUV?yCLo-i4)9Mn0I_-r4zklPW(~?#`eY zDL(NVS6qA75*gOA2OJB*DbrW+is&_SJ`Iz0`+Lu~(^!#ZUV((X{a?;a(;~OYxG9J9 zDoOOGUVQP%@U&iGz0NeZkl_FGW^AzNaT9QQ(Crr7bWezB&1yTB6Q2y(PPjCwOFi5l z|I+=@`$FHtP8uAZhq|imUY6GB?b7+w;$ZZ`o^7$t;#1EWw#=ypWpkFM-br&=`46no z$S8UC@Zp^=%lupzd|H`){C%Ud=ri|%!qU*p&?cA4t1FfznTbSID@`n5X<%Bh!EyoP z3_iALrskd@!K`IJV?@iWQiO^m9Mz>%5TnbzQsC;SKfO^Mf>Mj7xw6=#(WOB ze)G%pg|5O+%TI1P5$rWF-9&f&e(u8iIy?0Oe{H%g&cV{OcG6PT`3p?fGn6$eCTiwC zN@)?0m|J#YQp=4$N*Ol$UfFI^ab#=NNj+D0KIz&G`y17%o>6^H>_(l|)Bdm2S$;n> z#q+zBr`WqC!hzS!J==6VGjFMBwofn-tC_?hVCKB=!lFQr2*ICL?c98e19v>%?w}yx zpfl|Qlb}<=#)t$1iETHsnksDi5)4kb-c2w#khC#n4ZEM&glUlq0vtuI*O>e@UALZX z)SAs~`>wTze{;@+x1F6HJttqxIC5~0lZ1-rj_2l{Hgd<$&Njbz^XAMMAyU0Rj_TJnGPCoggnqBl ze{-LMon37c-t*`xB($#f3h<@!Eju5Oqqfx4 z!;D+xiMorOSbW^}WuC`h#6AyEf46dZblm520lV#fR?I4qiRD{y`idF%j>QX3PqTLj zUc_GV`mN_9zsdC)4#Bo;otrNvjWRQp4^m54Y`OI@DpGWw&Y5*TFHCU=NZhO_ zVw`$rxgW!Une96cw;nhnv!#NkHI4hxtZ5t}$?qS{`(ZN8Qc+`D#6m^|mrKzvPrtuv z+aY}Vp2o%~Vby|}yY05VoziZu+UZrn=Giu_hV|vIX(oEEY1%q$tWpb4AD=k&JM*0` z|Bx-l4{9n!*WXdUu{&c{+s~hsM;;fR*u>*|dBcVc4G}tQlTU8Bd{>x*#h%$r<%2^K z_w}8Vig+9uRe?C>FNV0jh z1qLnn__qAlKVb%`hUBBE83j){3-7a^*wpjtRaS(KSc{Y5iA_DTW=SRPIubsaALq!3m4bLx{@aE>`PEg?#78WkBnrl(?q=RFN zFb9i%Z!fov(AQJ+sJX}U|;&KJQ8r!3OlFQCrly}2eKRsKOrkyV1OCexIsJC93UvkqIk zee?DSSM^F>%uWt{7q))qa!db92Up+nE8V<d`%cS_}F)`^pjD+_QaNbooXirjcxHskCwUiRauQr|-!eYBA0cH$6m zefW4qrc~2Gh5v?9y&G=k7|cFvAk{1VFNa}`lzsQnL-xgD0yA!vy9O$F&fdh%!gP>t zqh=$!kl@#|=W6!E1@_iUy*oem>CQV7a<;2|lhNPd7;4(QV|mo|6MlClf8V@3r#!xX z{_b@LOOMX|@L;#f^FN;~O}IiV=Tz+8Cg@gKeeE8n;*6Oy4{pC-*PUU)<-~Drj%D+< zZDv0H{)-ncX8!y4Z$wm-lZg~7!wera(D>HYt)lLIGLCU^abZr7Z4L(xFjQ4l8CY7L zbY<~KsVTOayJFRdcG?ya!dSb90fZ1I*YBJF&#PSMfPVY$Li1{M|_ z)!*MaUN#Bv0`2jcV^y;Nv6LD_5^}s`8zf9ty^L|0md-nqGY7f1CP&38Hcf4UmhE5AGGnG>YWcKW%2w$Z)y*=}nO@65es zt;ljPLE!v+`{y&SYiVmQT&0zFXGi1Bn~`(Ba;RL|qo<>jkdVL-8ygE6yt`JrL)Zy4 zAO%`eTD-Kn|MuHsFJ5TOv#ad_E$X|p)cduc0*7KmRMerh(c3?Kc^TYtIC1WYAg_&E zwzO>AXjoEK_UO;g&(dBw&YG_2>FS`R@-t^jX3ScaegFCA#JoH{{kS~`K0iMn7$3iX z-HFY1-@klu2o24c)pqI9rO@}6O+cag;?*lAMHav1!q3mocTZ1O2Q7_nZDo!9By(;4 z%$bs3zI}Uiq*J)!_1f(Z3M`EJ6ct&zySsN(epa)xvJ&WYG3twVcV~~Q|66+GabZb$ zxpQc!1P>c%gl@?lQI2mC(Lxnonq6na{$$1qFWdF^!x5#O#aH#)mlRGcuXZra-MWL> z^Y!f$PIo3Ny_^(x>ReI$US7ZQMIwdE|IE00noZ<@cIwoMwTrCWA8$3gR5Y*7iDTEU zT^}ACWUjE0n|CTSP$a-hu>0tORaz>ZOiM*T(<4tN`+vCTF28WqDy_H7^OolSeKOe} zGz49Le2wqoizkA&xlKF$gpG-@=KtUCpoOs?4)Ysxi72vwhIi}#Sz1|JJ8C|yot!Gk z*4&srz58eqXfkna^!5Y;iA`mi>*dz-xktaV-nhH++F=h{KCpVvmWgeGH{=!Tes@HOVw#vPyW(( zi~KvP*B)+-H`{h7%V5uy+`<+CA79_bAg<}BkH$XbYiFLd?BLt7#mm8)`Z|s#O;F*S zJbCht+TUjX-kkr(Bi8Mzp5@c9|JN(+o}Qk9KwWL^!z+WA2Si76FTczg8fs{5-Tm%e zo=xqqDcrT;;o^{0%}clZ4Gk52`}XaNSFfUW%BM*7_V!woyb$2x;!^Mw`cr4$*4}>c z>Q&bD*LR;fb>2h_WMk!LwHiBl@qJBAOfr^5E$@@lh1=Jy)9dN!NyyC1jArTId2zm7 zEtl`*fQX2W!|nWwmn~xx>t?-}A#(ZhWzYmmT3Xr zd>L8%hpCal&CTt?&6_7Lf7oQl$-6Q$_&z_=c75*QSsXvS z#42m^eu}KNTl~~dNXUDV8hd0{b(B$;wWrlnuiGLGr!x-CHz|oL)fbI-_x5(b-}k$3{qJwzzCHT+`ME{mqa&-n?b@}=z`)=D>!yAC zK*= zyWh!Or@<9h{nj*QR|%)tY~Sns%I9kSg@yb#6(3$e{8q^!ze5)oM8FB zy{#=EJbZb!_av2`o}LHG=ht28y??Y@yzu9zr&}JEe|X?%SLbwxwP~-f-=UM|P-LLBDC%{Qi-Pi`}<>{p>j}Wy!bZpf1qP53ACy-A_z?4_~<=!avDJZhv1N-`yXk=H`#T zUXS;;u4@t4vSrJIzu)h-pMEOz`YqEzIra44aVB1YspSvi&E;HH9%btdirqN#N{jyL z`mj^0KRmv)$A8m|g-7_c-Dkzi$Ch@2RhM(C>yH&L z8P1$Kw@f>=`oZ-k*W2MCAq&>8?_Yn}&)0Y1qD4*L;{!$JSe1HBKhqjHYnP#~Q=rJ) z>WIrGD^{(l=D&UK`NxVM@4dXdu8HQEWqQPxd8kVDCgtSxME~nrv|#I2(X`E#cmMv( zng6S7fd*HM-t}GcdoRCixtQT%An|1Kk}EelZr+UCaW}8d$J6uVEMaRl?Ty+39N)|= zMJp5%SI*PaYd)KB;PO$+Gc3&;wA$9N-qSfffg|$2#8HOHI?As;9#YX;^Gy?${;PHgHGK9xZR68Er>PnlCOn~`hLT^DZyz$0 z64Lu@xL^Nli$nfS^}dB2fn1Yyl25$%t+Gtzj+yklp-ExQ(ZY3mxbj1;zm+U}9`4KW z>GS8GV!B?Md*bv%LVdFjZb~tkJbAK&l+>XsS43>&_?0GdJo>of-L2+>4|d(Y!PLlb zf0MeW5EnOhW&AcT8##9ai6{TJ-OK^4477b>sVc(t@qAbB{WN0^E|#Xf$G3s@Q{Vd3 z{lPEP_Me_ileJKsy&&IG&+KnEITxlF9$p}^u(I~oBNI=lx$6ped{@;^{^sLS&yuS5 zqUZP;y%o+J1q&8%yuWICFxBY2W?{O@`-5x^1r}#E&sJTWDb?222Alek;Q5q%DfW9+ z_3E4Zem?pBd{bYQh0HbAL$iIB)lTo4y7TSK1GCRQoBM8(gF;1T&g=usoI9(xt`TyJ zJoNwi{pObm3BD8Wzge~HU=pi?1Jgg3f7eTkPbVL}<(Iy9xo^_uUmrp*e)4=iL6viT z=#>ZYTeiH7EMyN`e|`Dpe(z8#p02wsHcg6q<9#&)rpAQaZJ`euTkh-)is5TN*m0lN<~Vo% z?EiMtPox>AwDZog=U8>o^oVWZ<@F(xPP^Ti-0vG%?QhA>nqV&VF)vryZbg3E*$o?; z6n6KfD6)h_-)j*7H6f4nN`LtM*XHxBh0g5-KRzUegoG3n`uBab z7d5C&5O{dSVb9tLb6)nP7TrEQKiF#0ahvOp)de`p>~HIRa5z+XcUpYQ|AH4Rc9QQ7 ze>a}YvHIhkXY14(Qj8iK9C%q6eWR5-eZAOr>u?`mvt?t{otY+AEav=u{P{qoeg+-3Ptbaive=t=PLt?ElLn+B+iRcxQU- z+_!6b-?Gezn3yN)iaaNQmM5*d*V@Y3)8GF&X#4J1E?^z2}OnrI+TJO+DsX zt3J-}n{(*tr>q&pQEH|a`GhN#R9&wmms(XVx?cB}d$rM(iBqZ!wK)s2=IZ?kyC?%{7`C5EOLiHeaiggtYYSqT9P$OCFnBTAuuVmYb#N?|JY3T@M#!lt0ps z?t5@w-lB`Sl63-yqn`PD4>e_#*Ig%GUp_DNSE|M+Tcj_sH^6B@qtgAHW4}w!{y49G z?x9zx?wL)Sly^Qg_cD8Z7JQV(r#Vm7Sol`%v^$!Y6`gbcchuXq!wexK&%0oiR#jEirdbX@{{HKUW-S+PZEfA< zdFIX?88$YyYqI+zblB#f-+uY7I0s8~%JD;4TT`?DB}Iz*?BSe^37%4H>Rm@Ud#|Fe}B)lzW)ELz`Hs>&onZ# z|Me;`zq}0Q;p6LCQQer(zj^Wfgszqg;u2?n zELg_)|L2L!n6+zMXCCEpom64>TQ~N>rW4MSf{bo2{dhfHpggzI^lqNPqZCiee~&jB z|A_CK=Mj5eVu)E#{SCB&sHt6SQ+3oasK@N$H)6$s|jq+zklrU z@&54M?q!)0va&}(OO_WdTo{|bck@T5cgGuNFnWoA%t zV^(2^vN*$if6W}}m^+@A^0z+iF>`Zu+IhBd^3tak@o}wE?cB#wyG!*scjwtmYyC0V z-_9{T9W*GCxc&CROsQ$prh%6A%v(Np@Bb$!C$Ct$_Ug|2k*T{DyZ0xhrL{$Wla-a7 zF>|J4ZLRI6PoFAue_Xs6xZ`e~o>`BN;HN1;BBG)XA06%f@n-Y+9WQL^{zwQ53uk0z zIwmG6>iZa5SafXMXm})P;~JKTNh))!O1YYwn_s+r+p6MOu>0Y}+Q5 zwpnsx1W#~qu!Mv}!|k{Cgdd$dwtm9~0Z@b5?*E_9rN{F#GB_45UL4s!-$CKV-8Wo~ z=?0A&1~UtnE@E3hB_!d^Z)3!zT9$XA>ISG-KR?pRrFq$Z|HL zNs7;Tu}D}mqmg2P#PrRJb=l7fGPt=q&O6yt6>i5TD*8CU(U40wEN|`m(8ToQIn$@J zduf8g=gm#ylG4&bbG_)`^l8?LEbRPpM~-%jFVr}-YHPLUq=IK>Bz=5+7cO4hyx-2& zc5U>2e;&h!i{{Rs-+%C+W89py!a~ExyS{yVeEebA?!xEiWcBPo`_t#myXJax^Jft; zF(rL{erIRr*J8^GcgE}}dkY$*J9Zp2oaD4nz{=YC;@!JzwU=|NZ2N9%W|mY{Rds$% zu)KX8XglqW>hF1BkDnLqOfZ{mXl->kUH*U z(Gs|yUzkx|t*LH@vLMSN!xbUTZkj%uCPgv$-Cw8VJUR8qp~MxZr|k(fJdk8?Ak%2! zm1hoH4|is@bBFS&&ruWRnRMp&=IO0-`x%#N=<6S!X`FuH-o3tw69tbX8NO3WS2}a8 zpkKza$^2f0a&m1!+R^$C2iXfOWGX&A;bdw&prtMA!Lse@_3MvMP1XMJ`>%wYoSVt4 z59fM%dle;k4&`lMZ`FS=;lR_=(-X~RgI3Ho4U^P$VQY^x>?WG^4)w)Kpcy_nQ%3Xu|K+ zDPXZC?8X|iDKpP#7(^zRN(DYGSa|4NL#W|_H5wB*BG(D;w|B7>b>f&cdv;)OaPz)> zc3-}KPp+$*x4S@BjQiB7Qx|UC>S}9iE6tv7?ET|z`F+st^$WLecXxJjvNSucSj9E@ zWXq#RNhapziEnHgxIsgwtkm)EafhtG|kqyicX*?lTC(yg%Y zgj<{x86G@MHugPqoC*Xh-VhhWED;N*8S~WHZQd{NW^t}v_kCf zn?1hUYHzH`$a`Ypds)F#NPfT1e17kODKQ);vbHMO+t;689xKLCm-j|{zx36=DZe(G ze_Opj$85IWx74lM@87DA6mWX*vEswm>+#7+NlI(OmB0SlwQJXg9Xn2}{d?(B(2bmJ zwzhms2hUHeRsF!!n83Pi``=@|(xAnrpmm&}POAQF75&KuulMcVE&cZG+dH-S1`-#p zUE7w{)8e3TWA2+s@x2ZT5zaQE)na;-o8|3BInJUJmKV^P4Ms;W96RdUv{ z#JW1U^YiVGPgeJT@U^P4W1d%=eX2j}h+X>{1Y2|6nCw}|iMfY{jH;^*fczPq~{ zv|FfZFKAEsV)y=p?Y9#RBm}yT3UIIp#QCwXs>J-*x^?T0s;{dG&U3R=izt4u7oH@4 z=TYL{&j(YC9-f-24H~-o_xrt}y*+4qAa`EIkHTVp!yK-*rm8}|de5>2EE-`%k^F*RMdeY^PIzkfkhpn-&ojokC!@7IQD zYiVhLdJJ2(ZF{!l&3Xrg8g(BO_5vI%yZJ#rY+1lPNlU_cEKQJCVga8Q*;Lyh zPyt$G!SPMeiTa8@WEHKcTLW3gu(y8$%z{7v8>gBu&6*U*!oa|wTH+c}l9E`GYL#4+ znpl#`U}Ruup=)5MYh)H;XkcYxYGr7sZD3$!U|*7#JE}Fff!FFfhDIU|_JC!N4G1FlSew4FdxMTavfC z3&Vd9T(EcfWCjKX&U`nwVg?2VW(EevK87DP(vv`f1s;(g!2|{dh6IKW*-qyf7#P?~ zJbhi+A2CXCo2t+IBo+r!S61Md&cMK=z`($Kgh9iQm6d^^fx*+oF{I+w+rO0+D(Q9i zIqU9qocMjDR-{ks3gea9%@Idu=WM#6a`jDExwmd+j_0%|$Ccl&+50*X+o{smw|J}QHh2YZ$``J=^*ZtP8Y5{E+Ae03PBH4g*si7Dw!rS>4UVM6z&kI=mbg5bJ$Y- z-bty_NbuVK*00ruY7Lq9e#RK1Kx=u1Z6{mzIxi>KyL0`1?^}^G=@a zX=yV&R6A3GTrO!EwQXF`-*DJ->S^D(w-3*9;8?!s!W-@j@A!kB7Cj6!mFZGqjaQmz zBLcEtszrd~QW8^UK;AD|%k~NL_CE<}hLPMNOk!hUO9)cjK+5rr1n< z^6d?e@l77{v}KGRtYxn3-n->ja|w%64oB$4Ctn2%ECO35B99o_!5%fY;=wz)6*PbVxE)&GRO<8wY zxVIowi^+I#X09GU-=73TRQ7cebdisOR+M z$$!4~uI5oodpmQNme|ax-If2t4k_iX`C3={?)}G9R@4qj9>8p@!20Q=W5veINHqhg;fOy3Mvf;9`}z|NjQT;&Zi`&d<|y zPWk`J`_vqL{m0fmKfY~imR);0G5d>9XNv0tCjH7c8_&GmyHVr*{+oyUf9DxZ>Ir^Z zwD=6?tHo^&Ph{U2>@>1H`v3KvLOZz^Pq$yoebRbN%%&Km+ofnb$ogvTne>HQxsI_MvZ@u}YVbB$D@^Onu#YyFUfu;zxv;M#H3(Jb`ocp&;LQSA- z*PAATOyBrV#XbktTfQ-qTf6D@;k8+1((S9lOb(Z=)M9KmuGeu|xx)9H$&Zu|W_qrv z{;`K^?OqFYJ}GqP+Vf*ARwI(q{x zzG@Qnmr-b^PUhZCW_qDZugWW|eg4K|hxgV(t)FJQG7V;*4ZCt|;R=>Hesx9#lh-W# zoa4ON%N?(mGdenRS)(XEEy z@M(A5E`ILJGCG~1Tyo+@ZjsK^X~!phShH}lG0(9{oc}_q_|I_&I?=BR*Yw|2@2a}@KT4v>UtMYobek_{v`Dkp-gbm;QL#_nem_PY` zm|#=1aUjT`s$^sT5|c(3rJdHGgkUM6_+$1%#(!TCWZKk$2AL^LqWci8fQG7+>c4QL$nA`@4I(5~cX7!&QV%DudHQ zGsB~uI`w5=nWisSU%Df_fx&3kx%xe2osS_KmCh-@y-|F46Yn)LYr&eKMM^KO}R4JL&e`L=z%J~sP@9M-}Ap2%=0=F z!#H!%{GCmDm4-199?L!)H}H+=nsuIgy@o`Zt_7}FFy+IEC0)N`DMw?;s-%%m`wF}&TN{WJVR|h-?xpcw=qs%WT!Tbr_*KA zB~U6Kl3 z|7>~hwofE)=Hu5Kt%aFV#xqN9o;PfqnbUUf22bq6SEA8O6FgM<#X;fA>&l_$@Bc}D zeY{do+B2?7$2Eo39jQihW=iNr<{Z1V?A~MTs@m8UUCnD{|C|nvHxlUAeN~e-OQ68w zgcRHI8IIL_b%0Qc_q~G^e7|ZcUQ0<>3cW zCWlhAc0PH|BJ1}r|Mq;x;3r;lTt1nA62*lJzLV$ke>0SrA6v8chKc7ZzDreoDSGa= zE_mk|&uo&u$nn}a)HPo=Bj%|6t&cqaA3d4=MPKWa+AM)3rE^pC+?A%U{SmRo%+uC; zr^LTKJHkKvm%mBQ_PyKmeZ8x)%O%Y(M#g!EO+G(&I6v`Bs<`ir{8?)A`T0N0er>i% zPh#5943$aC1vEcQYHt!~y67_JygS>cdi_mnHQ$M(X-4O`aR`Aj&{Lc2rbMg5SIh4) z#l>FbJFws4t19#6rLK1GS4{h!b=_CEebIxfG8tV>%QRLL=ayVnNtbxS)9j)*yS#69 z@f_`Powm$PhB1f#7r!rlvobs8!@rLk6Ze(>HL^UcVa>3S``wwp8*f+&I~h#s`F!+* z!t17ozyIWVsZ4Sg1Urmr&OC{yWjSd__w1dzbJm;xZ=NLnt2kervm)S$?)%d1t1i9a z-mrRI<6G0OCO`jd`PIxKb4j{;qG5^1 zhW^tAzGf|}Pj~%3`BCuS&y)HudtJj#>TI(mY7C7ItX~)Bf8gPSb7ev&gWE(ZPC6^* zzL1fYR$KY}&CHKp3pfsTZcP051LThP#W@^P#eeR*!82>og;)Haa-5BSznXm`@V3L` z>){j4Z4UfvOY^y`k}R=jca`n(W9}AqW@k2DOl%Y0{KTiZtiHqk*3TTzLVHuYL-y8% zGqk@ee&46`;M5n9+Rer}E7whhyMWo9<6w%>(U+YUe^2T;D)jUJvoC@Fe%=$$&@4#P z7WuhQ(%wDycD0$+3mlKNTFj|fuu^L^vtk&#Q%&yIsyr9vnj7`qn}rwuNtfRC zAtv~AQ)&33TJy^Q3Vx!}g@WV?p27 z8*~XYy*pX>@6#{UeRnUOoYhpC!MNwron!lcsqLASeDvtmg&HNHS2x(j_$p05y4+HX zC*_=f-^Y7r5tq(3; zQThMp%Ji6Sx2}7^EeGBg+`B&K?J5BlubN{YHzxjD{d>c+I4(t&?P=*V{FX-=J@Z$Z zXcGu3Py$#coO!xKwA*gN1zp7l5smG?4yT#TeyG1L?mydK6~5en&8uO9^AX{*vBxb zYMQk1W&Yn6RE175yRAFuEvLaG;djPp@>vGaQ?p|w-}xm@Upo8A_Nr|QXRcox=RW!R z{|)@L?|!h}&VKbXy?XxhztPusSeQ<{f1QiXx@@E5>p3Bp+G_6nJ3BLT(oUJY&~=Bs zRs6X@g^>s&&#`nvAL08kUDuZyoUL&bpT6qhRg;k9%tT|K$^c5p!S^cH(~tFBk1oxs2;uF2wqixEmWa)&V*yHjLFXTanZDb871Yp@ z0*4gOWtElBBZPS_zxW!ug)g5;arxxXeGFYE)C(fJWViD6uh|jJGFR{4!)>-E6}59# zDx_RksIi7S^kH#^i}B2^Yga@SKV-~ z&0~`}ONui$ZsR(6WV7Wtzn*F5TLc{b(|;D! z*|~4S4WC6Z8zn!zepf1|UtfN?EUKn)>F-*Xh^O2izMH*$FkkLx&qJ3zYd$Yr7ZR6q z%t^Fq%RJ?&rwyn2dHPp%KGA&iaJQ)UJguU(iTW$8!cCS=lJ^ps9l3$`XVI$t`wMr+ z^8YJLimw&ke|xLw^i}6%Gdt%C9Zl2vE%?M|@=Tv+iSo-_lqwrQA!^|gm+|Pd#^ET< zWtl|_3Pta~x>0a{^6@+KgOz_=HJxh{e!U~|;HINR8@29pTDDL3m?yQQGwzdvp-=Oa zdC97!$BryH_V(d+>+J$1yR@>5Mf;0aJU2RgF>{;A_JRe;vU<6xdF>^7$0tUczmSt# zVkz=y>etwi>RRt@+&g3bylc~)BP*xWv~y7<(-+I()7Pvy zG(&CA#a(UbuV!oCs#BZ#U;A& zl+M-LR8CpxBI6r+;)T`xn7v0j|K01lWgRj#Kkt@X?l}eX>i55kpSwI|4;Siu^3oAh z1SL+Ie{_Ay-*V=Pb%mm{S8M6FTg~>Hc)9d8cPxja(&WgAF<*BjPB@bmIK}e2nX6*5 z#F=H6MZa&{S}3Z&t$aeS&*J04+C}qP7(yq$elK+LGN@t5>?+9AzG&SEs|}n#Wcl{p zx|UcyKm5?TZDnR=W(%dQb|o(0IJkPQ^vX~7s_)cHy?#7%MKoEb1U1r zCG<{vaH!>tot*m*d+JHK_N%}9S;DDUpw$&qKiy-V(i5pYPYxzNOMh?Ys~q&RWm`@8 zvXiqMX3X8ndphfSTh#y`cG%jIvc=VV>~Y{D*wxQ({`^wzCrDx9$n|QS}W>(v-rm4?f{bl^uEgq-1_Ibf7 ztyw`kOAjtO_W0?|ZCB?=OCB=eDKa+vveV|%XN$P0x^hqcB0q3cO+R3x_-+@^1*@lf-(`Uam{mTD) z!#nBQQnPQ~zjLUBts?H~wNs&zYEeTas zv%cS}?srkz_+;CvRgVfRI9QlWOiV7^x^-$*=;{wudxM_L4>EB*xKbl#cAvqlp70pG zvbv3uhfQvV-tH)GopjlzizOy6$l?1Hi>^fvLPC95YClFM>G59O{G}&Gl#5qR-*LI{ ze&=-uy;b~$PuE$cZH{zvbL%*o7)*qXGCUvIovB0k~r-V)xhbVfyw0eE#UX5;r<%Ve<^Q7KY@!tCt+2zFOuQvJ1 zmoEke1_wT$w?E&lzpvx)#{#(vP8`2}{jw;1CDN9dAi*YWmXq-N+uQDKr_(meSQfQx z+-S&fC(m4e-w&mQ0UlARS_fF7{yNqaAByRmc~d7ahi$>l=j$zYo_*Z0Ov8k=>F48Z zU7tHFM0g&&b5YMQf^4pva_?D)a0ASw1Y7VO#ypMkGcd~EL+$Hyh5r4PUVmY;P(yDMq- z4Q<1%;@c)Z`}s7PDekOewL*peX3tQLJ^mXi?;YZLXv~v*F3IeBu)-%dCC({gKWmH> z)L%CzT2_OaBVk9JPZ||Y7tUON{q(i9(S;T=f4;{5H{HB>Gl!yttZZsQ!Gv||^fK?& zbrooEy>1S*5S_YZ;r-A=%TSFJBgtcFX{XM4wVqI~I62cT)%$$$u9ccxJ1zbHdI>l^ zn30`4<=wU?-Fyqry`62DfB27n*pKbnid}ob)C%g4e zS6lCYFInhh@G<5>$8B;ttHakX+q!kC_jJ958b8^;96920^smUe->-M|B}!;?HCc(D z-V?$Rm-xGgue9}c#9z^UVXJyg7jL)QJt@Fpwf|+4i>0y*p`mZDYt>4e|K6&R6a6Dj zZ~E#V#yYE8ObfKP6zQbjKeg4c@mzqY;_}HGQ<}P%c0HeH%KLBmWcMHc|GYUqW9Azs zQB&U!mNHv@G2fZfIqB+GSMfis0y7%iFGtAo6c?+_lCQaO-}(t_s0imq&$?-Q?Lv-% z8dk-*pDMp|Z`;~uxWA(>IPfJ;cyYFwm(6V57vInAXYzRYYwq6-`}}vWxBlIy`iVm% zePe2J>Qdd#D`x@WEhN<{}NQ=H_PC+rGZOg&!Ze{(kSE z;EhsMBR)!Mz=Y%_mQK?!F(oI&5m}rVYO&q@|M!3MP1{IN8iw zFZJyE@0IJ;rCnVWdgba>UCC61mJPKsYucl0Yi+-N{rX~;pNi1J_3QgPJ2}(R(hPis z_uu!Ajg`&K%?$|;7w7e95m<6Lv#qV|O|AFx%RBDoH62_a`)WtXjKx>|^YisTR?KN@ zW1BT=Ru`M!Svz}ar>i|G!aC+xduF^=Ud2@uCqDBfhnv~<1nd2W8&4ZZ&gz?>8SHWF zQi!*SEgyTGYidT?tSxz*ukW~VyIB0+9esJlB^jRMX&x^utIKpxbyzoeUtLjXCE0ma zAll_YYZ7O_#+}IQ^Y8CiPqSDa{Bzwir$;ukjy+o?WuoX6WwoX{wW?}XVsB zjh(&IZ83v|4Bxdik(@7DU)L*pb7SL!Wx`GckB{-z{dp{Z zdvS60vZSvUFJC^~D{cPfyIJn7BhmSLk1n5I=T%={KXFC${^gf5>%MKCf0JK*`st(F z_kCSEDemjC%uBa#pT4-*eP`uowT~5d=0)C)?^yQm`|rr@d9vs2e(S7VyS97Y?1>Wv z|6Q*Ca3g6r?JU7Sk@RchfH*>aS#LMupU%h(u&E4J8)8|zlE1U4< z?c1jx9v;54qp;aUDbS=lP=s~+ZQtx{ZT&qTm|kC7t8Hy?`e~7jRY}KU_x?$zpK`Px zu6dgszj*UzWBbR9OnmCquca((tSbDkPf`|f5p4Q$fg`Q)#Ox=&ce*zG6HWbfK+HXu zL&WfTwBhr3#c2ZNI`erH14J4PM04iXYj+)b@j~PM-tTfJQ|_rhIBR}C<#?YgsD|5|eqN~4FKesXY_>S7&W+ziF?d#Vy@jd+f^Tx8bQQzL*_m7VLnPlIV zc;kDwKvROjiT8i+y;p2`aAKlzNqM<&uUq%KP(_x!?X$mqD~qdoscL0q_3oF|G>>=U z{5}uXM$Q$DV=F(Bv(fDF!mPNrNkJAKyt@$%c;wdny;eGR<3>ZfUoVs&eY9A=@0Zqez1T;O`|Z<;iZ(s@&hXRV`0DWWMpjm* zZs+emyDj&&SDv-8@!_4t&)*cAnVB8?`T2R|?y|MHpIC2%-*?%1`fQ3-X7{7J3)q-< zBs8ATceP~P79a0)=RL=UyBYW9FFmsN#-6%#qkSzqx|`}X?{7ab+uY#N&)13hxyq$- z`JUa^u3Qn3ulupEcI}kej_F2{ZEbC!Ix!=oV}0G%)h&k~PGY`sD7!EtqvPJaIK7x1 z8y?KlkkR4idV)-)`NclpndDPu}i~e*I7H+tZhe>{@4RY!;FM4)HGVjigK$8nwza8GB5VNzWb>l|E zd$r$V&GPPOq<@;^-o>u>JCU~e#jbNZi=Ur(T2%A*b-cdO)b_)PR@TX=1l|UKCh( zxv_%E7=HUd6I_%MyGt!(maSabxoVY`&*hY?tgeR-6VslpYWMZ^{djr)KM~i!8#{}i zPkJ5^D54j?FNZ&9&Et<%d!H0pE?d4_Qd(Mh^2r;<3zLucP4rv7v*xGKL=P6<%PZEc zTefYR*!0t>zde0?eJ7rN8X(g8@?|EUtd&b~@#flnUbXeze{*N4y>1G9vts4*$p>%i zM6KH6vvKC4WPY}SS!y#M2KM;edlDOQ;n*tI`t)`U>)m=m@ruv(hW*)<`Tgjf`$>X@ z%U>9CUB7&G(e{?R;TP-fSJiorG3+Mspx`p-2I!6yqxTB_wdip&)>51 zw$Fa{EG;WLyU=Rxk;jE??d_8C^4n|U&ds%Mzx?va-12)HFZ)m5Ew+32?vrVoCE6S{ zL>8@E*XN+nkSO7z1d4{7z_V$aYkoeR&cehvuln82Nlt5CTe5=98ebHTBkgq**cUkVu*H?deF27v!>*exq4tqY> zZF<*!@}y_AZCzGY*Ll0&J$5bd=KJpZPp_?wX4ny~5ip%6_+hzgCs` z_4H3L?b)=ito3M`&8)UX-Is1(3z>XbW=Xr-1!KvWt}f@m!n2mex~99$POk5@4iTNc zOp#}C#2s!~U2d1#OEWE=e>=b9Gox#B^P{g7jo!h9Sz3CbhjV9qv+Ml1C@w*DgWch^ zSvySKC$cWA(2Yw-*tB+A<6F~GiO+hHiY(#l>uR1r^`gj|!^o?z~x0`O}DqgetcW$nA<+q#ZD_5`HyuQ&# z?eN;@?LXdZJ})67BiC$^*8+)s_gQi9g+j7^aO@8?6>uXh6-!<$<-rnB+^5siK zv2Mnn>}R&+-nJ-s(4cGA8u9vR`uTZV?{l|0{W-P%57*qea}Ufdtqu+4WNWth{buuz z8oPV-|7+9I(+zzmUtaFNIq@)?S3|(k&(f{GcC6_Zn1@V+y}%-}7Sh#P9$A_dWNx<7JbJ zCcWbEH50|-Yc`(Xj^t=O{q#|*c$~uIlLap>C{{}@+P-~y?eA~8H`IMESG-(0Jz{s+ z+P5j;(b2mfRGaxs7M~UMqpGCq_Qsi;HdhF9SjDdS^z))fYtGFJR*YXKtjSimbiF^= zJo7_UV5>s9M7hrm*UfK|CYG#}TK$JH#8ow0%C#x`?7HXNdx9@!aI;(#QGI{LJbp#% z)4=-W{OzkYSxFpPYv~c??SE}stJ?Rb((?92o75!K9uyhPJ}YK@UsOigX@M5k{g=0# zZam${b>>#e=E$n5s+__gk=DPzzuyj1dR=|Vq<7USt+?v9rk7rtxcA90ermpRdb(Kh*-7i)V|u^6@R~8|5#zez3yo4?QMcwtv~*K-=EKKp{}lOS^iF@x3`yj+WB3( zch8(RZ<}($x3{;CYp>svWbeGD`sB-!O=WMR_~dL(#Q(h-f77pad;a~js;XUUuC5f= ze*5g|_4`gWGPCEro^$os)1r^3wATxCAGN6av%}Wn<@@*P$NOZxt$4jv_`9lwDzf)} z-TVEX_U>&{Pd|P0)%J8_>iK!LZ|?7(@1PJ+_O0&O8Oht)VhP{h-CYyEf8UdD*Rp4u zvTt#Bz4)Jfo94w&NeDVIhx=<%W!Pi%zZ0zhWU%b$;&NlL${OnoUx3{<3mu0H{ zW`FVGMZ(opp)tG5&Win*Hcjl?ySvdQr}c6cuhIg!;Ks(}Z_LI*ofA%`h*fAtdy8gY zU#Dxej`cTph-K`WZ|px`zMVa#+h)7g@ukI3%P{|1-`#6+AI?@@T-|l3cV?#0wC8{SG&X*_1# z^qQa1O7BCi{_!z0J6xi+cf;8`by71*!me*xx-C)f$%Av18?S9#`}&lL@0~nzSy|bi z+f>&{W+zlUJj8mtDcSPL)b1|^zLS?NQ;Vy3$a*KQ{ATvvwQGCR=T)}N|NAEWo0+Yx zt7QeAK|Wzwu!QfqdJ-QJcP{lzLjGZQobDC;I*uX6W`w6 zzPZ2Np09oJ8_SokUOl?Avv`|pVo}khZ3|9D?s*dUU|#x%R=>2T8YKd&*R5N&b*pIq z@!+E9(?6eodwYA^kC5>2&B@36T&FWC*gw9pHCz1o`T6Qny-F<)uC0xBQJUE0_Ex($ zQ6gq{*;nT~_O2cc<;&mv?v9CgydA_U(;ZpWCTf5}{Kr zc>is_9l^oDJv}`)Uf-OyVBgB0dCP_Oq7*A>-Yt3(tHyef7@ zD=wYJsy^56^V8|^LR_sG85tT;X4+r0XTEQ%b96uX>STP23>6;a=VEx2(qJUcSt{y(V9^Pcv0ZyY=+bq^qk!%MQ)T zG0UE#VLLbCnYH;;!i@-%7vdut=zIy8tVfeZIY~@VwldhY7!Lxt?!NlvoqFxh*`d zvemhr@1~Nyo!z+~KP-;-$tp|m1cisYXJ>2Y=H{-6+Ini+uRHvTpMTb5%u0KnH)HNx z)#;~2pZab%=dCvR;jv!nlJfG)*RQ+lh;2Un@zl{Z-YWje+CpmK=T5HqP^-u?ckW!9 z|9?LJ`ElHS+vO|sEQ{NGXPe#3E)9Dm#;xsrx2V8E#x?CjiB<0NkotfK{zZq_`!2tX zxn>tJ@87kbn{|}@m6m0G@+y{&@8kP;IQsguxa4>-Ces<~SI?6@-Ca{zxc$qv0)_*R z9_4X=-E*@>vQYoqn&n1?+ov&Uyx6@!UwFm$Uq7s8mj^s8n?84Mv_{vV&FAgBb8~gw z`{h85_A)lB(=K*)cITd+p8m1I#>CuwbLs0av;2E|UL^d>n^~WK=KQ{lxzjs4IqN=l z#}_<2#A+?x+PW+=V^)~Ti`JW3s*{%Yvd>$!ZT0Hz!|nXWc6NONO%ER?s;R4IW@c*c z{OcuBToK~-!T-|6H705gF3bEm*QQ(I?^~cN6=@j!H|CVYmIQ_O6K&N$E%klA$YafP zvF}Yctq%x2(mT~-(G=XYRE zpgR4x>HPDc2wuB(?Y8F3#A7EfFZVBgc!)LfTI8pYPX}1Y!WHI>d|-W z?Vrw{mr>l;w5&>d@?5DC`rlKIs~eQgT{N?J;*Q{jJEEXG{e$$}*@4qLOd<>7o~^sQZk^t>_3{2n6BBA`?)Z6Ui)wH3d2oGwyt-Wf z=ePOwyB8hto@!*zzAv?P^ZMSMH6OzsX*X=!X14Fs)b$sxT??DCf6;_}`}T?6wC{XU z_@t3*Pioe@_nombjAH{uZZDSp#HqLD%G#-QZYEN$^Sj-{L|PpbHY{oX@%O;<&kxIX zzcuyCUb`V^(n*!gn>T;EFW3LPQ>=o)XR>)z*k#$757w`XU$;Z;chLsdx};C%X4#ap zgrAvzG&t_g^X2o`Ry@nwZ?12&V9t){+xLEoo>=}km%ru5;oRyKUn{id?VYY=m{Ylm zor%%P%Ib|vD5x{^`MiC4O^u9epoE;%CQkh_-)m`? z)8#pS>Q}E`<^N=myXO41Z>nP5hBh`a6KCG#67Mx2R0=s(8R;Z)Uo|$(hKM9N~56`B~%B-*ewj-VyAw%;8JytYi84ch22jeO>qUpLrIAP4<5; z+dn=3@6Gui-~a!+U;Omc)J@-~nHs-)@nXTojTiTQn{;x?+z&6}HW(?1i7hPJeb;rX zEQ3&IOYYR>J*L?!pEri8`YBaLRVFayS5B3;ue;O7YwkOF)-0*4tgJV0eJ*cVZ5R1A zEiDZ+jTmYDzwG6|%P&pT{pM(BDep`?+}5?X?!8*!>7c&i*VotUdp!dUCj9#STfEjH zvei^4T`7CPro6kKSzbkA<@7H~0 z&&!uDyY+tOvwvs5TkntkS9^WW_1LI={KxM+-)-8PzwY&;?dExh7p@Z3PmQ>K_rnK+ z=H})vU%wW(n}p z@OKu!fB$})V#hE2y5{cNzX}uY@bWL2HD{|{-IEi7esir-&!1WQ=hs zv*fyO?l!%#>C6sZUS1PZQ{CSO(`Kl#OU?TE_%8d8iVtRX?aabrnr6lAHCwIJS2HW} z2pe^8UD@h$R%i3$$rg=a^#aUXU1vqCuH2nDG5Yx9Djs zFE6Q@<=r{)>+9>&b@OX$YkT_oZXK85VG}=lT%jc)C8Y&4R%U*`W^<0zlh@bR7e6|} zS-tkI*P-AaJ{#1S^0v?RQEOhL!NbE-@bZ#s+1p#8U)~5hEqHBZo_FU#y8Z8&vTK7Y z{_r<%+-S(g&R)1PX3O^N;;ECSc)Sz;uJhq#$t>^bdQ;C>=1$Fsx;4eGNcmaXJ3gyJp?}<-r>(j>FZH)Z zMDsb_=xsNe&v~dE;;;YV{O!$6=kW09u}L>RP2XAf*DAi|qwB?-J)1WdJAOO$Q=;wQ z%gdkw_UX;^`Keb|g?@W*BWm5Rr~37o=kKlEawU3OPUMF3TeoiAvUTglyLWT-%WJ<} zbaz=CXk~5v*6OpcuyD-ovfj<<=Z$P__a@ZKZ8>=_DyioE-tTWxkG;LU{qs5N_a_#a zO!|G!HhgmT(WIIW2iaBA)2AKoOZ}&PXW8P#&D-x)osP?%-@j^=j734i%a@sc^K3d3 zCC;2X_o+MnPt)b){=$6ig;sORQr8^1GHunWP37<7zP-J@eMRcRWy{)j?Xs$7%aips zkZ|E}barOG|L2)`H=F*fWf!kqI|iC)di?6$yJy$dMwfl;z8+sc_wDWNx7Gf)wXxl+ z`RofC8&gwP4-{#YHqSFKFmNa>HLcq(f7xW_+_|bkoi`R1wj~CHhaV5$|Lbadkk0eG z^_??kN}ii*?QS%4OZM^k_Vx3&W?$Dk;S;90J6qc0m1Ujfo%1eQJ5zf0Y%NI=UE%pu zOCw;h=8nH{L5EV*W~=2&xaOV`?emnj`WQ1UL(TV)UQ6(fdGqswcg%Z#sU)p9a?wo_ z<7q2*Zphc@t5lEQx^DBXw)<~xd7U+~dG`Eqfknl`R&fbw>Bq-q+MaiJ(XL%m^FdAXGiS~Sbh^Bm zawV*A*7W$gollGpJ}uf<|G$onjjh8)Nu$fjMCvJM*#5)A!(~Tzr5I(3J6ur`uTaT-0q$#+6QAfYaZ?@UTV2YcXN$~NYd+T zYh^475-u_sEz5lLvBIL_gF@c+*~0366FgK5c$#0ne7We)r`D~vYVG9D+kEEH-}~j# zBIT6JCLgQzg61_RP85vpx%ATH@#Du5ZGoGP?aiE(b7MneUmxGPxV>5R+rBDXG08os zb1>_3Z+=a4zx}@%jg5@Ex$a-M5O8tZwZncKj@8xH?EG>mZ*Oh&p0<7N|KOM9JMKKO z{=7Y2$M$1V{e9l^SIs}i=he5>U#*?=@8*q1O7_#gzU@oi9dT=oJ`3M`{`trIrDnGH zEPHgwux!Qbh_r_ir^Nd-d0H=vv~CYu4=OkHv}M-aDUS#aJ`Bp|c7HxNS5;M=cv`g0 zbWOpt=!*QzS#Ig+>iK&<9-DM@zK(WGXsD>Tew@loA0rEkh)-1pJk13bXF#p#HeTsd zQFpAYtSUaAHUIhJaeuCRZ1#hA3BRmRpF4Z2&F$v*pSSycMlOe+mv^Gya&ec%Yp(na z=sHwf@w{r{>kpHDpWnoKZ+i)vAo#+|;|fNmA)3!O^pPNFI_C1tL)xq zU}%_lUSrJ?qtE>pXJzjBe9rnuja^k$)fcd9BKybEh;Do@#2u z?3+nWW33uy3&4Zhp z+t1E6H@31m)yggYruxdN=K&JEN-YOof4%j+`SQy(E7Dp@`6IrszyDr;=YMJQyeFro zYL{)@lY4ucsH=*H%6IO<$H#bu7i`_SbrD-zqCo%g>7iOdp`kK-?0VC=cUZfNpXRR5 z%Idm!G4S`XZAbQQ+&FR7Dy=zw&tt#Jce=cI|9-n{zl|KfudlC(iOG~bnQOm(U${`w z&E5TE+U9S+YNDfWFZ%j!ZLx)w)u}62rsPiA{^*&sl+>H{qd~_foqp<}BIKHCE3SP% zd!pa+w>2zI9Cz}*Pg2UCwXF7@qW%xN`RkWlPF9OwY}4wsYhyP?b}O-5UnKZ% zzxeqNHv{Y=o#PE=n>T7L?>>8V>l`V0#g*aDW{4M9+=#q=;N(WZv}D6CGiO>()SrLk z*y7BbwQHYWy27%#YF)$&P2G2TzRB*sw&BZ`ccdAA-gfePZo;KU{GYyWd-p6`R{BH6 zt*+;+8|=)%W1pazhLb5mhYfNV*R5W?c-O9~i%G^^iwfS}GW~bB{s+IxWKW;VDW9L6 zt$A*J|KQW2i5@Bvva+d3NlhVIsrQq=e*Jpo`gMKnKfxT1X0uZv6Ry@R=bjb?Mn`)m zCokT+K5T9EVYwAgFW){FujQpSxy)nL)_m{IKY!8}e$~+`og4l8Rng9j+}y46TZK;k zU9f+@{ElwPJ&QETL(jg6`@A8VFR!<^*TmFx>f8T$_KSDz+NBYbzp{2)(hqAZs~0a` zXjJ8IJlJn)%*Dc#dj4@yUus>D!0Nw2J7U%?T2nD~%f@fjE6;D*r>%XZP_^iT_}u$D zr);a=^P6Gz>UE!{Ute!9t7m<}yDEo9f zw1Or`s-w#{uihtUSbj&l_v_w@y>Yj0-O9+%*WZ11Z%upL`t48l7Hv-}^u3&sk+ERo z#*I&Yht)5g7rxZ)^y{x;mD|EM*-!n(TUkH<)~#Etzttz+@qbsv`!BI@e_r42x8L(l zUs(5~@J_CM`Q<#z+;{1nafeM-7VZ4G_uSeyYgRtLANTUviyH!wha&W=THW}#3HvyiJFvTjS=i`?9(7Jtt= z_S`L1p_5(;tZ|=Q5@)FSxhB>-8(U|++9}!{^#0q2pC1^SJ8$kZt{F1!1;#^tYIQuSvpc|?}C%ZtqmyS#n z4;J3OYSYS9ZC@8^)g|YO@0XE$@_8cTIzNBGvnd8Xixv9f4$RpZBgzySc!9^-V=u?m zw~oOgUn+zyn*@j&eqX#mvrG5gvJ>Akl|E$f&HBUm&2;Uft=ssvUA-n;^7Q(*`BNgR z6Ib5!5>D_`ne@9|q~fG-x9XQw0wpYmOIQxqB!8{SJMXqrVCE8asfFfZpmF<&rx(s< zvpd^(|KXC8&y^RyewzM^Gs<4_iLB`L42dV|1tjzl=pfdQnbBMn%pmbq^JPrPF>T$`ftQJ(+&?^P;(z zid`;c{z^;VAv*hETF$I}Hzh?BpK%>eIVr^^=^SrxSd7EObwOHb%uboL`xrQ#)2G^> z__a#P>d@7amem;_esWsO$>>U2zt>wu=;Yrg>xA!msPr3OTO)U1=?asoT9eA!H>>lN z*1kTnIr34}w2diED?_?G)Ld2ODjA=@vxb3j`Q)tSv!#}Iv-OMbzubEN;gRPBi!_q* zWbbdeexJ2m=I^KcyZ?Wbb-H}|0*a$6M)4_gyP7UPT^6+?^DFC{tv??v%KLfWXqTVT zbk%vM`PuT+AGs(^_E{R7((~xjmZqBAUs=;B&6mX@#q_%+r>6J}F8mnY$)2 zVakL|Uk!^;*7?sCC<~-nO~5U7&dRt}{=jc)YYMxvcV_V8hm3eHUHkT!~?vSEN^&nPA4vY3jcD z{Q0dJEylvh3>KoI>z4(-|9DAsU7B~O?(J2tGrs+NZ}G(T&1BF9f6z8ll{&~%jucDgzYX0o~;xXMrWhyAdLy|eGZDv3H z_J+s)&0aHoO+UK;zq#>!VI7Z>3Pi6gjbG!N*m-fw!s6t#S<=Rr|Ll0rW}WrwXYbb7 zlD5?=%d0P@^i_4b1Tlj4?CW-c_W4bItPJYudDcs>^EHFDdun`n9r8QN_}V%e~J%Qx)>`1eHNrB8oG7RG)jDj$2x9 z*2WpK`_8tVHF~Lklv`)qF28GHVQqQr>I;$ie0QFeTW{a6dK;rF$4)!_i5^{`O(jjvsgbv03}BjI8vg>o?@}S-KjgtSin~(GS|e|Ld0CzO8A@Tl4zoob`rm##NVd z9x6VTFdHueMw&2H$|J?1peIxd^LAaT_(nOoL zAWvS@a4OK1dKe#Xb9mvxeaB0#?fdcZ<+79d?rb|GXFq#imis*WZFXVyER&nwD*l$B zaMfY5urWF@zhwTCpcEmw&yl~AG@J}1=0E*k`_E!~`r<#1*UeVN9m%cTcvog)fp9PYGO6)^3_Ef5;*aufCDh0LeEV6kL7w-^ zrAPc9=Jxrmij_+_vd87q7e&^%Pe%$ft0rBZoNQA4_e%919o0q4GKEU4B4v3dUMeoW z`e0f{bEkjl1}h1rm9Lk4y)!BIz>%d{YxvquykIM|^=AF~zCcTBb#o%mr_TH%ldf?H zw=Xi9>-qU|WAq=d*HMS`B=z+t+T?EhR}TbfQ2&ODuQ zHq9ga#<44BYG&_bGMPFdl=pY>qn$afxiPD}jHaiW%|7{j{d$Jwt0tXz`Xlnc?mGA7 zKkajGInF(1HeC;dOvl9VNiymK=xzpq7 z`LsxME2o7_%iX-Zqo1B8X=gsZYLaCmmv?koXX<9rt%ccASM~V!Zq&H^vIK1P)YE6* z-rz}BKW5-Nxqefwe%|zMlEv*N^6NYb``v*PZZI@n`g|l=oEW zHvw%4fBdIk+^2QL$nA_v-bDHg9Sxc>-D%MFKF=4uxecJb zmeW9L;%2DXzuxcq{Yr(aU(z$7_mayx^CZfrKYYt5Haqg%zOYMM87}QQ7k~dvqK2Ke z?^mId!W}*(%9S|*Eeq<+jD@z?aD?gfh3E>&X`o! zgowr@V{g&Ejkg*OEYx_jHh;;frw>nkQ3+R?TY~D6+Qd8EfmKeQTe;kXJZS;63F`1cn^^di@vsV`F zoav?dbN0fzOS|Uz=SMp_im!jQKv_a9SK_pm*=0eV$;LiHRb?m5mSsM=W^ylf?>zBY zQ6DVl@ElG4^YZEEm_DCrH_!ihkeaK};~d(t>~dDZW=H0#Oj7nVUgkKUfKtkXO6=hSao_gvg1 z)xL-cwA;Aqb(Ws1n#c54hfNGaCZD;Jpt|+OzL{#%m#hDLdV(Q7JNoy7{SlomMfXA3 zP$z&zMo#w0g#h`Pu3RT(By@j!dLrdyk}+Gr+amoHQqM1%?Aw@Tdwr4Mz5Q346K8eT z`5kz8V728ABU{f+hmwrte3c&*6qQyLT}(1&``KhzUwv2%v~{j~_t6}+!&6_boYY#l zCdoMNtmFAFA$BXzD*awMTg^V8x?PjgwFFDrd(FhKV_x1%kn+D za5K~M?S~E-s@4fGZC~~2_T1`%;Yn-Mk=})u~1m!F-8CmJ5i%WEvI?|2*yyjlcs3lZ+`Aqe>@SS(o@P}^Rzi_7b ztSFA%cUWZgY-Y7N2a26(;=QbrEum6uxy)Eo>7__)sA-L*(TTG&GnW}_e)9fT`T9`) zn%gJt-`~R>&sq3CUT@wr{VKglhCQOZ@!|(MKMEe3%sH#3F48zosgenlI+h4N@x65X zSldP4V%PNPP8?~^UE&0r{^uM{lQ}gvbhXpO|0mx{uldCK!|QZbTPyYApMPF_zqiyysqzFUFfMT@O8A{As!jW~&!R`8Sn2=! z^(B|zeE$4u)i+k9|NYzT4||`@%~`i9ajO0XrPo)SI1c%V?|vdx!0FLHd$X=^yFx%Z{&q~kI#UG*!MoRng_p01R_^LO7lEm4JeXFuA0Ca|7VXP|9xLAe&WTkT753%mIRfV2^NCWw?0yxqz-?PqMbjEThDtKe(U-;dfq^KL0`|CGvGYqR$3 zPP1M6Pr>%0rqX52-4o6vJwGtRn*CW!#r>cePuN4$3be&kC!Q88u#oFq%GK#oR1OYo zpY%&ts%v+c@a~-?xh7MnY-c7vi|3O~DJ6?@Y{ebpIBI7#-@mnCW^DhA9nl(28YyvoZCSmsMtY+P`t0>Y?(z1GJX6OCeq2Nbyb4_cslEqy8}dJGkxcm7RO} ze>+U~-Fo`0eBM3J?(_Q}ofc<0A<4G+rjCtR8E>ac5FhA>8sUlkp8d~@wC`eTwOoCPOeuh00sda?4K)ARp!L{EOK z;q>s@6P6ht*6;f%yT|Xj?OSi9*$?Y~997?Qzm_dD@I|kWk(G3EY_9Fxue{#d@;|Wu zc(zMVucnYgS;XaH*t0S--wV7e-w2(2+-GvpTP`!q=>M+7S!?)1Kb~5(t?PCPKd*tz zT({7W+m&xi{^zCzovgk$e`d-gW9HfwT}?_NA+D(zYNuK!9_;%4K(qt8a>xv9y00O`KE&H>_UTi zq6VGSbNr%Kt$McU+|&M>uS+h)ELs&~`Kp$YFYiXjq;nHy&x;eBD9#b$YA2;K>9}mA zxVK!S$ifFj>Pbj6rdomX1V)tKITZnuk4 zWd$g@7k6eZ;%xMfmV9o=d?O^*dA0q9HDz+`&GjEny{=iBZ5;eD(kkoqixq35Hy*C3 zJ?wVv+LglWtfr{>^VhYx-8pUZ>|259?b%ThJ+V$pE96!j(a;OTOJPba=>j=x7u2ZiZ zI$rwB(whGJUH939N5vL5!|(su!2f#R#@#zxb4z)T7-hByScpuWHK*?(f57{-IhKy1 zO&cyB%`tP{dt3YYd!dubeKF0`Z8lkiPL10Bd*1JjpDu6zF)e*w{hSQ$*31v=pBL${ za5$PxRWmkjo_Sfj&((mj|Jj0bk3qv4HqXFCiYw!vS(6tY(W%`iv8%HDXd2JuB^O^G zUTC-Tapd&FjgLwSYSW}#HGU=;^Rb(!FLG(^6#JkYvUJVkIiWerZoXc8Rs6ux72tCo z&VV|YDTa+<^$hEuIdBAM&1pZUZZ&Z#U~ZZhe(+~tcG12xCftwe^_t+ZmliNq&)km#}u(jVUFD~B`i)w-}ENh zFxvd|w>_EM%T&^~`eT;V#y5AT8kjc5-+8le{r2rSe@m7x5-Qs{!$);G-|^YEIolTn zz5Qk|(`%A3@0yvL_Xc*(y(%)*s=vK6q-#y0!jU^hhyJ|fW;0o}DB%4^qp5xqua&wy zOh`W`qt9|@#;jB7%V$={i_CiY{Fk^>LRzuJNtLeS%^Dt_*|XHL|2Q7f3$LqnQJQFT z&7-lNN5E-;h)oCY;T`IezdmkZEMajnsj4}XqIC387$c}GwwK)FTU9aTPDx4Q`x%@p zGJIj|&M|ZQCR{w-GWG0Ujg}L$H_P&Y&fYSzb-i^VccK_;QEJ=fbul}WxPQ+w&5qb#S9|ia z)-#(GiuM8(cMN?eOKoqTyY*{KuiLv_@@HSV#9noA3;e7U|3k82*{Y1LCg;FYvz9;a zUcYQr#;PvoP{S6}YJG#=+saI@OA8`$O+;PXuIx2kFZIp+*Mz)L|-w_saWkk;t$+>viUL_MKL?TXVRBE>`a1 zo~W;VVB2)7a-X+PkL-0={(etv;|}iATF#FDbrjzn&DH#)`Qr8_iy4-(u1_lVC5k#O z51#d0b&`5Zs{i{ueq=Cnd&3hS5cH7$A|M2A5tLYx|+}>63?kiYZZphWD zI@9OU<;#Z;9AE%#KLedP!FPP}?ASMsj|(h5@g)?WR=9TGpJ|oAo}0U7eGY!i+Lxxf zq3i6AwMoWRAJ1$pxco{=sQIwUGNp=;-m3oo6g~HIGbX>T?8|JGd#%RQzwF|Ruak{R zjM@y(|Lkqr`ri8WBfZeY7sVBpN-ydCvrn{j>r?xrcVBC)m#^A*O5DNR_t8!rSL5$Z zZ@F$;i}s4u3n;Q|ZGFD_f|SAa{=`{(_EqWTHcptc*IUJ3;QJPb>{)7!4h?^Qe}7!I zTl|brX{@?fx1oi_i3=A5R)%o7270JX25ow4Ih^?I&CSmd=k?@8TLg0M?wb1VUtL>! z`^TR(Z|?7x??3+8WllU~S*pWfEj#IV`tsuWvv!1tE?@O1Z0pXqr*c+jSwu$*aBR65 z2|A!;)lW0k*6j~WKOHTQS=AJLRp5}uy>$m3uDihVKE!lRSCevlICSX&u&3TMKK)p6 zV|%{*!Gz4`GKUR3)F$8AQ#sk+{_m8B4-;DimIP_`$=mydhl|(!-)dNT8&|A<<`5j3cgAP3R)@d%=i4G{PVYA5UB0SF z*Q!?CXI;WI`~Sth2i9g;giM{6p)#L)LCAg9Ype{*r{xzXtDOo_3!QM4uiBxJfqC-w z>p2T_1txu8;PR(nD56Gmz`3-nvz7Pu16~sOae2#`7sgK0ZDnSK7BeTd(0&cefyqVWCFX z-&=Q+zGhd>e!Z(NbC&k4?OV@lUwxnSJVR5y^U&kD?-T9o>l>@@-9L2R*jG3?Z*SGk z+F1?Z#UI|9O0Osl-}WUS^ZlyJjw_b3riZTB^8Du?i|>a_s}d`ZO#hIfKO?ex(cY*f z@0%p|r*6~#bSYk7R=CaQ{ld}tPnX31sJxc&VCyy0H|z3u~%uS1z&0D{1 z-9!7B{Wbp2bMAFI7FraTm@Rl^Rh6^tgM$~xhSX1bWs9^$rS0uS)>-7NYhtnF4mvkY z{JWC--k*!|@|oK>b^i(6e|e`TI{N3C(rXO&cBL<0zP$7J;~C2$OU|2_ndw!XKes>b zZl3wuw{J6Mxf#t&DJYom@L{6TM2?w0Y|~G>mX?~X4qLlu=g!KT=a~;1bhs$-@bG{R z%{g<1=Vp%B_S?P+0vqPDA517X`P*!w2j~zCew$aXUcLEkDA#}c`;jkSOxhA3eEtdA z=Dl`p@2*`|(SKAY?S9&r?x?By?5SRox61l>lw%MQ-!xw^sj-vKzP>K?|G&Rmwr&mG zBLA^sPVjQS!e?hB)zs8Zq!@8K{YE*8Aw;Ig%*5mf=%|XSuUZ=;dSdj#udF}(aKW-= zZLzz{HkQA?SAC8*;poBo4+q)5`Mf+T9{;4*{GP_E>jpfaJ+?EA(*>Lac-VgKz5m;` zMF4bs!@GBR(JkD+x5=&1-}^;KR#sN@t-sdPMZ0&;o~-U~WMkuFHCOMJcxdRys=c5k zO3&xl&-?c6TUX!Jm8<^zx~13G%X?Y4_fD|ltR$Z)I|Zy-9OifG>K*uYdeMR`>F%!T zk8AdAdQP%c6^~o6b7$qr>eEsR^KB|8y?d8eeZb&<<%6P~D^{;getvH5 zn_F8~zp2%3Nyy3R0UcwNV$_*AYtNTU-g|z%S{<{mW+rHXm4U$qh0GbtGFuKOf>vPt z`}n4Bd>TkX^p2-8+X*+%YAJxs9H>alm!gf5u z^F?Z}R<6{v`}4tBFJ^~>-gIqisq-mDJiNSyR#vAjTo8!=`zoBX{c!g=3%!^f2h``+ z1eL7$yzvuf&6kVrJv}`iu0`hyaK z_56D9qRyg!e=6D7*(+bK-F{gh?xGiRt}Hz(Gm9~S#}bmn4}uXke8w`+goU6dhp zuFYzb(}LqkQ$!PPJMa6DE?Bs~xQ*+dj{ZCDBfB%i;^Xdc&t>es9rc`lUT)OU?bq{T zK1MFeoCP{y!Qbv@%gU9XJUe*~D~NR$zPn@j>-TTViVq66x998ctks*odHuqhHzWP# zSRDNL_;{y_*fKr6EmCvsYG+NHCB5Zx!-WOl33=9tV*Z&I6ySFDZKRoMqiB<0T zJ->hb`td4!zgKAJ)uvqCYzMW;8`-%oW=uJ6|9=h$SJ=cA+4CGuC@Pu+TA}QsBzUjx zckWCHpQ7tF0gdn9zFoS0{rS2Z?9GnVzIArjPW%NOVQ_XWjw1|soH*3f)qlQN+|SW| zcunl?ZBKfqdoBI(WU{|S{l7hbXWf3gZ0F9Iw{Atv@l*d;G3VjK#AC;g=N_~ve<$O& zd~*4{%Hw`}t@pow_b#vQ%VK#$W8>ugVX?8k#l^;r4hl!#?)~rXH&)Y5D zxpQWKM#!f>$C3=|K24r~;qKks-_bi_^k&VTy>j(x=g`na7gyx&+PZH$Xqk3_Ma;KZ z8i!0QZ|s^CEWCKn_1n%*SHJa6W^%e*aOHXvgRNM+!hv#|AJ1oB_`H7VoSowP!hcj* z7V1k0-?02VP3*hVWjog0mgPCUcJ}r8uk~K--6uMCFSp{0moFb4=C@BtOFQ=M{fd<< z7p`2{nPAYd?zNAvuc3uShW=((B|%Vj0OcVm?y&!xH*fy)NPNG-T)*4bw!V6mHOEiA z)1~R-$HHaHm%n|TS5Pov{+}o6phaX0mn~cNwuV1WhMO+td-#%S`-;X9AHRdOWKhCTFSNZMDO=krGhs!2D zmsKX81i7ePzV5`^+uH-dXMcbif6>vmnGT9Lfp%$ucQ53HhKjQD%PrZwS$O*C)CTGt*t^fGQweH{N`NF+!hCI!a)%`(RRIRM78al6p6Zl@KR=;t}4)NK4xaWL+ zW_Muasy}Pjt+ly)Vb7yS^L*4SYknA9%(zk}c6(zo`}#efxE>bV*e7m0lc%<}w&u^r z6fS@HGLs=HI=b@XQSp~AUL1H@q}ZY``z+|l2+)C3`T6VrMwOSF-`<`- zeRIXh*uwKn^G~MisQX)WlKa9g&Aj{jeEt3TH%9QBu;qhG| zOW(^YR;_xp^?IE0T)*NcCj=RSR*Fp52OWvBI^)BHvuQs+oz@2}yp{h0+P2kee(%8B zvfIB`uhOd9KOeLQKF93z`MPh$C9&#q{l&k&WLjBS$yGE;ARTA0T;;XRju^eT%BP}9 z8zqkS%Ws#r`TO_pn;RP!p9or@!DXI*@5z&slfQkucJ11yuj}jQ_Vx8Gs*~koUmdpg z(dziWS{E~>?AvF@ur7ADTYP-HE5FZSlSpBn&)?18KI!+DO|zLA^Ya9Y@6o{U$@5R1 zKemzO23MzcS;!Evm^EwGqW72HmR-Jiv-9%HC%fhgw!obw59)R`1@m%j#a` zbJ?h*c1O4&4;5$Z~so{_{IKi@$Ah&Ck~t78W)!y}N(I(x9CC`{shW=>-<2<~6Td zr+03yb$WD&n!5VQ6eCwjp3OIP($CG=$R63X=);@M=QpLDoi!;bSAxyd)b!20y|WiC zR4j>ApXdQ9@UE?kjh+}Wd)Y~EU)?`-*UkPd=U(plWRrGXUCXkEE7r2IxY-=6d0q4L zQQhb9 zdNs?)ck;GvX1jLnvZ(s9qV~SOkI#m(x3@OUU${!^_xJbP*?X4$Dz%V#bASK**VotI zt~6V=eEH70zf~9arrPemleaxr@Xn@GZeL$t)%D@pLK%;b_0F-czc=q_e_!9ElPO|b zt!6Is)DU^};9#?fx%u?kK2vk^%~@BqZgWlYTRwBvtWE4PLMMxMJYTsebJn(>yJT9^ zHokfV>XLq}uu1)0Y&CZe==6fZwQ=jWzv(%*Yvt-ys~W7&X{`*&I=Jog_2p}C7F+Th zUa)W9yzTevZofPK`m31eDMOy-?Ca}9Et&FeZd$tJv+qMYb|yx%oEr(;H9BI+2?-07 z*599cXqM#^sSc5MtDnwJXf<6Hv{Fgk?zLjo=V!jVeSLj-XPf8870F*)8||(kl2lYA zblK$P>(|Oty+U)Qg|t4nEjzbI`$DPvtre=`ETy| zSAY2TboTuGe|{=LPl|SKTA0fhVj zyC!OD*2P?%{Y>1k*S+nIt4#9Rk=__C!rFg)^NRLrv)QV%&z@gXRb34l+PuClcF&(r zr&H5Ee%||fZDjt+^Bd!|OG-3N9*ggLR${es{rcy(wq|ded~-M^?xFa_qZudg}bS{idfi|HM6S7vSaP zy}8Wwut7$4_U6Zb#H%ydnjK%gdNpIlj1BBuA1n4GZBDUc>^ml3lB>Mx<2aKr(G4uNC z+1BeKr)@vP*naq-hV^H?(z9A!O_yKl>`Gt566T#awI+_|b!Ak=nyS|88(fU$SL>Mx zbG82XwtfFzg}BRACm@GJymccWq6ib8xV*-Td=%XQxZ) zh)g<}l9OL>!rmk;Ep12H+gppw_X!*_I~sXAGVL7uUcPwF*5})<6uK%2TG!o^viS1t zo8I1OcJFIn*GwxqpmV!DGw5m2=lGnPNBw{0U9z@rzW-2V^(&pHiTShC>e`w6mpv@E zDAc=l?BS7&A|d(t2PU3-5M;WCkA2ocv9*=AMenWoY*zkm&aAfR%OA4*L>7u$-hIlt zJ7C4uy5bW%HH3L-4?4h`62Rg4$8C7iap(>f2U5f(VVB* zH2d0zQ`+l6hd)I>Iez@Oq`drd&>25Ho3?J9x^0_T*xIPn^J?C*$wo7`oXrT+IA8sK zZ}`QZo0S;Xw7Xi(b-Vs`k!r(nM(t&Hz2tRT9=y4^xum>2{EFT5)1phc_DOGEk~ynI zM(JA-yQB;Hru^LB2R*WByJ&99Yi zT)ppQUF};}i@APrbw5*+HcDJyAHRM5bI{38moEoT*}Gxw+TLy3%(AkwynbCF>XNI$&wqi>VQN-%UIIa^G~DTWe036t0}O zqqMoStnAaD&*wismj8dlFSz>I8A-F;TU!)Njv1}|`$&BMgF~&{Dngx)WUrf;_-YG% zDLNVZEZMUD>a}Y{UtR>}-QDGCG?QgR^8Vw;kMFGcX(ZR*{_I&AXcN@4v$MVVQgj0K z?)3lsy<6g%VT502|6|^`Q_mCY)HZmXYrJ~>uH6S;pR7A_`}U?Wo2M`Px#7PUTlf0I z&#&KqFS6idc(b{9Zo(?9s8CVAg^7OtVmWi;ySHCI%01_`&$DYkG&TH~xt*@9O)KAa znqT1El*==puT9^6et-H5wXJdb`?n7yK;v+OzqU6zuHr;oa)N_|L600NlD4b38Ma& zx3D(-+i`FCq~+c0dF=0Ay?XTF;o+YCe&v>gii#O0PkMqHQ@LwOZ}8jwXaJr5xc+C2 zT~u^*W^S&k1kWwQRo8wm_n-glP%F1+?>bReP_Fs*tt{{UzOz?XhrfLLR#m7|#Bze_ zWY2Z+`~C9s_3z%jyXW7p*J~m-r#b$*erUfeAG_WB{F{o=ZHWRLEMYcRUOO7pOCJ8U zZ(3Kn{?-1&4>e|5ED7SdyyV{Fr~PGblphp)I(#oXh3nMLbs}BM9<5#F*zMCWb$8qp zfBWqAeceALKXBRHe|~hQxc{~1?KcBW8aA}4zCINCXL9Us`4u~&XDsuyka-5`EB*fV z_RE(q1%H2)wzaqCUf!mHV$sHBYs-Y~6bEWYDwZqe0+<7H%-!qzFI>1$iRHZ6x<~TwYT9H=d->l=ZPF<#EiJur_3Fm-^Kzh* zmLw$;OG-dJgSNJ|8FSC_ccrDJfsXvUP&)T|Ty^fzKmKc;>i^z7t9XKTOtHMAB$%Xi-e9oO~d=H~VQjS!>2Yd3C4%&+}+)7f&n(8;2T1x)#Of3$8kK5QT+V%F)l zIA%}9MvcpNZs+gMeLh)ltIUQwUiq!RQ{GO$|6W~y6Rxyz69hK6#oFnzl}ZF;SS$S&=Lah=5p zGt}zNo_t@PyHr`=(ft_9<5OR1CTMQo{ZuhTR9D8OziFbEzS6sEIyL|Jbfzvm_iB-W z;W^nta~}?ixqEh(G1qTMRX@A$K<(#g@n3AF6!H~Y%`HySG^nSyxA4o0z;ko0 z+t!_r5WpP&D;MsQQ8+$9s=sHi9}wRVlG|AUrXez}OT_4LzC_WKJS zEYRRu7|>Ds``g2>RX-2%*Etx?+#-DG$m7C84>#O(R}e@jD!OF$u(zk@&ej9=>mrQh zvU&IJ-#@>^>TiHDlaG&yV#VT8)7MUk|MomrvVXn*WRBVHO`9u2U3pm@j~HCa^h#SX z`*Gj)zq0H*RWG0Ks@z;x%^SaALGL-@5i_C;!rT?Ss44uYDaGdUer^Plu{c zukwkAj`p^i3n~h>6~B6Wd%N~IuI&=j9OH~cu=(S&Hl9}*TZ;rr=HcG`oUAF zQl|0&=%A5Y?uDY$?e<$mD^1*R*?HEo&f||Gi(>DtnzT(s-EYo@!xbW~51&qtkDCy_ zZ-f7d=gabPa-KXpJG<=M+X$Vs_uFs3UAA=T(Q}G(ebhi@HE6v2Cv(K{O8Y~3aw1_q zo6kSr{CEMVbN=Fm#@e-OMePmVAGb94Y<=QZ!i@jF`)c2;&3 zC$+3II`n7M@2G!IZchJsmp6LF_aD!U?yu!>3Jqkg6WKod=gai7KcD^NN_!V3B%OUN ze?os@{kok~|Nq(<{$ugp^Bc`VOvPD0e7In8cHIs$3yTw9UtfPK^Vxgr?D+Eu8_jmc z^nLvJ(UtX@mwN1uGtWz`KxZ39$!*aJiQ1oTBw76_@YC+Ox1YVaxmiW1)BRE0)>oM} zT)&knzeET&Zr9sXWPk1OzvuSQrEy8B`<}{^X_c;t@HKr_3Ou9yjXF!+(1G_ zf=BG@?y9d@*VaadtK`Yo|1orSc6R-3vwzO(v$M@B|Ns5|^Q3>>q5irri@W5y`nCL) zPmbMPcJu!7%vqqd2FmVz6V9f2&trED^iUB3ogO~_-%O6g&&Qz3xEzlk`t@BHOd$a;pE8d#`!FC*+t;{Dy%18I_GU zLxNAszpd@Ful&f%w{88=wX673uf>)4`3kSUe)`qb)o-io%F3pJ&MEg$IaT-mWXdE^ z{XEUwHSkT*p{E;8c|@1WpZfaysCfL1*7-b#7c5-3P@}$f<(3U$x~+$l!dw3ydt7Mu z`;Bp*oNd&Ulb;eJ?yfm_Y^ruR=*XMq=H~vjU;T^z??@M}wVM0v(o*kK-@$7s`t+YRsKGmxYOu=Ws$&Qd9Qzb%o#F zMD7+@+?puC?elG#Waa5;x-ynUEUSHU%bKln=HkdJU=EFxvyXROGHhHKtWzPEY<;#Zj z^K#b9|2=NYoV6x?|Gc}q%Rw_)w>tg!WUY=|xgxT={Qa_J%Z}AGL}xEh_nWg|`SRoQ z=Em-?n|pS)`TgrDN}xq{2iM(V+jYpa#J{&VFKf>2jgoA_n}5oDOIj&n`Bbp0N$SJm z^T!O0XC|F5e?5QE)`~aZbqmiQKReqZw6`?$s?3{+^WM5|xBPNesCaQGu47pyXq*ZZ zHIcIZbFEx$S?h*b^BxW%94_rK!c+1?pW^bJ^V1?{k^^1HAx|%TaDjGF8c#Idik*YzX^M*zhAm? zrDa*BiV!Co8yje9&OHC#mgJYZYcyuFd+JSpd|{!pjBVAF7(I6jnP=JYzi%p;EW`f@I?!Ujk8ygrns0cX*iY&UQ zF~8;$XFI?Aw$tmQB%kmccDQU3qc{ESi^Z?4HgDd{{j1DEX3vL1+&#U$H;uoaJn0Eq z+5|s>W8&$jGiJ_IloM&z5Il_JOcWFH@D}IH^9r;Lnf3Lk|u3+M7ePj$XME68KSLwbPw%Z*PmA*|zp~ zFGOrDuvfy?_RYk1$22v%U_uf6*gtPyeEG1h_*exo3G=3ulV`7 zi9Tw9BCQ)Y8lIbPfBx3iZ0%E(`ju8Yv>Wxmbi``;S0_p(=#!$Jb8Hy}fPfoULMQYFG&5NI_?Kc?Rv=nJwdv$Bhx>XNv%g&uMQ{w579~{${ zsn7IS>hWw-W#zotiN8(R8QYt)w$D!S)3&~CemC!n3zO~KgG)1i%;7tvXU^xl-Cq8{ z!c{CWde>T3-Mo6?$iHP`*Fr-dUzRCr-Q!;QM!f};HfJn5dH>JZ`!^;Uyeye@@nWEx zn_JhmtsL!#L2X;mkeIhvMv;XK=x&DF+w+Z$j1FD7A`%=Ntol1tq}6w}*~eqj`BO^g z74M7z&1R<Gab@J9o~st^T&5@^hM-yZg?9hfTT~CsQV+80}mzqjzf0v)T9ms69VFfBEj+ zvo~%uy!3L5t;L2kE|fz*KK}me{qFm-v$H{Cac^#Jj@egpvrK3Un^Rauf`LTJ-(O$1 zS#AJLp$2HA$XD+S+C4Q#9ekQc=)F09>8_EFTLf5|9e+!JrWD`b@2anVy>@%V_B`42 z^YfPO-@pIG@6Fdt@8+5J_V$8i?sTKKt*KDk{4{CiUg^Dd_wrsp_&V*X&+^N6=JgiU zrp=nYQDb|0`V6n-CnVWE#_+W-YD%*_GPQ4`xe5Et3YpOBo==J_LVBZ?cQ3f?TCCvV zzGGJD!>nuJS1-Ky{*OwSBq5_7aK!wk2sRFQR#fKYhZzeOxd~4*xh9p zuU*@=WJh#KX=&ku1B{?j%iq8MZhhjs-dC@(K=VTP_Evj`t=Y2S)c$?H-|gPAZCeqj zf_pVb)NXmERHH*fueAB6GsfowqNB6hA4LAnnQEo?=K8MVYoA!z%|BnaUglcS{L=cU zla+W^0%OcO*x)qg`ojtv_ zPF^Kx-G}bXSt?&;Hs91?{wT3M$@s{o%)FzEe3ne|S$f&!lIEtieGB&9f4Jn-!zFHk zk#G4Qm04tPw?#;=TXya7(bol!LZ&qB`O$A4W)W#31zPSJp_Ar*$U=tCX`#T^uV24y z3f=kMcKYd$&GLU5Kt)2*_t*RXz0SG2>*_iy3mY3B9Wm$Yr6#`0B3u{m-0?AxSW|H! zA#&2Aj~0gyANKL}y{Y!oLBXN8*f=!w?$xLc1_mY{PZ!6K&~JNZc`fZq`{B2|_+W$8 z^wSePRJyz_>xD(#R+{T4{*()J^tj8tUF+G{*gB3sHZd^?h>q4y|0u+8d)Jnn#`fLD ztao2G?~Ylz*eNayYUV_*DHyD+7ub^D+Gre!N4v_JVbEtqZ_|Gnbxbm1SMqsffl zc+KPfzH7g?ZTgyAudmy$m_7Zrc#>JG<|g^+)gDFvH?sU&cm099$<(+5-zQ0ZtGl{1 zuCq@2?c=(+i>ouYPkR0^Fts znZIk-pKP(7bhM(v(nWQWI!E*_d0Y1KPM1$pJ{>T+>8T>r>7rCw;L$kULq(|b38xF! z9v7vF9xDC{MZ9rJ6KyUq4C=trLu4PWktL?HnG%Y(7b21_lPz64!{5 zl*E!$tK_28#FA77BLhPVT?0d1BeM`gLn~8bD`QJ-0|P4qgP_^DW+)nR^HVa@DsgL2 UK6bT>fq{X+)78&qol`;+0Q30E8vp*7#JE}Fff!FFfhDIU|_JC!N4G1FlSew4FdxMQnq6`d-7a91NJJ)~&3p^r0f(Z-^3<(S$vYpN|Ffg!}c>21s zKVp>PHZxz55&9FPuB^Z@oq>T#fq{Yf2!nF!Z|{DeEOUJ7gOC1U z<^JC*8d}U*5;I(wCn=>WL`}SOYR;C`-4?k=j^3SLF8KTO+KXCS+&#U!$~@OD5#70k zd%}l%{jW2lEA2~slVkYX4m32}_&QfUZJE|;Eo1HYS<~O|<(ltqX=z#ORJ7Z8!sN-H zp9Z$f-EJ*zX=%BAj_@ZzJN4!3Pq5mlFYl)vsd4We&llsB+d|Y=Bq#+6g!$Z`miDrF zfx=F|UvuUheZo{9e1`K*uM1Oe)~YP2AlKC9Hl8&T>`Yg$TBfPBQo>}){(U{OEBXJr z-bO>lNfBO&y?(!TNrwA&x4ArB z@3Z#4%=LSR?d*3qe!mc}qy6C^=kajePZ!my_Y2u(8@#>+CAgA1jx? z3EPnx`N!yb`upE=x2<@V|Gam>b*s&-l^i#HUbtEBp0-Q!@~j|dsaub_zGtmkIZKH3 zu++{2>37~#zfiC8dAa!8q~yIU>ubGT4NDz)S!YkpD@XIebpt#^)AZfoBQMKYw~%e>fUxF z9QnWPryCCY8O~*Y z<;4x2)JsLHR4k3o+8-S3`bpI%A(-ELj?uG=tXaFKt(q3{c-r#^-Cc$IlFt52`ZD7Z zzs@Zst|YZ9or#ORXWd+zQ#SYN&FRLAcq{dzu3D$b<#kGnZR*r8fnU>R?P}ZR5*Qh|bM~%< z%NA;eo_HsA<%;RnK!Gi$#+&CeHn)XV*<>cpte#%@@9OG&#dw{!;%jdfFki2k8d-dJ zw!(^;ri)WgEa1mbMONK>eCp`+ zGk@=&jDEgf#o8@8_Ql7RzI}<;wropm)|wP*zInykG_T`3mQCfXIl4h`aa(9r-(Fqg zWoI(Hc%%%|7Ti7}z07C!)gYrB7e~jdQzUt1)w@U=%Mr_H>Ej+d^e|E znc5>UXZr`vMUk7+4AyLN`q{i-<+3!lq=-n@jnA8O7c5)2YTcBjacdda-@du)dNg`R z|Ci}Uq_}Ggo(9jiSgxiewejxZZ|AZla^G7hO}zE=CTl;xUbX7&%XNqT#uYkVD2r>_ zHGQh9>iJ#iGsWe6ey)|gD5kGxu%9FC&#{&Vn*91Zj6TjRUs7@B@uCMC%9kv1w^g-x z61y@XNcy0ou*coUJ6%P^&-^;#er~4d)Sla~KZa^c-j+%WxEyqS8duk;w%+u|h58fo zSI;t8xhx^X;70AEn$7bVb6F41FwMNQCd@3${HluE77-r(*|)cCH(vU(U$pj_@^YVS zZHY~1>%MDtHhJ3>oKWTEk=TB`E_zz7qUrqKM=~!*X-IB9?EEhF+6~PePm<^FT%sn* zv+K4U|IX^rwm{Q-tJaL>=RMCK?=cno@nXV#%XK$pHPz;PX;iP-zWRms%}2b!P08DS z-P$w#!nf*r5!c(f6RqCNIC3Xs-&13k)c)@?=JD@%VQ{YM(lXZI3&H=^s7V$4Z(>?* z_qlPw6HZ5wn6I7NuA~(lJ%01WwVAi#iv(9H#F~iB`?$>C^o+0KdW&bt^;NHVT=DU(YCTC|Ag*v+?1w^07`MS(xlWDDY zu;-#VwwanMh3$mad~W&wW!}v^$>nptuFS6tJb65>)atd^%)*<2^1tS}PuJ-|ZiJztY#_b*^=9iTX$PzwAwp`ZscJHEe3h$&%;c zSX9)%GSVbt`{E|a<>EbSS1P{}y68Fi^Rc#sYo3l9&h_Q#Oc=-%^GQuuwKDnG{+VCQyN`Ub|9fhQO16LJv|S-3m-Ls& z#V&MfZZrQgds@Pke%nd;$!Tk59$;_Z^Jdk8jQQ@lK?X02-_I1aGtP@PO%K^DUM?o8AF1w@^H$NRvwF%^)488+?5PQr6=XfwB5ZbM zchx0V$1Sxd-IQH+|K$%XxU&0J5dSYLiRdjNmg=Efu3x{SYWQ?P|5a7JUyE~BM_8AY zJfB-y9q1T+w1^K>py)|juv}J9JmX)PFyL=_Ye6>c(>gNZ2 z`~zYFHD38?-F)u&S~NuVd*kY+Y33?wp*An-9?$r)(?3LYUDb8lyH7(6o<7aFw(aQT zGRezj_m1BSKki;ul^3^m-q%&0okwcF-CmG$%(K48FX@oB+4mSDR^9w+XJO}k^WW}% zvLTI0-lyWKXlK*%y%W~5mscDx+aGfE6MOt=x9b^qKA)R??Njw{BgVS>ijG^(8nd~u z{$_E!I^V_d;@`+qan&pr&0fzDo@YvApcd&n`%q-k3VY?c!ZC zg^S;+3(BueJfa$WzUz7I?SgOrmCdR`qYG|K`MO%-u`qMn8sk-q6il-ZiC&(SHicKx zaMzl-iEloBKUMN-bJCR+j-rR#c*?KV?p>!@eUQ=luF<{s^J9e0BwX;`clGU*Z?~Vu zUtv8SJLmb-4-?O;F1q{g*fyQx(r=8PndauoS8TF=+-+A9r}S&q{8ZVZ4cF!VznF5w zQvTKz<@LGmt9+SL^{c!T=btN&+`RTWzs&RwxlfB0=A7|T{dT+mL6h_6p5phPr??ax zw4NpOeD7jSt?G5V|Lxo6eMfO`A7_?HlKV1;T}JO;1YY$yE+b=p`K;?JA2YT$Z*I0O zIsGiL@YSMpoAed44>R6B;Wl4WJj`&{na-4LQ`8iK! z34h<6v-L1vbdJF_XZQNIvyR(lZ-_qjpw7RnRHxs1!5!Q1Pc!ZYIbOY4dAo((bcxrz zZMv(EYlMFCo$Y!sReZPqYHP(Vt$m4WW}EFgzS%1%xBcq0vxQ&W*RM!C@Oj_b8Qs5a zuDLvDmXy!xY^$xFw`|6Wl>Lf*7@T=depsAz1ipNYlMGl++5lrxqS07 zfh#f-x#Ucmy#3Bv7YB*tZh5%OOFsAOTGq2#Qkz0Ww)m`n#K)R?_}HD?#k^)W5+?Ir zlMI}cwPI#yytDUVmy-(&rE|94HIrUzUNl#*?f^5J*i7ZOL1M1Xa`6RvQd@!@ZlBrn zrT@o7`Ckj~cBiQw-qz8yth4fNq4Mh46J59Nyhkiain(!-y=xZ}U( zZ=8R9zA~^^~N-jk-Uw&YH;AEEG5^m%-`cbGtl$m1(VMQrd}c@1n(8gaYwLqcjzk)3RKHtxH~HPBlc5JSH@mqUG`xJ{MO?bz zeg^0~_nw$yYPA1P=J%L0WeU&E)I8rKvTka@0Xg~ZTlYe?-;X`zwCs*d)TF9)*ZM1O zwkUjDGX0Z);^_@;O!iAvCtAFDKYisBd*{W9LHm{B=ixbHG()7?K z?tsyn&I9q$E9aIZ&HP*(_F*ln`{J*0AL`GhbT_77Phb*z6B20rdW&t-YQKe#;tq<< zmNk35!uF<|&BZ=Bv9#^8ot~ZBbm8Pv{kc~J-&?KGGTk2>bL*?i@-L#_Hbayc+xIk?t(vvPIY5>b;RYIg6vFYVOpm!&MnJ5{_9`qY;jHY;t1oVvCmUg<=$nA zIVYanIj#P@NP6e_nkh54-r>2pLDrA++>EIfYdT}wTs!Zwowa&BY3{idNjsiTyYp79 ztp3^c50^r&@3`FS-PBx{9To6xf_qNzIk#^QuHTDN`yY67@w@Vah3a#PLqk`+oU!>_ zb*ALf*e&iS9S^4N|GM3~r?mK*%LC!&oAKYRFBI>Ic@rir^Xd4y(5kc2+txe!%w81x z&-rxd^PDo7)ZL;>7Oge2$h&2Dd+wzp%dE>DUB5Q#x%B)mAHwFp-&dV}oR2T7F5hd> z3)b^GQuC`LmE8hQmRXxzjVU|h-sf$)CwVWkchLKf#uuktZaN(IeQ}Kc{tnZd?fyNU z7s?!icOUoP<#A_y#aW&=p{2}pFXf@+q>&`axdxZNOhkszxmywy=$I5DHDh}w=K_n-KlpEJGSq6JNJyj z>fmL53K!Gw&b|56S9gcfuG4NV{J;8|7Q8Dx{_e-pbJI`H+VVYP_HMyBA75_LT&E+o z`}wlZ7v0!%zVs+w*?Vzo_5P20rrgyQl=-ab^{r&>g~e~*9h-80b>}sGL*ZTb_U5w& zMmwMF(F~sH|7VJ4@G7ZiN%wW$tKOLTO5JK&W~phYukKczAFjTi`2;KO>h3sd$(0p5 z`BwIEyU*wAUW)(A4y^8QGkV{0%|`upcKX5x)^ewM*3`ew();u|Lb*}?bHTG~Ket`x z&XZE<`)$47(DMC_(DkzgMPxU)X3am&a!@i~Z|1e~chk;&-V{Ic+%=g{VV#12Ubi=5 zCbE~0^sVbY`}N(8hhd!l)}Mp-Kgju3e6BM5Y=&k^{KRNG7Po^9lNPBIx9xqi$M~Gp z<2BuCq3;*G_jLtLuS#ek9WzD?nw)6FxEi*Dsc+V{S zoS47v(5;R25sM{rKeW6JIw<-7%?6X`M%7pMzVr80-QHiOz3;8;ah8kwbmq98Y&?^h z_Hc`x)`}N1vgb@%r*$D|%h9`c_ixv~a7FBm*@EQHp_{U&PJG9{d0ExDC%T+pEx!BC z?Dn&Mx8t#&)%#VEJQ8L}7Pk-V3Oly*)0EJVDFMb0nJKWn+HH|va@ZKb+E?l!rD zuIqOwc&S94`!V&pvG;C^^IOWcuU&LR@c*l%X;y+hRmR5aZf-iXeCMLIaYi~`e-Bn~ z-~FLnAUh>f{O;T8e-~RGbnO4UK$gvQGM{x$m_bJRw+%aA+9=OEc_wa=FW--;({`D^ znIXP0?ZC4$e(z1i?=DTeU%KtmBE|0(iVG6*#f~JsyW{uTc=!K_p?N1=rFm9!`u$?B zR%NTHJ!{u>abtz|)~miNQ@52CzcXTX>yRfqpf8W{N8CU9_8?~=2E{k6NcF*H=Gq}|zm8rkHxFbC#ZF|aOyjjH?KFFrmYV3Hiy>8y|Er-Pge$4o3^mW?{J~`uezVlZv zylT2ccjxiX?@q_1T)xfMy=d+}Q+*RhlNU437`;1RR%v8C@77Dp^{KD7UCFZBEAZCf zk&;=t`uFQxdydaHi7fNhORIWYa?#X(_mhHa5|5j%Wb(7moZ-4ST64Q(bG5DefC@ptraQvUMjbRiWr&S*|n%C zuvwe!KwQM6CFw_Yo7tY8uXjsL=Bx1Hd#CMwS%kCiuD!lxvD6~rRd)p>T<;scdaILl zaO*ddgw@M!ZO(rz{rO#hxB8^(^1=(R>n7AkUR%X#r&WE+L`(Z!%=>Le4cq6w)S6>< zD>wdnxn)Mw``-`lxo&h{99#FONg?#gyFU9)X0z)1k-Dz0#aULonR>f)-}9~+l{xx0 z+pqJRh)m-&kj(vDyE5bZqQ|rU-ucZpQz+!l{L207zb{fV)9YM0>DJ?RE3PB=K7L^5 z$pDl+7p&BLJ1u0YYF zId&6PdGJWbO+6rPlg8cU9h0!Q>-iOfeMer~+EGGaQk`Ru8go%2^ZYH-%75z_pkQNzINX3rdXHgzgGkU0?+S#E@0RH zCMNV!wwr+Vy?_a6bpeix*6cX3L+RzZ8%h1?I<;OGf-ZOd-nOf5S9#v^eTtj!e0DDP zPgt4lcIMiV)^$v|@BB{NFTFnZnTPLeqs8-j-#bQ^#>M<{bav~JIOVcE_tcbCvu3pJ zl?uMp853=q8)drsyI({1ⅇ8p8cLT!({O)#Xz&1D<`y;eJwVXtlqa?KkQ#$-=fQg zQ>T{1#8qESQQwwl+uaduuwjYq+IO~V&ISHleU>xck~qz zrbhPPozhm`cu_W0m09Z)+pftQW*pu4w#IzfiYsp`7EP;4TXFekiQ7qsXG`z>WGTJa z!070#{Ck6oqo(z@=>e0sFS7kAuYRXV|9r`gyK>L!IhS&=iM{;w{7yphvfch-KTk{- zuT6M-D_+&LIqCbW6v_PW3pOi6zvC;f^1b)3{D0^}G0vKEuOf6NUBCU9Uwcku?>XD; zmaog-u>^mbGiQ!|XUv1*qF){KCza+_T(z{cjIRX`rT72%pIxsyku#*bh=GAYwZt`| zBqgyV)hf9tHL)a>!N|bSLf61h*T^iy(9p`%#LCoE+rYrez+hvC=pGaex%nxXX_dG& T9Jsoc0R%i<{an^LB{Ts5^4p*` literal 0 HcmV?d00001 diff --git a/docs/html/reference/images/text/style/stylespan.png b/docs/html/reference/images/text/style/stylespan.png new file mode 100644 index 0000000000000000000000000000000000000000..9ffa05b5ac4372e261ef3935d43c0ac05b2e921b GIT binary patch literal 6371 zcmeAS@N?(olHy`uVBq!ia0y~yVEn*7#JE}Fff!FFfhDIU|_JC!N4G1FlSew4FdxMQ97#J8C7#OcGe0{+04iYTzhy)2HFfcGAFnq{%I?uqsz+U3% z>&pI!QA)r^X)1||gt2IeCS8iuT_3=HCHJY5_^DsH{K8(A)MeCmUb z@hOSy_j8^l`q}$br}f!>n;1Rq%rck2 zMOy=xxO52}^i|m*tn%$vk(}1V6Ivn-N({QWPwpSLa!Co(nOUg(Zf~Ko(VXt5PoC%q zE_%O<$u+Yu>75C zeelw!HUDRZK1jd(J-OR3pGEoKRb9r7a{r$!j4bA5`*SU#=)c?lCEu&BSDya&X3B!Y z>us0aI%KS&`C3G`%G>MT3vS2t_1hHB+M2Cf@VNT>ouYlbX@WmCU2dCoX2HZV+iMZ) zqOUl$&zpN=!aoWM&MRB1o7c;h-p@P#GU7@Uiz@TG zh2G_}qKfWF?wxm@-z<948I2DITa@?Ztuk`w+O=lm-N)NBbOPQqO_(AS6>!1%?%(_2 zo3_sRyRR_cN_9s{@86jZiw)Pj7j$mAv8qI)v*smla?;~}tJk?o8tkvNcYHTj=g@-Bc3-AEUwcaa?;g!vnnEJHX+f*=q}+G&7FU_3DeXv=S#w(B!}i|p zkh`ImwsL%(zb5lw+caCT|2Mtg;xd0*UFZ@uNl{HC=pb>fVkggvrYU3e;3?xg&Go5o@!T#&^!VZs*(||IPkt-me4c*ZJW8J5cFTXlnSM_TraY1;yXY zcvv3!>~^X6x8E;KJJWZn=Y8tlc`CMo*V%2Zy2_~v#lT~am+hbNEB14c_dCvrW#z{% ziCvp?;luTu!zbQYwnv^i6IVS!sCd@JO~)iM{l(8(z1?`{e~ED=W2pJm<(AWxM3dEe zG+tj>?L3{m)^_41L*-+oQOnZf7FP*O>5(>9y0kiY`Zl#+9+9GsvlUiInP!Ew$Hi_w zv)S?DjKoyisuKGR-D~GuSrU8VPpP--g>~0=SnRb5RTDnOtNrTQ>gednzwVm%+<4XV zDK3<)HS;S^Q=xF%)RKs0(Z6M^qCI<(CY7(raO&B{=AN(n+)(*cRmtnD6PA9{^|^iL z#c7IuHG3-NeP-rNb_2o3XZ9z(&MjAd+m@_VI#*9)_lqsv?=)9wy}0yTus`yC(VXyr z!h4+4d9<@PAJVh0c)-T?_?+e2Rf(xvq7%E-bMiMNuDVoU5t71Q{touI2byx0& z^T}4ruUgM2xaP_1cJ$Vo4Tl+D%O^x=S-n}6urvNuXwebXi8mS)R#?1p-Wa!ki_O~~ zq2_b0zF%e-%6dAd@IW8iLjJtj0!OYbHRac{TBY=@hncthe(^DJ&v_G5x7~O@aT)W; zC&9IseI1YT3aaf{7rf>!_qMBFTNKmhl-=DCF+0_)`qJW=%i{~rR8CygzW0KFV8!v- zZdXjs+|`{n!&v(BH_ok*ao@xD@>;!+oi8D@d~W>--uNG@pWEDaDOo1$Q*k}eIrCI) z{O?1L`9n@Wn6-V5=d|Tnn&q)Yhl1nQMEkAT^~m@j7yI0zBe@%I#aKPrwRzsR%ONk8 zMg3eR+_}{^=R)donHwDMVh^#NvocUM%^PfBYdrTJV?_ack;kx*hYprai zp0xh9bpMVeQ?@;rw|?%IE%MP5?_AAWtlY*TZu{h;Po?Pl-wrz4Zn|Ba=5=P~+)D16 zlXJh^cZ^SSHQDj>`#u44i!i3Vs=Ev3Z`+^}@~v-wij`OI>OAf3HqC6|f8wVLxa=xj zA!Xhu_tX!~4Lu}@UeP#R4teA6f!vED^BDr0MdOI%dyStev*>@MTu{m2W*OZn7QLoif8%oL}KXQRY>uH_qL!)Z6u`@eGK{l1}o>QvwD#a97X&){4suHrtsjdSn~@Cfms3xYgeyD;B)y$bRujc=eG* zCFU+4mR{%Oy7gwu`6|}$bG=RW``bMEsj|K4dCUcUi}%Uxlj8d;kJmD?+guFlv@E{7 z=)iHcOR>2d7610B+a=t{t#<4Dec|~`zWsNiWZmxg>?#W=J7cyxt=KGd?S~EA~^_cG&*Ll4q?E*~>-wRGc^-H|AU36YvwD;$Mb<2K5 z9Q1QEc;&ot!P$2%AJo{NSKliNU-F<*U;UwT^qY+*-}h?$UVVIWqx!Z>-gy#X-}cS* zv{SFq?8q(hXs#1LRCtCb$ z`rTJuF>S+%2aCKVcbCqezE-Tq@?pnBG1q-B)-3X9f1c}={w!j*yw%&T2Yu1ge6wGw z?cW$_`uC`qeSvT2)rO>?*PHzBiXSPf_AIZ;PuYIszZXlkp4!!SeIakxEqW9-X_rGuUj>KEIiArnsY5v#QBuf zktW6GS9)fqe>KrD%H4kU{p}KNT5i6$>45p`0~^h9UwUc!SS+8Uarj`Iw9)#i>Fsm< zSn7@(?UB3?acPG3%6j`7tM$(=eIV%mdeNV>>|6Yk4)kQbDxAM3DEQ!N-))b#r7{Jo zNAGlM4^-T&R&&>OL*1>W0N*{!Q+L2dt`47q}C-}xL zpT{*L^UbTPUv^Fx37xch;%l#_1^S_d>L!h6gw*AWQj5D3T z;H9*u$N{z7%hNQKSI3C-NNiCGexz6bJa^;gb&`R>&p*F4?DjsraBF|M$nHhbdg|@T zEarRjQWu%^OZe7bo*ei!`^!rY*SGU7^@RP|(CMSceY;cKXUD5OjNg4!xUG8h49v1! zT(4~4e|vD}sEjn{v$y{tRy}asD%7ys#=8^F)R)4mCqQm!jj$LxjcCFY9>B75eUmrzY%=kLp zNWa3&F|RaX#@q*M%z3!2Y!lb}T1}qQY+sqq$RsaxS9RWJo_#kzY3x*9V6J>CQvUC( zUDLw%yxlNq!R=(Z?UCC(1bBYFcRM;Q{oIk=t2H>TrEcTA>#6g1<(5UOA`%;`ygpyM zIxp|Z$;d-VT$k5PmG$p4>8*Kcd|s#O>@+Lx3)62K$u3Qj*_e8(S2Z*FvfIyxC9QVX zOp_f_)~FZf?s0sZJvHIZ?>3iPd)@>zU)r@H;f%)3?crySys)=Oem6TNWOhV6d_^XN!J#%gQ6#bW>V{HQ*=$Ye|U_GV+L z{K95!iyxQRS9!`ymLGX`Cv*D=od>bgjn{2GDY(~M_=M4`s=GQ;yQfMrg`F>YbZXNX z&DF{$JN`eo^KGlbwwwH=DssP$>6yi6J%4_E8|(T02W3Vk->yckI_ztWXaJ#*@mNcx_4rTM0|EB1(9Q#*X4^0065 zjCo;YI-Tq9yf*iYxW>6$?8CgGWtTS{U}v4EVYwvXj%K^@>YIB#eGt0?2oip1qQ{kkl(X*7((ZOodA9(RF4HqQ5zpD``31h z<>?h?TWQm+v%j%jzw+1RV_fL;d{xN^E9s>F&5rS%M{gOM{a>NDHDH?N>bk0^oV|G= zye4Np9_-luXSaEc&H05!+dSt->D-^Wto%;yjNR&2f8MZSUt8?KuO@SH&aMfY3!?g# z{#W|kx!mx2&D_rN?p*J0(~g(jn({7c-|Ob@Vikn*^wyS?#E%jyE^)r9S~`{I$7bN_i)a_4$e4fT()!M`8x-ZRCrd9USLzFiY`y>=>6aen*b=rtF+ zZFlOfAGLV7OEzrfj)#+WXUcv4zUlZit0&*pK5W!3do9fKvb^%vLfNyDzH^FKOW7H0 zb(-DPmFy;>^L>ih)?k+X0S7(HZL($thR2v?eR^?rWwOGmcPlzx^|Y?qefd(t`rv&c zmdQ6JhjFvbEp_zFU43bqW%>m^=}9Yg9?pwgoUwdu(ndS`uk}K_kK%39y7M;v{`FN_ zxa9V%lKq~g^HTX#r8ARX%YI=`74Ew+=^{tEmf>rOSN zV%?{(T#xnM^Ud(rnxeWsK8Z=|mtUzDT_$#L-zndT(*M@HlJr*%|7P_(*idixlyiUI z%9S21h&?B~a^nWG&==}=GCWpK-FD|Zue3l+QDn4`c3}7M>XVywZDuD-fBbH>Y_^z7 zn2K~)QuUux^Y4h=`8>~zbxP=@KlP6?C#Qdr^NXoIpR%m)fo6KG-j1;4*UrZzox5{J z>%&#aBZ?g&B~i)S|C+7fKh&f9SzB7R+&S{wjE8>Fw)y^t-Oj8GdS(0Jx2TNS1Vh$a zZ$EV{UZ=+O<;|*?oSoNozIRUSiMx>3E-zi?82Dz>#VcNGSDjh$Bs?TTWBpz4T{6Y) zIx2_mrFGBW`D{)_-`AGi$Br&?v7NuFcz*g{OH<2fs`?t&UWdl|Nf=k2DOqx!-}YJk z4)-D#b&{L*dCR_1%NK^vV@gi%Sk)Bw`t63pn|4k85V8FEnnY^+6k5BII{J*vno2%v;sl3=| zoP2!!w^_&MmYr=m!FAgcRDfn=vQM~Zkf8(^=BI)$p!1cikGu3BlrB+4wyk+?|DdNIHV?*U- z@A8kv1{W8E9#NWm>*&b^qEba2lcvmkePhiY1+#@M9_@W9-!qQQSyp#_$5XTFTqVoG zZHpiBx|}*TdGS^?|DXM7Ii6eJO*h_}@X7b;He=C}tm+3JmMqAL(aD(q?c(cOn*X=X z?Q*=L^8ciH)ZC@zM=pznr|TImx14@f@k95)q#AQIoe$sde_?MExBq^#=+ClSn=aI( z*L!z={=0x(({TC3(_&AyWcMDekmWxAc=lV7?)lX==dT3XguKQRUkhd=ty+_r?>g!AoJaRvqk)e_f;l9a@fRIB8o)Wnih z1|tJQ3ta<4T_dv)Lo+K=Gb=^AaE|~0 literal 0 HcmV?d00001 diff --git a/docs/html/reference/images/text/style/tabstopspan.png b/docs/html/reference/images/text/style/tabstopspan.png new file mode 100644 index 0000000000000000000000000000000000000000..89a1121d074566ca938ef2347dc491b85d650095 GIT binary patch literal 8963 zcmeAS@N?(olHy`uVBq!ia0y~yV3KEGV5sL{VPIf*YM-IWz`($g?&#~tz_78O`%fY( z0|SFXvPY0F14ES>14Ba#1H&%{28MdZB0+))3=9kj3?H(c&NDDDu$OrH zy0SlFl;&kNjOW$_t1ByTOlM$VQea?UKEj}3$jZvVpm@g9#WAGf*4w*3i)D^aeelsg z?6DGO)A!oRyYydAD~+(bzUlb4it@6gG_$1o8#iw3&q*#_@n8x+?$))HmOEV z)0k-GDngl)GwhTmddznDEZFIC zOJ!EShsq_ji#0Av6U{oGDGPOO@iYNx@g-Z!rNY&s9Xh&>0uwy*w`!SBP^mxW)AOZP zJCg00@|j9?v)(Yyb-w~QXWJ|)SbcS;25tOfu@;J4-#*gQ4y|4_cDV6BSA~e~#{m z&a+$R|Lttbt^~E(hig9XTljpM__e!E1*hgO4f_7`lI*OA_UVp|cV$^RkDM*x-u2T`kC#=yDkSp0 zuTON0(&0e~{r%Jx}~_X- zyToa!a>Y@S*}36Gr!=ptyeqHYdSKS%V%zK9elN`zC9KsgdmUPKOQY)T8Ev!TYYJRt z(JSv8iu*>q*UT>3d9a21oV1D7##2$ZbJx9|R@|<#&Y8cRTe`>M zZBus8>NVSMtDW5Ql6}r}&pBs|-^|$TBX+4O?Uv#4Sc8O9Uea&2MYd<3^Hkjy zp1twS7Q>PotFyzpZ>Jc$iLYqB7Nl>t|JtpOf9|k7TYNn2c!*kXwtW48-F(skYxecs zHLE-{IqaN)(eyu?3umg=9^G%9~jq! z?p~}Ow)1>=&iu!zn%0ke(^uzJ%3iCw^!aneMV9yDs^IULyBp`_*Gy&-Xml zZ(F_eLT0nNZE$ds>iV539yO=CjXz0z_nyyX#DC-UjQM6$AIHdiZ%aOwed>vv{_Tq& zB*m}&s#^W~jHCMM)bI<>1@$c-sxK?~DV?3l(N*_&Ny66udC7ITyN=dgHLrQpf9^~$ z``Tr3IdyBdJ^mT_?OF4ZTRrREKjJv|qcz!R>*52R%VP^pm9Jf``5`1Q@Y$;QzfJT% z1iY;@(Oy33*Q)tHRpeVj>Wm-G=$>b~#3N2}g67OA8b1ykXZvikyjZvD@_y-=7d>^P zKkb{m?`fyx_2nY0%Kx6oGMnx)cw{U7=fXXn-Fxl-ur`jTfI@#m#Hrp0_%#r^Npt35GhmDl`5Q|}cW*fvY1|A&iaymLh6s!o~b z&-DAQW&~+YcGml`@Nenf9j#mU&HHw~E;V4qs{peH3%bpo+*)S5M1*yzq-tztVe`EY z$CyelM7=ut{Cbx4y!p2e)c8oAl;5@a*N^Fcj&O2Tz1`$GT`@hzVCfs@yLAD+)^`nr zm;0P#xyuzaH}}NM583joO)i(3MX!9aO51I(=HxTCmV_CKyS|?-QpEn}UCoq#?)yVU zL~=G>j#8aI$4uqL_f=c@uGH$f?a2$8-h1t}(X&n2Mh7G9zw?`2S!(~fWs%(MHIpUE zF0N20J>hr$eB`z4yZWL$yA>u!=1=xXbKlA=Bl9iTnd92+yse(oV*^6UJl5ww_tp9_ z>G8ekJKpHtUmv&k5x-Yc)AP4eO(#CSGiA^6C5Nk|ziPZHKRV^Xp83DqCGBRO&5HG` zzOtUjGi6uqHov4Rs?K)hUVr7*8XTW`B%Mz>egj{W_g;~oTd(y!Prf<3Kj8{1qiopo z>t1|Tng{FdZwlUiWQySX3g^IQ9mns4hjX~jJS(^Pv{vffNzB@iBjc9q}f!@{aE}y^z@R-wc2-oUFY>m+4gf6^DdUuzyDrLI618*Y5mp^ zP!Z`<_jb$UV>;EJ4+>6aoq4rV++xQQ;hAUD?r#rP3l{rQdDFCBsK@fzJS)qYd+QR? zeW(AZ;Vx=Qe6Qv8q38Xpb92}1wn=WTpSk?O?%Knjt96bEF-)sCRpLr(H|5nuXJ>P!yZ+jk9w#&c$f3iox$%d)L zFQP6i+;l(V*L%5xUmIgj%Et(?od3B^PLr$3cJ-6{Q+lh;|JxIox~=l$c^}Eo%VNVG z&-k7i@i@CG)%S^o{LHEQ9&38e+jqNr*WVC9cbm|;jG3<4cBNiQo9*P8LQmx{mydXO zR_w6CmN53WS0Yvm-O4}S8{x*iec`+PE;kd|KTo`|_uBJk$sYZ@*Q$?flZv0{vz@(E zxj3+J+83Ys;cq5+e*P=E_9UmDmHxz=Pgw6>{W9~4o&HsxqO|9~m)g|M3J*ECgZJ9) zcN=HfZk&0>&pK72c&{(v zd6k?I^WVDg*zyxO-zU9$wR#HYzLecQ1&iFiKPcos&2oHizae`s2iLwB<9OrvKJKkE zmfI+<)SUVt@b}Ut#razomKwZNR#lm|soYJsyt?w(srybK4_=SR7P^&tyf@NpZ~v7I zxAOhc?x@cHeWF8Q>&eYOPfxRYP~Y%wmsZ5`%PMM1Hmlt;icVh}xJ6C9^wbg-{#y~| zr#FU%pG^tVjeWN|b5qK$8vXdx2uo+bWtXik|J(LebnCizo!2UlSWcPyL4Xro7`> z{n-~>Q>Xf9t=i+4UAuc(hE=-G%h>hjT*CE~b1zs}ztY-QX?d{avzG1T+S1A1pKEoU z?{53FVEeD#w=y?4*DZX~6&`E2)TkhGFUzsTg$q`mkz38f^~?8FUf}BoiwqsJs_$Ko z>`p+9K^_2Fg%YIk13;mc`b8erip%BD-fH@J z-4woxGEX+W;@}gfm*klIH!)dXYA`cjA>!_(^c9t2zh6e?)Op|To8I~J#Fw9|=FQS} zTpss&&8l;}*Mq(-G>=*FOR6j+{`<;lU3)4mGwof z`uO(br8&n>AFH|W_#<0wPflND!}q_3RxCKIR-L=esa-+h(`!p9&*Z3yn?nLOG-cO4 z(&q@fnL2&now+&{=O>E&_BStn{eKVA=HFVn z$N;Baw`8+wY51G(xzw6)H$!1@rcOWR^ECC+%;%S8U9;sfN_95h8ejf7VpUm8qU6qAQ}2tCD}A=JXDxe?w`Pt{ z`o7>Yo$`hAJ+&`t>Pfn!rT2FGzb?{RsV~j6dU}ddj^eJ*;zcJ|FTacH{kQsR!6E5& z99(gqcKCc~EuWV2ZE;{FuLR59u&oQf-rA!($GDK4FL~NJ+3+22&$zG1yQ6=l)U|o# zGU>F{MY}$8aJU|Swanak^5Jz{$)}jYu87u|* z_sd>OomlH$}e#J8QJrQuNOB*|(H`+C81AT5?Ig_k2N%^>bg< znW6iVwyvFbSyAH4uQds*#ZN8lRo&CB1{=KS5Rv73(mX9}%egN%b%lAXO-|oX(X+0- z{oMl;5~rS+J>I39ck=hoYnxB~n-RGGbC{fU(nGZytL9wc`yE;~anA9hqPX;POx!EUhGdre{AXW^PRrNXAA zfnV6uS!bTT%6aSBmUmxPOx?8Sen|AazjaS9<>uQ>TO}L5gLk-k z=Zk+_fkVH7=@aF8BZcBCmw$fjoatg0_8r8S&%@UolvnZxRlRft9 zmRzo&@IB8bY&sS8r8M;O#ja~B|H^$zKiDyU*OSD(O`Pg`9xr*b&0OC5YM=IpXFN_09LW$f?nZN9zz@0|rlBJH>N&F7oU#^bs1l=*C5TW_CL6|F|vg;EY69*16>en%)I% zO`g{J^TZh*(H#bN_bq&}LGxhByO!CU+bZu_`z2pFn#HotO`GZMiIsCU2A*+0C2ZWhdOg%Sqj_AwHmmmA>da!EsjCpUo zZJyjVj@!I*{g`H5j`H`u_lmvl znMqJH+v>dZevL5Oh_q?n&rHf)%XiCj?~FfDxyL^jo<8e-P>OX;UZRxR=5zdSi%y8T zu3T_9i@)lc+w{){#eYK|Uz{kva&ys@~nf8uT`HmTztx?Gw-(T+eC3$!@k1DrP-SN6$hjHq|P<#l|{WbJb&iH!WsWF ztJjETB^^I^eEO8XhW!7+XFR+mW|Ono*{(3u(EipU=lRvP$^EkUG2Aer;Cf9nloG zx#gjr8*Y`o{zR;PP$;*^WT3t`<{MT*xJT`DjMCxkZU4M`MH`DvPV*mPC z-5+i49O!CWt>kzz^m(u5>undqv;)t1D@RN<@7J{dw9jSw{_iUmWuKD{yO!Ad_Ggca z?q}P#DRN&EU4z+L7?<;Mhuug!y+*X}%8G6_pH&taxhI?N`5MpT|1EVj`)0@$9_(^+9l@}l5ZDy2zGEQC=lC&rO zzW!A&*)7wmKK_iYKlJR@$Cx?Wc&g+(_hlX2;?x%R{pz3CmrK^J*!S-8C#y9x)!Uyv zHQ2j1Yiac=M;`5$>{D50?wiFFJF~rg_G<5&iQQ|BKgF&vF=Ca@U3Gcuw4d{~S%0_4 z*|y~RZ3E}sQNB%ALt~|1ZrUUjx8831p*dFP=05fbySjwSbbnTi+SzPd-wB?dii6i> zN1a-{zWmykA6bjHZwUu=kZS5gHP3E;{By_CXUT@M_eq`C*)q?0b5T>aSEb5_D6hWf zvF~)bOb>pT=Jmg|?(&|NFyE4G9lPJYxRG;U*0&~Uv)N{)2aoNVd#{sQui5nK_0o0e znQK$T=I=7)T)Xx0&mC|7*yYI{?1=v{@p9TR4sN@zou?ipYi~4AK0USn@8af4kNufK z%Ks+l=e*0;diS#^RrB^oqZhSz^n8BqJskA^iMHGs_jxM=<$uS@&3N`uwex|<&Z#WR z|6WR6Fa2T9*`Ga*CoA)xy!-yzrmUG+?ry}iO|tHvFPa?WVqMw&@2d5;2xW<)Q<1KY zS@WMipV8KR$FJm-)t9cOIA_pMxWbT7xmZztb;`+l$3W&g=pFOSOYwt4?=*5=AQ|7n|K z?-%Xwzumjw#iq-85(Y~zN8Nt2^<`V_#2|T#Za#k#3s1eGWDcXl=PaI2GCasN`|YOJ z+vXX~{oB%=dwjX;=Zj*F0bwDWr=v469~uv+#`=}ecV(ay<2u= zPwVo#g^xBoI=AGLQRxel{l>T6o_+*!a&GZ+)8!t@@4i&Uo%!s#eIt%x>S-6RyiD>J z-XC1r=`u~*{7#LDd~0#ey$@FA(*u{LF7{U06ti>TGi430**5>zyfNqF-t}ke@BEGW zN2lDk*yK~v=`u~<{7Q|?*FI1Fs$HG=6=`QoJJ+QOp02WdDb%SGo;Tws2ixCEnFljp z+k|}Pou3`|@YhA#?$g^he!2EkCvLjOlXK75zS+6)da(%qyvQUPG~IVW!fDT03m`u|y{h1TcXiFi5DBSp>pOifDIuYLU@Z|93z z79NtAByFNvnY`3pY2xcU7gkB=^3B9cNCUXzTB!AJyn+7W#S2w;#vJK71Cr&>)x)^Y+(zYe0l%NHS_%EU;PvN zb9IER&87^a{uKf5E#;RS>yf;iI#n)~d*8ORp8QRdIECF*!(LunoZQ&xwb;A?(=O%5imdt4WvaI4 z*`@_=Cb3MeTN%CVa`>BhrIVtct>Kq7&hScV)s_3|aCheVt@|_lstg`-&$mcFB4YLR z=1axu(sMWcH7#FlD0Z7F`sLP3!{7P8E2d985oFN*(}QLH%t<$29O$ujZLd*@c$oP8 zoP|yP1x03~@7gQo2v*(UT=TKi@#aRGRmG_hhEI#9ZdluYz9>h(^0f5Ni&q#A>sh_x zyg#c@;)~C$iE=R?qUS$LuM1npWAT)^zxw8_w&jJqzs=uYn{(gp&76MQ=c}t0F?Qbh zdM44%X!jS%1Cqj~>Py(0&v~&-_6s?Ge9axpbRWjwYbJ0uEqT_u%zHaa@Xo{+LMgje ztGD_;|D(RVmwT7!l1i0hp82o8%#F;#m8z2P6gO0R^6%SP4t`_e28ne zbk5|{CBK&$I$o64TX5Wb?NraKJx&Xr6wl<9+jyJHRlMfGB8B1;%HK@qlt)BV8N8WZ z`9Ed#@;4WkznRoun;;~A^XXHooxyrs+p3S4*>>q!KH@obZ`0Yw8~+RLJU#QX@U7hS z{symd&T_{U#b~|{jwx9xwb)jo+)XXQ{IrZua6!L<#OL&x3l7&-p89q9_V=Ib?*5$e z-1*)2|8qopEb`vV+|twes~qo<^6lrsw_4g7(svi1e|_o{U$s|0v-_|1JxSj8>Z?vH z{&zO<;EwHgE-Tck6qy^Q?^uv9UDQZ5eahV+2z^&uN|LxzzOg zr9b;`9!uW6f7|vd`^}ewxjg2E&-v{5Yot_w+yKmD$<$bTM_a)!wDV`dcy4okeC_;Vr;mc-cFQ{%kdYDPA zHZiMHOHpX2?aa@y=htkxt@-8kcCIP1;Zts=&p$tPj`4)|I-YZbMb>P~o+$f^FZ-3r z%gd=gD}GJfqWpE`onD=_>t6WWUuhE^^)@+n`GotcRSWyK_i~?`xj~CBUa8#h^ARiE z8*7ChpL@FQ!D7D?CAZS|Z}uAKjregFo8MO`>&S0+));=OMg!BnI!*udRTg$ z(!|}9E1z5Le*1ail?Lx$J(haG1rFQK$kndf^{j{Y)HzK~Go8m0(ktyNb?3{yy|&uC z+?ZSMb^ghQmOG+v{cOIP2xYcbo^?-KKGn^~;&t5gSeNy-Ml-jTy$mn8qpUW^HSPPH z?rqy=Jy;PhEm%^0LV4TkbJem3SMzO4uxrwZFXa3g z!fq&Y=)EY9(b{N#UuW9QY}KZ>z7?$|v4@ShMa#wqZ$68bMC%h`!hDcNUc{*r6_#kgGY|x zrQ+Vpj^C5CzmX8V@2k${lgrfFSZ6lLiL$TTd})>a%S#5+znl?!EfDi@)@h^8BU3)t zz5BlNdgiqQnYJ&!ueS@D;-PZN?)xf1#_zWxFHg37_W#%XxU=c5N)vZCvwq(m`t1TA z_l<<(Jq{;q`7F4aY<=}^l|8+EfBBwwU0=K2MSiiH*}8Q7w;A0=GG!O-vZuxtopO*n zH*-rPca^}L@5;a5URwNQ&Uv1vpXKyJ9$nC!GO6SKp#~FH_-4*!AaQ4O3=efWAKBDQotRy0N zJ$vq~rT4woM`YiS^VffDYd`Tsknp|}Iy=&W3eV@gNt+t-I3wd>oI$FS+KVfu+NG;^ zW_bPDGUrOmCZ9#o>G#ArOD|=AUd_U;_4Ly&(_6>nr%WmJT=cQyj_RCiPZX!#lyT-d z^V{ist$y%@+5fW(C(W8#7kjQNYn93W`oG%q&rNVEzh^%4w7==)iPDQbp1gY~#&!N|bSLf61h*T_7?(Adhr*viCG+rYrez~H&@L-)C~Xt literal 0 HcmV?d00001 diff --git a/docs/html/reference/images/text/style/typefacespan.png b/docs/html/reference/images/text/style/typefacespan.png new file mode 100644 index 0000000000000000000000000000000000000000..67e2cf9b04684bc601618a3a9edec516d5d5d612 GIT binary patch literal 7749 zcmeAS@N?(olHy`uVBq!ia0y~yV3K2CU*7#JE}Fff!FFfhDIU|_JC!N4G1FlSew4FdxMQ97#J8C7#J@z>{>Xp0VG)95eX7ZU|?WKVEB;jbe@5MfxX1j z*OmPdqqLw9-};W)DIj%a1&-+q3``0P49rIuGz?i;85m^wJY5_^DsH{K8(AE4eCmUb z@%OaWuzatLSIrZ0J-JCIYx|Qt{kz{v`tQDb(b3uYGfDU&zwkxF%kR#5-zd60O($~N zR_`e;flFB4X*)DAbnIPKB=>4jh&D$P>&%e*_Hqm%SI^E?&wqCEOtIfQuKDhgl9H0! zeLp>4=iugcY_F%>wtqXbCr+GL=wIl!?bC@9Cn6MPf++zSQa|m>S&jC+YFV{^^On+h zRimec?PvIUb0SQHSd5p6xUyKzwc0f8*Qp7b{c0_hn*sr_05)Q1F zH4puqwLh`Fq%O7UbbW4=M*o93n?HxiU$ol2U&uJW;IjPOjfaHI?r-X5XH`Dtnw*!u z*Qk8vo!_$;Bpg~V>-+TO1aT2jk(|rL=e?%~E^GbdG3UeU%H2s`a$AG*E^7Tuy(Mb* zVbY?EOZ}yx?|!rvERb zf9+qp?|d!V_R3H(J^Jb{<%{prxxD&*d{eq-JL5wBksG#l2bwg)SuYl~mug(yGyP-e z!q!h3mTw%+Y9I9df1IJ}Z*ywt-QCH7i_fa{DzALm((QcGv6FjR<%VM^Q3mT4q*MjI zm*d%`_mhif!=l0sKKE_caTrHkF1mdwa%;x&96lZGjw3?e(z}i%l}+3f`tY=5&|JB1 z69Uu&SNze@6Zfgn4AL`GWp$Yw$?-XShUFuhM>6m4+%lDZrjxEW-|1`h!gq5Ygk`Pz zsvaf%Dr3{QC&d=8!zEgBQ_Os;kAn)M4vsQ(sY}ecW(Rk}xDXq7L^6I~Ha?{rD znEEIF%D%XV$1K;c%DesZ>gtcHcD^>Nx6He{ZSB*QZxuhk7hF5P(%LKEzFR;=jAgP~ zkLHW3i+AT;mp;3s{^_iWj5IA-oAnAwyDk|n^X)vbea87ypJhFTH>O3!MAn~u^Z)6t zssFt%UDuc;6m@4|&it7L!54*Wcd34^THq+)JKONVo`$A}AwijUr-i(VHCon^!q@L( zGOI4_+b+$?FD~hJ=Xm(w|Cl$;GU_w6p)^))hm<~!>t$$CPplht~= zGw+`>&soOpb?UFVXZ4X8nX%Jetd!>L{p7QApzy ztZ}KYxX+Ix$JfmeD2>S5e)zM&2R3Fu>7t_(1e2N0{l0H%e9Gv*@m3>N^j5k9~~BUZp1WNA4DU|MS9vm0MUpRpoqL z@%#0m7fp5UcTMsRE%BV}tD;$T&A9Y%`k~6>bIb0QSVgi}#$KyDk+@g)`8UJkGNm_N zQa7l5s#>%0zPZ$%qR-Ql*QuK>zHcbrv&<`Rr7qu%$i+3?|5n6rI8@G}Uh`y9p=zgH zwCtwfeJ}s6vsiY&bn+sX{;Th{pI^1hOzy)Z&5Mcg#@iPByXJRmjm@ITaYfgBmY(LZ ze(%ixJMHL+TZzr#JzVE*WYqf}-v2N+aLY|L)xYOF&ghwQ+c{r(TF`B|^74YZm~W=9 zv!<1a?@153^75b5L~py#3l|(d+@!JNp|Gp!>w^=LGq3CD_pg_9@eEis`@4SS)xyhZ z;**@uSv=E!edb&*i+q)#Y=B~{`k5VpmzQlcdax)jT1J%DI?cP=$bT1$*)qQS372>e zvYPLCwf5Ah{zZZBt)E--nXbJZssAc`&6e^IgICGRZQk9SRj~d@dfdd-C+_=9TGY8K zZ1%_b_e>STY%i|JxTE=e?)SaR*sedAbKffRW$M#x?`6Kym!594p0N09@vev8@A>yD zPgyEcc8;Cz_p+yj{jRLSaprH=Uv3w<`&>Fs%(vp~HLvd+ ze%0cR{m<8w2QSy-oGQ68SfF$(hkJL`o02GlXGy($dX}kPN>=$tUTIYyYx&%i(5gE% zO6BEUh2`FyORtC-?wGxUQP|w3OvcHS`rzx z`BOyH=9{;k^EO+&lMXYPBHLrF$TjVDxTKEb1c^`4@3o`S*4lL@hWB{KeE0T#lgZIw z{rY~40L$KM5qkNTO0S=t@u90}|C(9Fi&f1+a@WoB4BE8hom{1@+NGUkJD=`yRbD4G zS)<~7*8Q9tb6A)E-QB%1eAR6uTOs2-Df6|5>+Y7Gdw6@|V#|Xa^M6LTEb?y8xV7a- zi@oIczgpr>6Fho;?=1EzvQp_d3H;#|FKt-C3cr;nvX`;xnJc+rMAkc zG|ln;cXcM~x9#69x;+(~^F`a7rLx*4W9}Tm`PKJVyr`A7ompq}ym0wfZokU&!7r~0 z|4VQ=;LO*tYfnOq!CTSk7vHjKyL9&I&Qjm#J6+RQuVTfjxW|5-O$j$FvsbN?Gt}rX zGq;$pcKWZ+EGy=e@k&YbQyR9x@CTYQDEE^W2Twlh#)n=%yaA zmpZ6szh)zE%PL3lwPpJq4>r`rXD_nR+o94?c4f^zj<&t(6*q*ho#ITJ^VK??<@(=y z$7U^*<*B~N+IfVr|Mm*!0HcRH!g;l-4qL_ND; zq~xQX|Flvyf4OFkEML^(%R4^XmG}mH*mYKJMUbic-XyMAxsn4_vTk!fZu1sWj@!0$ z9V>^Qrrw7ATPJVR>`%Dyp2zcv&C8uzl=C)TSF<->_dxge<+P4z)z?DJMfW87ui~*Q z2olKsG{rCQoSdV3~5*)2`}uxG~~EHZqO zlmFt{(@$a1%`O+E6G~T2d6gY->1||x^rV0P8@ARh*LY!`^>j|LV|dmhZZ9F1O7GXx zE?upEF8gI);bps`4G%5iE_w<#@%@%g=~~qO_rRnr|75>KzxuyZr|{Hr(^VQ(?)zE# zf2`a0rE9_I$6>!Z1%v-R*{5}Lm)5dV*Cv@*{kZ;PW9qc9N(ZYu%dgw-)Y|dTsdv-z zC%)+_i>B^-oiO$2qVrblzSq|(<;C}<)&8Ef*el}EdYc*Re7E1q-g;kqiC13luh6T4 z=Of}?3bOyXQh9L4^|xNt=?0HVg@xwsd;0N8!|pp_)_;z;c52&YYs`Ek`vsIj&HuEY z^KxOgSs$)>ceOg7<&vB?mXS}L_MBfVbg<$2S&x{nyCegrr)wFfg{?ZsMOeN(BbZ@jVarIPi_AG`|@)!-S18BfAlSvzH@B$<#4xEclbAd|7-L+I{Vg^T+Ven zlse3AT`>sG+i)!MVCdwjuBlrNZC)p3{Lb{Toxn!JyPI^TWURJ3%yq6( zA4#*PTWVh}Z|CK@QP+O_$l|5E{ri+-HyktLS%2*J+l}X3YhHbnDv$f=5Vhe*+)g#= zjlRBocXT(OTkv4n>Zzu(|JH|dpn z`@QfM|8BnV8sGG}Aw{PucGc{eaj!Hw`rO$oyZW7PZQ01HdR}2 zwojHYN?4f@wZAE0xoX)9!@L8cyIozy&vHyVul)A>p;wzKSlIWZ9_i_~iHP)NS$@ai z-oIt;za}rcd2Z5`xltyOm1{F%1blw%_buP1`nc&vN?1&XPVl;24^}A(uVm7k<&~_N zE&k?d)kRzX#R=6ti9VU-+qZo+iQ*E?a6S_w?q1TJHDy-5GoD-V>cuy71AQIYlv!8!pWg%e{Q3bNYIl zx%xgQFTZ@B7&XOozp-Dju*jS*(>5P_wdqicSo7YP_2(+f?tD90D6xK5m!|j}|HYBz zx3e$js+9Vbbe>wNx4+|{-7{-pmOv%$wg%>kzX3I-}sGX>_bn zpQ`TNE#>AH{bqmRwkuUJEvZWrx%g`kCqvz>2{*hgK z%j@2kYU3upYMBi;jVwDA-mftB&9b(ZK5t$gTm3ZRIcvY=`#Icfy|a$3FrWLGdH1u> z#Su4qRA0XL-2bst;l_i+-D@X!_owjua9_-wcU!;NOtW9b^nbyAW!>i=e?3hsxO97J zu+rqXjXQbWt3Pe44nN2nzNTC(wrrUoqx9OsYrg3@We0h#MQ+XL-95v_Tc*IRMTJ7Ib z(c5}yS#D_3Ocqz>G?C~(Q(lD}pD?|5;q|!6gR1o_KEKv)*|d9_rubZQm#0s>wj9xO zyr_9tPvJ%(e>(Tw^rb5v%-}qzI-R9+uWtIqpvw{snb#fT;-7Bq`?BJC3*$zit}8!^ z)pf<^l*R^Zx*J@z>d@W^^M0*WpI>!B?YSh^RXxi|U((CIUd!F;{BO2=q^{ST??3mv zGM=_hwd!lrqVv97pX4?_oyJ&NE}gsi zJLAr~KD+k|;(C6c@%_6fWBJx%7q%FanCrU^9^Jyce%&da`8U>vi^vuyPn>psq0q$( zcLh@7?M~hBIQOkY`BUe?%94BEP8dEduY0w;?(&2ng^20j)^@fPyL>%%F5!}T+7$kR zvgO$iZ+cvw+2+rGr8e=}UH98ljvKMFo~bx{bXHRHk;wl~u1{51a`O#Ga?>7VGmV>% z6&(vsuI7!NUX;1C&ERg>l)GDdq?bLLP;@1IdBQPC;~PfHCD-mcVr1O?YP#n87tIq_ zb#(8HuIUvUax647+^)vHcs6GAC zR4(?^aQ4Fzv-dZ}Q{CO?X5TVhENiC8xvt`>%jBx7QQo(d-TOnm7QMC$pRe%AZI#Nj zYda67%a}%)^Vxl!sqkvEda2;^FSB?f{y*Bu(penmwfWlJy<2k6%Gu;>p7dbS{8#?I z1)WEx@c&tTIqvi9t~%YSyONGu)P3Jxe&|v4Zc*9mAU}ifya(J~D|M^#7V(CK|GO76 z`Kwa;a=$54{12UZKRco7iOWGz<+t&bWV+t$-PIzI(acYF>oQeyXv(D9+%ms{G7VyZ*IopE4!bT*s?dkoJ zVsk$Ef4jNJ-DFYxeon{!wJ+bMZP)oysb%`!^|j1LZr0$qjZ0rKgfIX4a^I)a=CGOD zo{PP^y5jn_Egy@^mUGso-M^icTE(|Ke_r0Lj9yhW*5`j$-xZqM{i*b9>^F;bOCE$K zm%R)#%8WVruJ&J?mOe#F5$8p3#`vRtUTM`xOiLjr)^0JuV3%GT9vM%_j~2>oNI}PM32`dosoL1 zdo7;d;yJIXYyRu)m)&e<6kZMukNLdjZs@m3-%fm7Al%2f?nk=yn@u{rMt`n!EJ*Pe z4$r-4eeQPpT)xTYejZt&5cSq0G~%Y+`yCpFZ|rLlHuIU!E#K%>>9_SIhr{p8WTOQc z>)%&iG(EV%{^WA^uG1&)Ppdw7c%|OnhflAp`Q;J1n7#e7d&$k!`{Fyzc;9_qCU}sy zUO;8qU8Crrb7uLP9n+RAyP==8^Gext){DO?+m^bXeru?qVfnBnW4Uwpr=!={Yu_w6 zGQ++i*e}E=d1b9`jnhT0-?3o@!CQ{(vplqPai060x4X(^rizPP>P;xP@tF5zar64_ zIHMj%=9gKP}qTEQjBy+vt0vks-*|HiI9EAHx3R@L{j0{ysWF1dNv zYte--)qL+m`c|CZ?)&O^OLE_G^*ZjC&yttto64*`6PrCI2GjT`ra~X>;HSNBx_$f|5JK@_)h8FMgEQ#f1P4Wo33dG4AWQ?u7sUaFniCzfqzJA1w3CDDE`%>ci{aepZ6VsK}{c3xEbD+{P^!^!b6OvJ-ecR zeevACZ~bO|Kc)QY(~{XQ<{29$E$2P=qWt8Hra5`pZ?`O;^w@C5#!K-HvZb8Y7QOv< zt6_o4`iMZCPUUOA52UhIYR`VMBxB86uKPwRx9$~GSl)Vjd)d=jD&4(iKe<&ep4)Z( z`fqD_i_DMdFpk3_Q6}LUYs#f-Cg5;v!dGiRdTny^|K|fm$_8lopyev(Snrb zx%b4M$X+l$UZ6Wa+BRive{I;$SFLCHZd@(;z*3!4cI~;Il;PSvUtey%rxv|7W2W!6 z+nd!ku5w-XA+ktr`%(9EyH+Y(%I$z*k|MpF|py}@aYj$t>xL0A8&;GUBR}00-Jlo;DdQ#p;4}(;z zv$y75F6@5X#QOhD?ZK%3t33}{o%p#`{OOawa}O)2X~*xqa93%{-fdgG)Ux+IS`hqB zO#8skuTwv%cTfEq>bG2P^Gc@4meKkgFW1Pej#~BAEG4n%%5~FOUfv#;=0(&$c%*)B z@{2Fuc}>vrGyboj-(U;9g> zpKiIg@%bg!$2GH;eT{wgN^j%SZ8|}mhx*GyF5Q_j`Auo)U;FP0d*}QTFFXHvM%B-k zCE@>eUk&axopk>Ds-Mv=UQSZ^kCx}@sl1b{VpkBd{zwbs^{E?f72n+TSnpAsWl;U{ zlv9u8ly^LgkeVJ)c5CgIw`cAZieJoK>2kcjapBji(?4jtO}RYrTux_Rv~BKp-St{a zCvwj0ip(k6_mZRO>Gy=RJGYgC<-SW?Wd7x&!Le)Y=1Mu~X_M~m(>-xjw(fL>{lcQS zSF7(|+Af+ntI%BF(&3y-!n&VNyHEPDezW-bh?+d>Lne>&5)`=}obI?RZqx%nxXX_dG&xHXjWF)%PNc)I$ztaD0e F0sv1ASGNEF literal 0 HcmV?d00001 diff --git a/docs/html/reference/images/text/style/urlspan.png b/docs/html/reference/images/text/style/urlspan.png new file mode 100644 index 0000000000000000000000000000000000000000..11345206e5948230846a82a4f09d9f1e4b467a2a GIT binary patch literal 9769 zcmeAS@N?(olHy`uVBq!ia0y~yVEn14Ba#1H&%{28M^%z1aYcG>gqY5#hDNIl5OWs zc5c2=m?KkQCVg8capDb@j@qkj@77i-hPw*QX1K}mr{-K>(vsa*)<%c=U)?M!EhF

    e{Lii+<$4BI|_EUfwS@%V?|e;>?QylNHK`+dLldfXn@*_$1d*zl;DKIK7822-~af|;^zSY0UOd4&r3LKh#b0fX;RG5_3PH1x}ULVI!ghc*?uk`{YJ(I zQtZK7H*8pb-C4l%d>_l6MH(+&zdme!zlPb>bImNj%O-QIO1b#jnXTsL?SGwmTqZ2K zC_P<$-~WHrD^_uxv;0$MZ};N?^9-M5rRR5CJd(4zO5m14p`7oVNxWilCym(*Y_=c2 zY^~JMe5fz<%bemBt9HFDv=*&!YLIa;@jd+Ii^-oldj^N)mk;J`Kb&Cjru^c!KQYF} z#vH9q7haZJFyY<2d2>W`bo0rSsPaFjPH{bZ_H4<@qiLHJm6enC%Qq`Z_n6g-n=Tw^C+VkO(v-EAJTSG$X8`Q=XieLt8M z25`(;ws7fE*8F`xpVis3ISL%nKH0~zXIW-OPL7MO@7ZO?Id;Eom#^!1`7)EiAuCJk z-@ots52tO;)%?K2=6ophzdrZ=`~2&#b1Ue)G4Z|qUg@o*@8y8#Xl~cQ2W7hxH{T41 zkKey?@!Stx;!JiDJnz0M3$Qru+4LsDHQz+aJmfrAOLNGQ1z{qrV%@BnnVGlOW$?f1 zGQZSUb76}>bfSsW#Vc1@gw_2{Jiqlu*7epE{%BhP7DpMAGEw&Z(uz87Hg$g3cQ3b6 z>HJ+!OEc?yZALN82j4!O*}d~v-kuBFckw^3xqSa|;e^}Xwr`%+zMpz_-pkgz_MghE zce>g<&n@Gr{wFW?@wWH8l>Dn@WtaUQ7yi5Ra%*|eJC|>J|K|KZa`3r>$}Yh}m)?Ir z{QPrbX{qSK0FIR_R~pPd>*49ycrf8ci0(PdS{Tc%7AdGYnvky(i$yb;0w1=dxu8XO4>&(`{2BgiYk%(LrjXNCzM zi?iVMYpwmk5)a~;qrWf}uWpEA+%C_!Mw4^>^|P;}IA(8eI;gNGPM@iffrrgGB$hdv zd+XU-D`vc?+8bw?XK*aV+GzIK+i}&4jnp^QauiS7u`O=>^zRo;c;nWG*Y&t5>xgj& z1qCITNO@imzCGy+(>%krr%zMAeEYU&`Equ-es?vui?6?Kh+3`&PCiMlVxdUsLm7E4R3rs6~(AnX_jfetUa6@Z(KiU*8q0RvmhKd%K~nE$=aj zkkF%7u1pbG*66^nJ@4+Ma~W*SjN*DR4uyq`}#K5xHY*M0SBXu-LOvuAf}hp)?- z#nNWjcJ*p#&F8b`5iv0=$vn&b<~rr(zFqb`M1+-@nK>gre|ob|a#rpNfR|YRXAmLnJZ*OH~wIEYUA~_-|YLcK>kKvcEUlR)p1sy5`^z`(O9qkt1 zF7m0z@JEf^moHxytX|D6*UxS@-~ZaF~P$J^JO3D8^4wger5!q!9y9HohR&$!#x*Ir_LJ2!1Zp81sZa?M|Rn%A}{J8UtR zUhwbw(+?~USPw}Ye)C3W*Y4eh1_li`Z$|$3TX%EXnKWY~W8=WUz{Xj#q*Bt-@^&5i zT4A%J`nz7!K?R9qi`ri%S=rf!mX@8%{pTkoBrtGub6-+Fl47K2Y%F~B>eUtN*Sl9$ zRo(e(H1VzTWs{PUl7xf=2S1y2@%#C@k1}n)-K*|D@4(BFq{2eMg#jF0iw@+N9nRan zc;7y`<9)KXFFc&8z~ZU|I$Sm3WoBls`1M6oBH2KKCnGCs($kcl z#Kc5KTU*Rwt*LZCUI_c-*^Wfj# z-!I<2oyuJFf2%oi_T9s~vV$B%_8 zR!LnoZ(02A$`z4czkYpqaZx#DXA!HZsp;!me91g<>%;dMU-Su@r{#V z_3G6#=FUC(;DN%fUAqe2-ZGssZCc*!&59R6X<*0Qypqz=MnksQXWOPt6SJxQ#`FE% z-NSF*3tCchtqx{NE={*NCOinqT zPBD6`QOW;YCT+7MUpuq2vvX0Yot@pjm#acAo9u|uV_{-kxlc}7`tYSom)cg#2c{<{ zCvVETeED)1}DmO!welNls-J29?>S(t*dP0Y;N&fEW& znYC=;zI}Cvv%6j!9J_G!YHK^cyqE8^iy19(>!&ZX3Jp!2U1l}+$D7UP3qC(PTblbn zbqjx?tl`hx>}+R$fBqUfd5PqrHv)m0hZ-#fI37Ga+-@Mjv!m?otwtX{w>=e;f(~$h zeZMw(`-Tl06xbWOy11@fzpkvV&TciA&)eHuLPqAuJrHXJ7X%oybz3vj$Zuk?dP8tu3c+ex>QxO!*7lSsIb!9 zus&`t*ZS+)xko|C>&X)p1_f1B*6{UlQ;o}A11C(MzFnDdfks!4B|i%jsG)WH?XgZ_ zb+a}4>#uW9KdsuTp3KwT-7O(2>lzkzt?heURMep}XL{yzh=_>fNT1SpFz4{Yf(Ose z&i*vd+1dHUix(4C|4lyLw{Yjq$a%?|Z*DW`tgw;Gn8mg?uK&}gA{qO-m}mYGadB?( z@$$QN?|!P1mX^l0|Ia7xPoF=R+aBO7KCw1>`yvygojZ40RDM$N^72a9d~=E9vfaC- z=gys5r11XLE2qFfLA|&=J68YRUn0R=e7?1HR~+N{(+v7&nJ(!%7_h`m-TqNUQE~gf zWKo@#-HS9e{%`@XxQ;>pxW31cS(bU?%o&&1SWt^G(QLLL4;!c=ICsvk!ba|9y1ac|&#qlo7BYR#g0s9;rY#Zv z;AC)jXEFQd&z}QCSgq#rJwHGH_|wzV6E{Yru;a<|`dXIIx& zktK2K#rOaFwOXX4YOh>qXlUT|JDDtvJMycltZd}?EuKrr%C??=?wpnNYL{b1Ru)%q zaB!gZ;{*ea>8D$N{rXjUt}JP@_`~=GmtO`11u2Cqf1ILkY%ClZ8Od-+SSazDKuTJg zo3F>V)y%$^Crp~uzIf%zmUmV9t7|*{JuI*&`1`9=L|okc>+M;yq(Z{OA3yH5Z@c|= z+A^N$r&}*wx+MPTTt~BSP}6RWDCxL$a)tonClWpI$LnXPT_w{K;Zv**v6HL1S)>aU~S;@5BPJNnnP zGD&cMZt0n@f^&&mqn6$ZxUuE=^XKL^Wf>VBSt3fdh3;-{OTHK6`~;=e63478t*)-F zG8@jD?VB>)W&1Ii?uKcfK-19+WLK4U74_{Al z7q3h1Q54`vC@BfKmhR=n*iTL*U-~D*2*oO($cePRe{yq55NCLx2;&Z zbm_yj+wVD*mToOs)%$10*|eBF6^5r6O(l39ot&&*^i;QE=38fnqQ(9NN8W;X=oCInE0O ze*OM^y);Z=Pf%`VrlY54=bm{Vw(zz(fr=q51BErS)~{WAcls{_2_6ki&57CqiS7L| zcUrQ}KP~cP-W{humhN$Ja&gsfO*8fH zzL;_4O2|q{LF-Fv*Q|N-YhRDym8(}D{`&fQN7-Acb+NnI+7EAhnc}kLe3x z3N0xq|L?t!Fx)AS+_qu!`Lk!&zSh=XUHhWMYK~KF0Ms;o==}XU68Al z&+WHzoR;U(MdD>QJfwr;;^aPk{=EI|m7P0xo;Y=CQuIwfU*Ci8zpw8We>z9m);4z4 ztMKsfhhM8cJ~+r+u{W-ytnAd|&!0Xm3i+%reQsw7lgL7&#fukbG?(Gfo zf?DF0aXEd>a~E8HUHIwA$rUNe6K}Qkvia_flTWqlDAn`HvzU;{x_w4mmXq{DlbN$- zaXskwe0JbK!{3TjNv zn9(7XAD8y~!t1Yrxz(GCZs(Xu^t!Fw*nRe@qd?OVp$`lOrlzX4kB{cWtiPUb-J#vp z^yNzl4?n-R?}0;ym~zZ+?@Cq<{rdg=efH-vdegZlPo8{9O*iE8@4bmgu3kcJJ%GaqGXtcWm81+ea<)y*Hct+_i@| zTREK+*zW8VsNQG5%i8R?X6;(nm5HvBNui6_^BV59f0F8Tn=*Ck!ri;0-+5L3|5wYx z#Asq-^5DhA#YRqUSIm=r{J8#U{JM8qPbt*P%PYkrTSH`0rL zS95rDRpBMeo&a-B{^;eGJNxbbS*&BTF*Fnu?cTR()25XhF1-Glk(qh$!Uci0_V$IV zR`pC|Kh$SYT3Y%$O5AB`L{wDQzkhWM4=p~te3`j&6%Si;fz{j~*XIQm2^%8c#AsAo5Z{GFd}Q%gM{D%jc@Kon7CiO-3_2 zj=e2oZkWSkaBRZt+1$JD@-4s2Isbh7!i9>JeI51UUjrf{Bt++a&{8sym6eqR6;xGw zi=J)^+{>Zvt5oNra@zX+o@VRvcPA?HeJ^j=u|s3BmSnHnOu==#c1b;b`ZTC??h7+1 z$#lM!%goaf7d$V$)ajjtYURg^^%MB4aMM;W>_FuYu`TK6G zl*&(^J{5d<5jgW}c1elI!2|)f#e$0$FAiEeW9Cdv*^)D9#-~o5TJm}B>8DNi-?!h) zId6M z`%qm??ZM}tOE{fu0@~>aNu2{9| z(3dYJZfsv~_j(`n=aE+l50ydv}GsIW_wh zd*K96Ek^wyd$*v_(AH_wrpd0?JDOFeoxb+pzq&u4PV2w^wD|Ymx;wke`B_<6f4@s% zZ~4pW<>mF^cK&`v5iU^oRIZ=BY4w&z4h$bGXoo&o^|W=dU|?JeAxBvTUkm<%7Ud!RTH0n_*oMu_fD+abzgLr{1Hd> zm3tgSTpQ1(O1L= zm>@8B?p!1P-*FdBc#DgR1Eb~K`|KTrw(~c2G%peQ;9zk#c}el?(@!U@+E~%?d`Eyr z%eQZ3lE(Zkd#mr)e!shdMRM-kxfS2u+^h@dY}mjce$v>)_wa=a0(JlYmP_^phlFg= z>)#V_dA5%lL&NR2ZKt13I%yLW6ci8=^5s<3?=GF>iHS3B-n{vH5`V+%sV{ibnidua z&3ZfQGh-0r?ijr-Tem(u*vy`hpWpALq54|UDd1L)S;em*!w8czE6xgl&l4Z}?H0RvhwQqi8XJ=P@c))n<_;K^t zOs=chU(9}e>y>G&Nv++BN5TT83p(Gkv-Vj{mM=PzH? z?`yO1;=QlcibgSf9LH~Rz5jW3n~x~-lEV)tNUjGB5&hk}VMD>)W2V)*$tt&b+fFuY z*m-Q3_T3e0HM^P;3_7-LGvndmQStkHkTGR>qr7MBzEYh7ZqE<6rJ3I|n`X$sbLudo z#(s;(_oK?>6MGb`t*y1z*Tl!mhlPc0dvm>RzrB`y>&cX;?VN#cEc3QUKkL1rGq1L^ zG}N!`s9|bqszuQgkC1wgE|9NPUPdV-^Z5Gu8r|{cxcUFjbNd3Txkb%0PxvbcIHaem z+x>hZ3~IIf$mnGds1PmyJ0pDr%A0^qXFx`eoCF zo5EEN7wo=!Y2A#DKD|pHyRKF|`OeD9T9kb&qQpv8hL63+O}WATP=m|7-#0@4#~I8% z8`!+?_~V0@E(INWrEsy}T;lfI6DLk&RQU5QEiJ9!_qSXhfB(}NEF0Z--e~>@njSb6 zqBe>B?Lw6Y*F-*SnzP~UT*HJ!(NpgBO*@wzZtt+YFWKO#KV`j~^H2No2G{qE0hhaD zFCF2E3>4WBqgV6&ZuyLvGdovhFI*ZG6?N$E@9#_5e7`hq*|Np=y>iR<@c7!+=s{zFhWCy|eQ4(?jm^ zwJo4-`-Tkxw+>F5Htj))RjQNtF^My2#$8=qizbC0S@n$ZK-ShpTeq5Oo$T(ooST_> zaC&@QXMl!E=-1ZP*4JfInGb1Nye+E^5!<`F{5@#E(7j*I)j-0<)%EDSLl=*@X&*g$ zR6i0`8OEmQL+fSz4D%h$c^To)>Xi?Fh=(X2c+Kv~Vn>ckUYxhy5aM$GI1L>DZtfgU~k|5?_b?Z zSN&W6R|YTN*4rxH^kegRyThB)&p#-zNZ1;+FjGoQOzgt#+uav02HuR9NY2R1bMy4{ z43YPm1xd0jHUjS=u3o)b@p|p{4_~jxpKcNC?(Tl^;zdK;`ss-p3j;isO%rK8admb0 z#Vc1tM5piCu%Tgh`Fl6ZH>T$1i`T5lxtnlM!Z|rv88kR}_b%@-2{m=~!vFtj_k2EQ z{o3e`;jw_o$jSP_vSGH8_RQTM6Wv#g% z*S-9p6}P@cooN-@gPrYr&+^QhDi-!M`{eug2Q1xlb9KeU#S5RGlieBFbtA!R`Q?)a z=TH17nP~p+(o*jg>(<>Xd#t(eY*a+VfywIr7p`6vHI3t7)cIHc=SLx^%H7eOcxelN z(+yUAmwYX$(v%yw{1ST}9_bYR@t~PMAUs?>^Z~<@RaOEVC$gn^*g`@=Ktoj)#m{^~ zWL9l>%q?24Au=g?a-PU~Ew(Awu7xdGy7Xi^XgF=2P35LMfeR+QuCA`2u5^s1%fD$W zK1fasyOy=}RA=wqy!hqY`d!ywU%X}wPu1QyzBwP;j&puZO8N0H?7LRwfcx{T4iY*3f@?e_b}b{0QhVy0du%GPo_qO{aB@7|uy>v7e*U5ic_ z_4M_5HE!MA#Nc7-_*Z`Vg1_614@|t0u~7EXp+j?v9hNiepKbePzuswg5O+(6L+!tt zEA%Y>KmId;ZN6EU%PoP zKJ536w-%E5^Ht(-hYuH6&7Co0#(`I_ zw44@B*ixIGo__dY!GcALlJ3@BJhW3sS(!P1&&Reu`?hQeX}h2EO-5B!6*N{2YUr>| z6PUD7nAtV(!lg@0yYKd$JLl))=cgpX<+%R3va)ir^VNTOugq+1W!LZjw=0Bo+LS3d zt2aHd*|YxJ?EHP52?iW<{n}^EnziHI^!fAq&F@t>^X=ekPd@$lZJG7G`v0{hWo3)@ z?~k|gSh{4%f>o=uvZhX%II&SXe4R&p=bJYfPS#v=S76yklrFS1$nI*+m z{4eFamHjkPw01vx#JASg)h8M9wl`iEYn-pgtbdN_cdg8-RgHyw>ogub{q~}hk$u8! zzOVAn4>T?O@Myn=u)&LM)2_~8c4OYM-=t}-9(VoJESBvvzpFfMJ?|A9EG)yv&c@De zXkyYbbEf3X>2b?1AIvd3Jy%e9?)^uV=baXZ#l-ad`&Y-q!;_Gf=C)6J^Ua<+cVa@q z!xyhvqqFYcXQf;1i!?wpQE6$*d_cpgdvha?_)WK)&(HAW+eUW9tk#O18x9_HKn!D0dGu6+3TD0x7Nc6KVU-uT}sV7sK z4kmO&?R~rK%}4Q;*4AC-5`68CZQi7$Ut)gtJlC`O-Gt@U_w0UtexLNZY2$~lF2>gc z%ovX}7rdE#{kqV!2KOuTBX)L+t`R76TWq+l?(Y5j{rvWS1nlPf&tm-dZTtR{Ytye^ zyC%TF^5nN*Zs7Xs?|;ly-Bo+!+tUhl!%ms_&cp3D7QAh}`9S#Q110I&C9CW{eSiCI z`|VF})Lhq{I3fBoR_W&1&2O&xY>?(pD4c&|%VFu(k9*e`9qnFmyZ4k_c9F-4>Ryor z8#Q)qSY`LAT<`y8JDa|TJ2%`v=Cgp|hHnUSbzEE^OKE;>%$0?UG+uwJ5#Rs5R7Oy$ z@y3N+cjK%tzI~k9>wRRkXOPv++@p?x&azXAf@I$W-;`{)$}nZU)xj`kcBL!6hb(s- zWqJFH^FWfm3(@o|p@Nud<;7p3I*vIwn_fF>BP0z)IJ$4AYd;%?~`PTeAvG5jqI=|1RS*1_I zg`XF=3%EQ|Wb_yKeNs;LYKOr@&%zUSegcUe2ZUJ)6FPgM1QW9k9iGc#`*D>s(>#R^ z=5CK;U)I1p$|5i{xcx@4nvzT2I6>Q6YoXAH-^(@Sk7Dg7?#+w`)!? zFfgc=xJHzuB$lLFB^RY8mZUNm85mmV8W`#tnS~gdS(%z!8JcPv7+4t?EN)vSi=rVn cKP5A*61Rp+x8)WxFfcH9y85}Sb4q9e0H~aL9RL6T literal 0 HcmV?d00001 -- GitLab From dcc1f1941711be22960e0f58ec75215237e0a11e Mon Sep 17 00:00:00 2001 From: Vishwath Mohan Date: Wed, 24 Jan 2018 16:54:25 -0800 Subject: [PATCH 179/416] Add fading to the pattern unlock screen. This CL sets the user traced pattern on the unlock screen to fade as it's drawn, reducing the chances of shoulder surfing. Test: Build, set a pattern, and try. Change-Id: I2ad37a10782d826d076dcf5142700d8facc2f52e --- .../internal/widget/LockPatternView.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 32a7a2daddd..7a248f26427 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -118,6 +118,7 @@ public class LockPatternView extends View { private float mInProgressY = -1; private long mAnimatingPeriodStart; + private long[] mLineFadeStart = new long[9]; private DisplayMode mPatternDisplayMode = DisplayMode.Correct; private boolean mInputEnabled = true; @@ -596,12 +597,14 @@ public class LockPatternView extends View { } /** - * Clear the pattern lookup table. + * Clear the pattern lookup table. Also reset the line fade start times for + * the next attempt. */ private void clearPatternDrawLookup() { for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { mPatternDrawLookup[i][j] = false; + mLineFadeStart[i+j] = 0; } } } @@ -1136,7 +1139,8 @@ public class LockPatternView extends View { boolean anyCircles = false; float lastX = 0f; float lastY = 0f; - for (int i = 0; i < count; i++) { + long elapsedRealtime = SystemClock.elapsedRealtime(); + for (int i = 0; i < count; i++) { Cell cell = pattern.get(i); // only draw the part of the pattern stored in @@ -1147,16 +1151,26 @@ public class LockPatternView extends View { } anyCircles = true; + if (mLineFadeStart[i] == 0) { + mLineFadeStart[i] = SystemClock.elapsedRealtime(); + } + float centerX = getCenterXForColumn(cell.column); float centerY = getCenterYForRow(cell.row); if (i != 0) { + // Set this line segment to slowly fade over the next second. + int lineFadeVal = (int) Math.min((elapsedRealtime - + mLineFadeStart[i])/2f, 255f); + CellState state = mCellStates[cell.row][cell.column]; currentPath.rewind(); currentPath.moveTo(lastX, lastY); if (state.lineEndX != Float.MIN_VALUE && state.lineEndY != Float.MIN_VALUE) { currentPath.lineTo(state.lineEndX, state.lineEndY); + mPathPaint.setAlpha((int) 255 - lineFadeVal ); } else { currentPath.lineTo(centerX, centerY); + mPathPaint.setAlpha((int) 255 - lineFadeVal ); } canvas.drawPath(currentPath, mPathPaint); } -- GitLab From 36a213c4180b620cfa6e31c651edbb2a306790aa Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Wed, 31 Jan 2018 12:59:12 +0100 Subject: [PATCH 180/416] SysUI: Trace inflation listner failures back to requester Hopefully allows tying the cause of b/72648842 to a concrete test. Bug: 72648842 Test: none Change-Id: If79aaf00de2823a1c534482ad8dab778d8203318 --- .../notification/RowInflaterTask.java | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java index 3491f812154..c2141714b39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification; import android.content.Context; import android.support.v4.view.AsyncLayoutInflater; +import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -30,15 +31,23 @@ import com.android.systemui.statusbar.NotificationData; * An inflater task that asynchronously inflates a ExpandableNotificationRow */ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInflateFinishedListener { + + private static final String TAG = "RowInflaterTask"; + private static final boolean TRACE_ORIGIN = true; + private RowInflationFinishedListener mListener; private NotificationData.Entry mEntry; private boolean mCancelled; + private Throwable mInflateOrigin; /** * Inflates a new notificationView. This should not be called twice on this object */ public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry, RowInflationFinishedListener listener) { + if (TRACE_ORIGIN) { + mInflateOrigin = new Throwable("inflate requested here"); + } mListener = listener; AsyncLayoutInflater inflater = new AsyncLayoutInflater(context); mEntry = entry; @@ -54,8 +63,16 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf @Override public void onInflateFinished(View view, int resid, ViewGroup parent) { if (!mCancelled) { - mEntry.onInflationTaskFinished(); - mListener.onInflationFinished((ExpandableNotificationRow) view); + try { + mEntry.onInflationTaskFinished(); + mListener.onInflationFinished((ExpandableNotificationRow) view); + } catch (Throwable t) { + if (mInflateOrigin != null) { + Log.e(TAG, "Error in inflation finished listener: " + t, mInflateOrigin); + t.addSuppressed(mInflateOrigin); + } + throw t; + } } } -- GitLab From 5332988c62e2f2ededb29ac3bfc4774551fe956f Mon Sep 17 00:00:00 2001 From: Jakub Pawlowski Date: Thu, 7 Dec 2017 22:56:03 -0800 Subject: [PATCH 181/416] Fix bad type for txPower in PeriodicAdvertisingReport serialization Bug: 69634768 Test: compilation Change-Id: Icedfbaf1ba933637e935ada0fd98aea42c73f2b2 Merged-In: Icedfbaf1ba933637e935ada0fd98aea42c73f2b2 --- core/java/android/bluetooth/le/PeriodicAdvertisingReport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingReport.java b/core/java/android/bluetooth/le/PeriodicAdvertisingReport.java index 51b93cbd64d..6fc8d553946 100644 --- a/core/java/android/bluetooth/le/PeriodicAdvertisingReport.java +++ b/core/java/android/bluetooth/le/PeriodicAdvertisingReport.java @@ -71,7 +71,7 @@ public final class PeriodicAdvertisingReport implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(syncHandle); - dest.writeLong(txPower); + dest.writeInt(txPower); dest.writeInt(rssi); dest.writeInt(dataStatus); if (data != null) { -- GitLab From 967a952b34d08427f0aeb69f1328417b8ad7ffc3 Mon Sep 17 00:00:00 2001 From: Mohamed Abdalkader Date: Fri, 12 Jan 2018 11:52:31 -0800 Subject: [PATCH 182/416] Add unique id for sms APIs to be able to trigger correct smstracker In old APIs the SmsTracker itself was being passed to the RIL and onSendComplete the tracker's pending intent was triggered and then the updated messageRef was used as the id. Instead of passing the tracker, passing a unique id to be used for the lifetime of the message. Doing same thing for receiving flow. Test: None, APIs not exercised yet. BUG=69846044 Merged-In: Id19f854e2c48649db8f2031ee4f49cdac331451c Change-Id: Id19f854e2c48649db8f2031ee4f49cdac331451c --- .../telephony/ims/internal/SmsImplBase.java | 58 +++++++++++-------- .../ims/internal/aidl/IImsMmTelFeature.aidl | 7 ++- .../ims/internal/aidl/IImsSmsListener.aidl | 7 ++- .../ims/internal/feature/MmTelFeature.java | 26 +++++---- 4 files changed, 57 insertions(+), 41 deletions(-) diff --git a/telephony/java/android/telephony/ims/internal/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/SmsImplBase.java index 47414cf73b9..eb805a88da8 100644 --- a/telephony/java/android/telephony/ims/internal/SmsImplBase.java +++ b/telephony/java/android/telephony/ims/internal/SmsImplBase.java @@ -124,70 +124,79 @@ public class SmsImplBase { * method should be implemented by the IMS providers to provide implementation of sending an SMS * over IMS. * - * @param smsc the Short Message Service Center address. + * @param token unique token generated by the platform that should be used when triggering + * callbacks for this specific message. + * @param messageRef the message reference. * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and + * @param smsc the Short Message Service Center address. * {@link SmsMessage#FORMAT_3GPP2}. - * @param messageRef the message reference. * @param isRetry whether it is a retry of an already attempted message or not. * @param pdu PDUs representing the contents of the message. */ - public void sendSms(int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) { + public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, + byte[] pdu) { // Base implementation returns error. Should be overridden. try { - onSendSmsResult(messageRef, SEND_STATUS_ERROR, SmsManager.RESULT_ERROR_GENERIC_FAILURE); + onSendSmsResult(token, messageRef, SEND_STATUS_ERROR, + SmsManager.RESULT_ERROR_GENERIC_FAILURE); } catch (RemoteException e) { Log.e(LOG_TAG, "Can not send sms: " + e.getMessage()); } } /** - * This method will be triggered by the platform after {@link #onSmsReceived(String, byte[])} has - * been called to deliver the result to the IMS provider. + * This method will be triggered by the platform after {@link #onSmsReceived(int, String, byte[])} + * has been called to deliver the result to the IMS provider. * + * @param token token provided in {@link #onSmsReceived(int, String, byte[])} * @param result result of delivering the message. Valid values are defined in * {@link DeliverStatusResult} - * @param messageRef the message reference or -1 of unavailable. + * @param messageRef the message reference */ - public void acknowledgeSms(int messageRef, @DeliverStatusResult int result) { + public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) { } /** * This method will be triggered by the platform after - * {@link #onSmsStatusReportReceived(int, int, byte[])} has been called to provide the result to - * the IMS provider. + * {@link #onSmsStatusReportReceived(int, int, String, byte[])} has been called to provide the + * result to the IMS provider. * + * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} * @param result result of delivering the message. Valid values are defined in * {@link StatusReportResult} - * @param messageRef the message reference or -1 of unavailable. + * @param messageRef the message reference */ - public void acknowledgeSmsReport(int messageRef, @StatusReportResult int result) { + public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) { } /** * This method should be triggered by the IMS providers when there is an incoming message. The * platform will deliver the message to the messages database and notify the IMS provider of the - * result by calling {@link #acknowledgeSms(int, int)}. + * result by calling {@link #acknowledgeSms(int, int, int)}. * * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called. * + * @param token unique token generated by IMS providers that the platform will use to trigger + * callbacks for this message. * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and * {@link SmsMessage#FORMAT_3GPP2}. * @param pdu PDUs representing the contents of the message. * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} */ - public final void onSmsReceived(String format, byte[] pdu) throws IllegalStateException { + public final void onSmsReceived(int token, String format, byte[] pdu) + throws IllegalStateException { synchronized (mLock) { if (mListener == null) { throw new IllegalStateException("Feature not ready."); } try { - mListener.onSmsReceived(format, pdu); - acknowledgeSms(-1, DELIVER_STATUS_OK); + mListener.onSmsReceived(token, format, pdu); + acknowledgeSms(token, 0, DELIVER_STATUS_OK); } catch (RemoteException e) { Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage()); - acknowledgeSms(-1, DELIVER_STATUS_ERROR); + acknowledgeSms(token, 0, DELIVER_STATUS_ERROR); } } } @@ -198,6 +207,7 @@ public class SmsImplBase { * * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called. * + * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040 * @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult} * @param reason reason in case status is failure. Valid values are: @@ -213,35 +223,37 @@ public class SmsImplBase { * @throws RemoteException if the connection to the framework is not available. If this happens * attempting to send the SMS should be aborted. */ - public final void onSendSmsResult(int messageRef, @SendStatusResult int status, int reason) - throws IllegalStateException, RemoteException { + public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status, + int reason) throws IllegalStateException, RemoteException { synchronized (mLock) { if (mListener == null) { throw new IllegalStateException("Feature not ready."); } - mListener.onSendSmsResult(messageRef, status, reason); + mListener.onSendSmsResult(token, messageRef, status, reason); } } /** * Sets the status report of the sent message. * + * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} * @param messageRef the message reference. * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and * {@link SmsMessage#FORMAT_3GPP2}. * @param pdu PDUs representing the content of the status report. * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} */ - public final void onSmsStatusReportReceived(int messageRef, String format, byte[] pdu) { + public final void onSmsStatusReportReceived(int token, int messageRef, String format, + byte[] pdu) { synchronized (mLock) { if (mListener == null) { throw new IllegalStateException("Feature not ready."); } try { - mListener.onSmsStatusReportReceived(messageRef, format, pdu); + mListener.onSmsStatusReportReceived(token, messageRef, format, pdu); } catch (RemoteException e) { Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage()); - acknowledgeSmsReport(messageRef, STATUS_REPORT_STATUS_ERROR); + acknowledgeSmsReport(token, messageRef, STATUS_REPORT_STATUS_ERROR); } } } diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl index d9766867509..785113f0ff6 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl +++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl @@ -52,8 +52,9 @@ interface IImsMmTelFeature { IImsCapabilityCallback c); // SMS APIs void setSmsListener(IImsSmsListener l); - oneway void sendSms(int messageRef, String format, String smsc, boolean retry, in byte[] pdu); - oneway void acknowledgeSms(int messageRef, int result); - oneway void acknowledgeSmsReport(int messageRef, int result); + oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry, + in byte[] pdu); + oneway void acknowledgeSms(int token, int messageRef, int result); + oneway void acknowledgeSmsReport(int token, int messageRef, int result); String getSmsFormat(); } diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl index 468629ae034..bf8d90b3241 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl +++ b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl @@ -21,7 +21,8 @@ package android.telephony.ims.internal.aidl; * {@hide} */ interface IImsSmsListener { - void onSendSmsResult(in int messageRef, in int status, in int reason); - void onSmsStatusReportReceived(in int messageRef, in String format, in byte[] pdu); - void onSmsReceived(in String format, in byte[] pdu); + void onSendSmsResult(in int token, in int messageRef, in int status, in int reason); + void onSmsStatusReportReceived(in int token, in int messageRef, in String format, + in byte[] pdu); + void onSmsReceived(in int token, in String format, in byte[] pdu); } \ No newline at end of file diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java index 057c9a86d04..8d888c2bcb2 100644 --- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java @@ -154,23 +154,24 @@ public class MmTelFeature extends ImsFeature { } @Override - public void sendSms(int messageRef, String format, String smsc, boolean retry, byte[] pdu) { + public void sendSms(int token, int messageRef, String format, String smsc, boolean retry, + byte[] pdu) { synchronized (mLock) { - MmTelFeature.this.sendSms(messageRef, format, smsc, retry, pdu); + MmTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu); } } @Override - public void acknowledgeSms(int messageRef, int result) { + public void acknowledgeSms(int token, int messageRef, int result) { synchronized (mLock) { - MmTelFeature.this.acknowledgeSms(messageRef, result); + MmTelFeature.this.acknowledgeSms(token, messageRef, result); } } @Override - public void acknowledgeSmsReport(int messageRef, int result) { + public void acknowledgeSmsReport(int token, int messageRef, int result) { synchronized (mLock) { - MmTelFeature.this.acknowledgeSmsReport(messageRef, result); + MmTelFeature.this.acknowledgeSmsReport(token, messageRef, result); } } @@ -456,16 +457,17 @@ public class MmTelFeature extends ImsFeature { // Base Implementation - Should be overridden } - private void sendSms(int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) { - getSmsImplementation().sendSms(messageRef, format, smsc, isRetry, pdu); + private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, + byte[] pdu) { + getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu); } - private void acknowledgeSms(int messageRef, @DeliverStatusResult int result) { - getSmsImplementation().acknowledgeSms(messageRef, result); + private void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) { + getSmsImplementation().acknowledgeSms(token, messageRef, result); } - private void acknowledgeSmsReport(int messageRef, @StatusReportResult int result) { - getSmsImplementation().acknowledgeSmsReport(messageRef, result); + private void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) { + getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); } private String getSmsFormat() { -- GitLab From 6e545d5892566af017d5bddc054db5962885e711 Mon Sep 17 00:00:00 2001 From: Mohamed Abdalkader Date: Fri, 12 Jan 2018 16:37:08 -0800 Subject: [PATCH 183/416] Move Sms API to proper MMTelFeature class - while here remove unnecessary call to ackSms from SmsImplBase as this is handled by ImsSmsDispatcher Test: None, APIs not exercised yet. BUG=69846044 Merged-In: Iec4bbd07a67502dbbfb2142a7bc95f51be0cb377 Change-Id: Iec4bbd07a67502dbbfb2142a7bc95f51be0cb377 --- Android.bp | 1 + .../telephony/ims/feature/MMTelFeature.java | 71 +++++ .../telephony/ims/internal/SmsImplBase.java | 17 +- .../ims/internal/stub/SmsImplBase.java | 271 ++++++++++++++++++ .../ims/internal/IImsMMTelFeature.aidl | 8 + .../android/ims/internal/IImsSmsListener.aidl | 28 ++ 6 files changed, 388 insertions(+), 8 deletions(-) create mode 100644 telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java create mode 100644 telephony/java/com/android/ims/internal/IImsSmsListener.aidl diff --git a/Android.bp b/Android.bp index 69fb4597d08..d14a43306eb 100644 --- a/Android.bp +++ b/Android.bp @@ -496,6 +496,7 @@ java_library { "telephony/java/com/android/ims/internal/IImsService.aidl", "telephony/java/com/android/ims/internal/IImsServiceController.aidl", "telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl", + "telephony/java/com/android/ims/internal/IImsSmsListener.aidl", "telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl", "telephony/java/com/android/ims/internal/IImsUt.aidl", "telephony/java/com/android/ims/internal/IImsUtListener.aidl", diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java index 4e095e3a700..51971072840 100644 --- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java @@ -19,6 +19,7 @@ package android.telephony.ims.feature; import android.app.PendingIntent; import android.os.Message; import android.os.RemoteException; +import android.telephony.ims.internal.stub.SmsImplBase; import com.android.ims.ImsCallProfile; import com.android.ims.internal.IImsCallSession; @@ -28,6 +29,7 @@ import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMMTelFeature; import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsRegistrationListener; +import com.android.ims.internal.IImsSmsListener; import com.android.ims.internal.IImsUt; import com.android.ims.internal.ImsCallSession; @@ -171,6 +173,42 @@ public class MMTelFeature extends ImsFeature { return MMTelFeature.this.getMultiEndpointInterface(); } } + + @Override + public void setSmsListener(IImsSmsListener l) throws RemoteException { + synchronized (mLock) { + MMTelFeature.this.setSmsListener(l); + } + } + + @Override + public void sendSms(int token, int messageRef, String format, String smsc, boolean retry, + byte[] pdu) { + synchronized (mLock) { + MMTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu); + } + } + + @Override + public void acknowledgeSms(int token, int messageRef, int result) { + synchronized (mLock) { + MMTelFeature.this.acknowledgeSms(token, messageRef, result); + } + } + + @Override + public void acknowledgeSmsReport(int token, int messageRef, int result) { + synchronized (mLock) { + MMTelFeature.this.acknowledgeSmsReport(token, messageRef, result); + } + } + + @Override + public String getSmsFormat() { + synchronized (mLock) { + return MMTelFeature.this.getSmsFormat(); + } + } }; /** @@ -346,6 +384,39 @@ public class MMTelFeature extends ImsFeature { return null; } + public void setSmsListener(IImsSmsListener listener) { + getSmsImplementation().registerSmsListener(listener); + } + + public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, + byte[] pdu) { + getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu); + } + + public void acknowledgeSms(int token, int messageRef, + @SmsImplBase.DeliverStatusResult int result) { + getSmsImplementation().acknowledgeSms(token, messageRef, result); + } + + public void acknowledgeSmsReport(int token, int messageRef, + @SmsImplBase.StatusReportResult int result) { + getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); + } + + /** + * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default + * non-functional implementation is returned. + * + * @return an instance of {@link SmsImplBase} which should be implemented by the IMS Provider. + */ + protected SmsImplBase getSmsImplementation() { + return new SmsImplBase(); + } + + public String getSmsFormat() { + return getSmsImplementation().getSmsFormat(); + } + @Override public void onFeatureReady() { diff --git a/telephony/java/android/telephony/ims/internal/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/SmsImplBase.java index eb805a88da8..33b23d94ad3 100644 --- a/telephony/java/android/telephony/ims/internal/SmsImplBase.java +++ b/telephony/java/android/telephony/ims/internal/SmsImplBase.java @@ -17,7 +17,6 @@ package android.telephony.ims.internal; import android.annotation.IntDef; -import android.annotation.SystemApi; import android.os.RemoteException; import android.telephony.SmsManager; import android.telephony.SmsMessage; @@ -37,6 +36,7 @@ import java.lang.annotation.RetentionPolicy; public class SmsImplBase { private static final String LOG_TAG = "SmsImplBase"; + /** @hide */ @IntDef({ SEND_STATUS_OK, SEND_STATUS_ERROR, @@ -68,6 +68,7 @@ public class SmsImplBase { */ public static final int SEND_STATUS_ERROR_FALLBACK = 4; + /** @hide */ @IntDef({ DELIVER_STATUS_OK, DELIVER_STATUS_ERROR @@ -84,6 +85,7 @@ public class SmsImplBase { */ public static final int DELIVER_STATUS_ERROR = 2; + /** @hide */ @IntDef({ STATUS_REPORT_STATUS_OK, STATUS_REPORT_STATUS_ERROR @@ -114,9 +116,9 @@ public class SmsImplBase { * @hide */ public final void registerSmsListener(IImsSmsListener listener) { - synchronized (mLock) { - mListener = listener; - } + synchronized (mLock) { + mListener = listener; + } } /** @@ -128,8 +130,8 @@ public class SmsImplBase { * callbacks for this specific message. * @param messageRef the message reference. * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and + * {@link SmsMessage#FORMAT_3GPP2}. * @param smsc the Short Message Service Center address. - * {@link SmsMessage#FORMAT_3GPP2}. * @param isRetry whether it is a retry of an already attempted message or not. * @param pdu PDUs representing the contents of the message. */ @@ -154,7 +156,7 @@ public class SmsImplBase { * @param messageRef the message reference */ public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) { - + Log.e(LOG_TAG, "acknowledgeSms() not implemented."); } /** @@ -168,7 +170,7 @@ public class SmsImplBase { * @param messageRef the message reference */ public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) { - + Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented."); } /** @@ -193,7 +195,6 @@ public class SmsImplBase { } try { mListener.onSmsReceived(token, format, pdu); - acknowledgeSms(token, 0, DELIVER_STATUS_OK); } catch (RemoteException e) { Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage()); acknowledgeSms(token, 0, DELIVER_STATUS_ERROR); diff --git a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java new file mode 100644 index 00000000000..113dad4696e --- /dev/null +++ b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.telephony.ims.internal.stub; + +import android.annotation.IntDef; +import android.os.RemoteException; +import android.telephony.SmsManager; +import android.telephony.SmsMessage; +import android.telephony.ims.internal.feature.MmTelFeature; +import android.util.Log; + +import com.android.ims.internal.IImsSmsListener; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Base implementation for SMS over IMS. + * + * Any service wishing to provide SMS over IMS should extend this class and implement all methods + * that the service supports. + * @hide + */ +public class SmsImplBase { + private static final String LOG_TAG = "SmsImplBase"; + + @IntDef({ + SEND_STATUS_OK, + SEND_STATUS_ERROR, + SEND_STATUS_ERROR_RETRY, + SEND_STATUS_ERROR_FALLBACK + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SendStatusResult {} + /** + * Message was sent successfully. + */ + public static final int SEND_STATUS_OK = 1; + + /** + * IMS provider failed to send the message and platform should not retry falling back to sending + * the message using the radio. + */ + public static final int SEND_STATUS_ERROR = 2; + + /** + * IMS provider failed to send the message and platform should retry again after setting TP-RD bit + * to high. + */ + public static final int SEND_STATUS_ERROR_RETRY = 3; + + /** + * IMS provider failed to send the message and platform should retry falling back to sending + * the message using the radio. + */ + public static final int SEND_STATUS_ERROR_FALLBACK = 4; + + @IntDef({ + DELIVER_STATUS_OK, + DELIVER_STATUS_ERROR + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeliverStatusResult {} + /** + * Message was delivered successfully. + */ + public static final int DELIVER_STATUS_OK = 1; + + /** + * Message was not delivered. + */ + public static final int DELIVER_STATUS_ERROR = 2; + + @IntDef({ + STATUS_REPORT_STATUS_OK, + STATUS_REPORT_STATUS_ERROR + }) + @Retention(RetentionPolicy.SOURCE) + public @interface StatusReportResult {} + + /** + * Status Report was set successfully. + */ + public static final int STATUS_REPORT_STATUS_OK = 1; + + /** + * Error while setting status report. + */ + public static final int STATUS_REPORT_STATUS_ERROR = 2; + + + // Lock for feature synchronization + private final Object mLock = new Object(); + private IImsSmsListener mListener; + + /** + * Registers a listener responsible for handling tasks like delivering messages. + * + * @param listener listener to register. + * + * @hide + */ + public final void registerSmsListener(IImsSmsListener listener) { + synchronized (mLock) { + mListener = listener; + } + } + + /** + * This method will be triggered by the platform when the user attempts to send an SMS. This + * method should be implemented by the IMS providers to provide implementation of sending an SMS + * over IMS. + * + * @param token unique token generated by the platform that should be used when triggering + * callbacks for this specific message. + * @param messageRef the message reference. + * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and + * {@link SmsMessage#FORMAT_3GPP2}. + * @param smsc the Short Message Service Center address. + * @param isRetry whether it is a retry of an already attempted message or not. + * @param pdu PDUs representing the contents of the message. + */ + public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, + byte[] pdu) { + // Base implementation returns error. Should be overridden. + try { + onSendSmsResult(token, messageRef, SEND_STATUS_ERROR, + SmsManager.RESULT_ERROR_GENERIC_FAILURE); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Can not send sms: " + e.getMessage()); + } + } + + /** + * This method will be triggered by the platform after {@link #onSmsReceived(int, String, byte[])} + * has been called to deliver the result to the IMS provider. + * + * @param token token provided in {@link #onSmsReceived(int, String, byte[])} + * @param result result of delivering the message. Valid values are defined in + * {@link DeliverStatusResult} + * @param messageRef the message reference + */ + public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) { + Log.e(LOG_TAG, "acknowledgeSms() not implemented."); + } + + /** + * This method will be triggered by the platform after + * {@link #onSmsStatusReportReceived(int, int, String, byte[])} has been called to provide the + * result to the IMS provider. + * + * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} + * @param result result of delivering the message. Valid values are defined in + * {@link StatusReportResult} + * @param messageRef the message reference + */ + public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) { + Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented."); + } + + /** + * This method should be triggered by the IMS providers when there is an incoming message. The + * platform will deliver the message to the messages database and notify the IMS provider of the + * result by calling {@link #acknowledgeSms(int, int, int)}. + * + * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called. + * + * @param token unique token generated by IMS providers that the platform will use to trigger + * callbacks for this message. + * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and + * {@link SmsMessage#FORMAT_3GPP2}. + * @param pdu PDUs representing the contents of the message. + * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} + */ + public final void onSmsReceived(int token, String format, byte[] pdu) + throws IllegalStateException { + synchronized (mLock) { + if (mListener == null) { + throw new IllegalStateException("Feature not ready."); + } + try { + mListener.onSmsReceived(token, format, pdu); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage()); + acknowledgeSms(token, 0, DELIVER_STATUS_ERROR); + } + } + } + + /** + * This method should be triggered by the IMS providers to pass the result of the sent message + * to the platform. + * + * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called. + * + * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} + * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040 + * @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult} + * @param reason reason in case status is failure. Valid values are: + * {@link SmsManager#RESULT_ERROR_NONE}, + * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE}, + * {@link SmsManager#RESULT_ERROR_RADIO_OFF}, + * {@link SmsManager#RESULT_ERROR_NULL_PDU}, + * {@link SmsManager#RESULT_ERROR_NO_SERVICE}, + * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED}, + * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED}, + * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED} + * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} + * @throws RemoteException if the connection to the framework is not available. If this happens + * attempting to send the SMS should be aborted. + */ + public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status, + int reason) throws IllegalStateException, RemoteException { + synchronized (mLock) { + if (mListener == null) { + throw new IllegalStateException("Feature not ready."); + } + mListener.onSendSmsResult(token, messageRef, status, reason); + } + } + + /** + * Sets the status report of the sent message. + * + * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} + * @param messageRef the message reference. + * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and + * {@link SmsMessage#FORMAT_3GPP2}. + * @param pdu PDUs representing the content of the status report. + * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} + */ + public final void onSmsStatusReportReceived(int token, int messageRef, String format, + byte[] pdu) { + synchronized (mLock) { + if (mListener == null) { + throw new IllegalStateException("Feature not ready."); + } + try { + mListener.onSmsStatusReportReceived(token, messageRef, format, pdu); + } catch (RemoteException e) { + Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage()); + acknowledgeSmsReport(token, messageRef, STATUS_REPORT_STATUS_ERROR); + } + } + } + + /** + * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS + * Provider. + * + * @return the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and + * {@link SmsMessage#FORMAT_3GPP2}. + */ + public String getSmsFormat() { + return SmsMessage.FORMAT_3GPP; + } +} diff --git a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl index 52b3853ec5b..cce39f4daf2 100644 --- a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl +++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl @@ -25,6 +25,7 @@ import com.android.ims.internal.IImsConfig; import com.android.ims.internal.IImsEcbm; import com.android.ims.internal.IImsMultiEndpoint; import com.android.ims.internal.IImsRegistrationListener; +import com.android.ims.internal.IImsSmsListener; import com.android.ims.internal.IImsUt; import android.os.Message; @@ -53,4 +54,11 @@ interface IImsMMTelFeature { IImsEcbm getEcbmInterface(); void setUiTTYMode(int uiTtyMode, in Message onComplete); IImsMultiEndpoint getMultiEndpointInterface(); + // SMS APIs + void setSmsListener(IImsSmsListener l); + oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry, + in byte[] pdu); + oneway void acknowledgeSms(int token, int messageRef, int result); + oneway void acknowledgeSmsReport(int token, int messageRef, int result); + String getSmsFormat(); } diff --git a/telephony/java/com/android/ims/internal/IImsSmsListener.aidl b/telephony/java/com/android/ims/internal/IImsSmsListener.aidl new file mode 100644 index 00000000000..5a4b7e48902 --- /dev/null +++ b/telephony/java/com/android/ims/internal/IImsSmsListener.aidl @@ -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.ims.internal; + +/** + * See SmsImplBase for more information. + * {@hide} + */ +interface IImsSmsListener { + void onSendSmsResult(int token, int messageRef, int status, int reason); + void onSmsStatusReportReceived(int token, int messageRef, in String format, + in byte[] pdu); + void onSmsReceived(int token, in String format, in byte[] pdu); +} \ No newline at end of file -- GitLab From d8be4a0abe7e2439813b384482346d1ccf11ef53 Mon Sep 17 00:00:00 2001 From: John Reck Date: Fri, 17 Nov 2017 15:06:24 -0800 Subject: [PATCH 184/416] Add API to set tonal shadow color Bug: 68211332 Test: HwAccelerationTests's coloredshadow demo & CTS test in topic Change-Id: I09f5d1067b3200564a9d47219f70985edf3a2527 --- api/current.txt | 6 ++ core/java/android/view/RenderNode.java | 29 +++++++-- core/java/android/view/View.java | 63 ++++++++++++++++++- core/jni/android_view_RenderNode.cpp | 26 +++++++- core/res/res/values/attrs.xml | 22 +++++++ core/res/res/values/public.xml | 2 + libs/hwui/RenderProperties.h | 19 ++++-- .../pipeline/skia/ReorderBarrierDrawables.cpp | 8 ++- tests/HwAccelerationTest/AndroidManifest.xml | 3 +- .../HwAccelerationTest/res/values/styles.xml | 7 +++ .../test/hwui/ColoredShadowsActivity.java | 30 +++++++-- 11 files changed, 193 insertions(+), 22 deletions(-) diff --git a/api/current.txt b/api/current.txt index e85beab85e7..0fabba2bbe6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -968,7 +968,9 @@ package android { field public static final int orderingFromXml = 16843239; // 0x10101e7 field public static final int orientation = 16842948; // 0x10100c4 field public static final int outAnimation = 16843128; // 0x1010178 + field public static final int outlineAmbientShadowColor = 16844162; // 0x1010582 field public static final int outlineProvider = 16843960; // 0x10104b8 + field public static final int outlineSpotShadowColor = 16844161; // 0x1010581 field public static final int overScrollFooter = 16843459; // 0x10102c3 field public static final int overScrollHeader = 16843458; // 0x10102c2 field public static final int overScrollMode = 16843457; // 0x10102c1 @@ -46716,7 +46718,9 @@ package android.view { method public int getNextFocusRightId(); method public int getNextFocusUpId(); method public android.view.View.OnFocusChangeListener getOnFocusChangeListener(); + method public int getOutlineAmbientShadowColor(); method public android.view.ViewOutlineProvider getOutlineProvider(); + method public int getOutlineSpotShadowColor(); method public int getOverScrollMode(); method public android.view.ViewOverlay getOverlay(); method public int getPaddingBottom(); @@ -47038,7 +47042,9 @@ package android.view { method public void setOnScrollChangeListener(android.view.View.OnScrollChangeListener); method public void setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener); method public void setOnTouchListener(android.view.View.OnTouchListener); + method public void setOutlineAmbientShadowColor(int); method public void setOutlineProvider(android.view.ViewOutlineProvider); + method public void setOutlineSpotShadowColor(int); method public void setOverScrollMode(int); method public void setPadding(int, int, int, int); method public void setPaddingRelative(int, int, int, int); diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index 5070151815f..ce7e8f3b55b 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -353,9 +353,24 @@ public class RenderNode { return nHasShadow(mNativeRenderNode); } - /** setShadowColor */ - public boolean setShadowColor(int color) { - return nSetShadowColor(mNativeRenderNode, color); + /** setSpotShadowColor */ + public boolean setSpotShadowColor(int color) { + return nSetSpotShadowColor(mNativeRenderNode, color); + } + + /** setAmbientShadowColor */ + public boolean setAmbientShadowColor(int color) { + return nSetAmbientShadowColor(mNativeRenderNode, color); + } + + /** getSpotShadowColor */ + public int getSpotShadowColor() { + return nGetSpotShadowColor(mNativeRenderNode); + } + + /** getAmbientShadowColor */ + public int getAmbientShadowColor() { + return nGetAmbientShadowColor(mNativeRenderNode); } /** @@ -915,7 +930,13 @@ public class RenderNode { @CriticalNative private static native boolean nHasShadow(long renderNode); @CriticalNative - private static native boolean nSetShadowColor(long renderNode, int color); + private static native boolean nSetSpotShadowColor(long renderNode, int color); + @CriticalNative + private static native boolean nSetAmbientShadowColor(long renderNode, int color); + @CriticalNative + private static native int nGetSpotShadowColor(long renderNode); + @CriticalNative + private static native int nGetAmbientShadowColor(long renderNode); @CriticalNative private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline); @CriticalNative diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a2ecfc46918..c63cb404189 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_nextFocusRight * @attr ref android.R.styleable#View_nextFocusUp * @attr ref android.R.styleable#View_onClick + * @attr ref android.R.styleable#View_outlineSpotShadowColor + * @attr ref android.R.styleable#View_outlineAmbientShadowColor * @attr ref android.R.styleable#View_padding * @attr ref android.R.styleable#View_paddingHorizontal * @attr ref android.R.styleable#View_paddingVertical @@ -5443,6 +5445,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setAccessibilityPaneTitle(a.getString(attr)); } break; + case R.styleable.View_outlineSpotShadowColor: + setOutlineSpotShadowColor(a.getColor(attr, Color.BLACK)); + break; + case R.styleable.View_outlineAmbientShadowColor: + setOutlineAmbientShadowColor(a.getColor(attr, Color.BLACK)); + break; } } @@ -15442,14 +15450,61 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * @hide + * Sets the color of the spot shadow that is drawn when the view has a positive Z or + * elevation value. + *

    + * By default the shadow color is black. Generally, this color will be opaque so the intensity + * of the shadow is consistent between different views with different colors. + *

    + * The opacity of the final spot shadow is a function of the shadow caster height, the + * alpha channel of the outlineSpotShadowColor (typically opaque), and the + * {@link android.R.attr#spotShadowAlpha} theme attribute. + * + * @attr ref android.R.styleable#View_outlineSpotShadowColor + * @param color The color this View will cast for its elevation spot shadow. */ - public void setShadowColor(@ColorInt int color) { - if (mRenderNode.setShadowColor(color)) { + public void setOutlineSpotShadowColor(@ColorInt int color) { + if (mRenderNode.setSpotShadowColor(color)) { invalidateViewProperty(true, true); } } + /** + * @return The shadow color set by {@link #setOutlineSpotShadowColor(int)}, or black if nothing + * was set + */ + public @ColorInt int getOutlineSpotShadowColor() { + return mRenderNode.getSpotShadowColor(); + } + + /** + * Sets the color of the ambient shadow that is drawn when the view has a positive Z or + * elevation value. + *

    + * By default the shadow color is black. Generally, this color will be opaque so the intensity + * of the shadow is consistent between different views with different colors. + *

    + * The opacity of the final ambient shadow is a function of the shadow caster height, the + * alpha channel of the outlineAmbientShadowColor (typically opaque), and the + * {@link android.R.attr#ambientShadowAlpha} theme attribute. + * + * @attr ref android.R.styleable#View_outlineAmbientShadowColor + * @param color The color this View will cast for its elevation shadow. + */ + public void setOutlineAmbientShadowColor(@ColorInt int color) { + if (mRenderNode.setAmbientShadowColor(color)) { + invalidateViewProperty(true, true); + } + } + + /** + * @return The shadow color set by {@link #setOutlineAmbientShadowColor(int)}, or black if + * nothing was set + */ + public @ColorInt int getOutlineAmbientShadowColor() { + return mRenderNode.getAmbientShadowColor(); + } + /** @hide */ public void setRevealClip(boolean shouldClip, float x, float y, float radius) { @@ -26898,6 +26953,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, stream.addProperty("drawing:willNotCacheDrawing", willNotCacheDrawing()); stream.addProperty("drawing:drawingCacheEnabled", isDrawingCacheEnabled()); stream.addProperty("drawing:overlappingRendering", hasOverlappingRendering()); + stream.addProperty("drawing:outlineAmbientShadowColor", getOutlineAmbientShadowColor()); + stream.addProperty("drawing:outlineSpotShadowColor", getOutlineSpotShadowColor()); // focus stream.addProperty("focus:hasFocus", hasFocus()); diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp index 37ff8c8cefd..8770d788fc3 100644 --- a/core/jni/android_view_RenderNode.cpp +++ b/core/jni/android_view_RenderNode.cpp @@ -174,8 +174,25 @@ static jboolean android_view_RenderNode_hasShadow(jlong renderNodePtr) { return renderNode->stagingProperties().hasShadow(); } -static jboolean android_view_RenderNode_setShadowColor(jlong renderNodePtr, jint shadowColor) { - return SET_AND_DIRTY(setShadowColor, static_cast(shadowColor), RenderNode::GENERIC); +static jboolean android_view_RenderNode_setSpotShadowColor(jlong renderNodePtr, jint shadowColor) { + return SET_AND_DIRTY(setSpotShadowColor, + static_cast(shadowColor), RenderNode::GENERIC); +} + +static jint android_view_RenderNode_getSpotShadowColor(jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast(renderNodePtr); + return renderNode->stagingProperties().getSpotShadowColor(); +} + +static jboolean android_view_RenderNode_setAmbientShadowColor(jlong renderNodePtr, + jint shadowColor) { + return SET_AND_DIRTY(setAmbientShadowColor, + static_cast(shadowColor), RenderNode::GENERIC); +} + +static jint android_view_RenderNode_getAmbientShadowColor(jlong renderNodePtr) { + RenderNode* renderNode = reinterpret_cast(renderNodePtr); + return renderNode->stagingProperties().getAmbientShadowColor(); } static jboolean android_view_RenderNode_setClipToOutline(jlong renderNodePtr, @@ -575,7 +592,10 @@ static const JNINativeMethod gMethods[] = { { "nSetOutlineEmpty", "(J)Z", (void*) android_view_RenderNode_setOutlineEmpty }, { "nSetOutlineNone", "(J)Z", (void*) android_view_RenderNode_setOutlineNone }, { "nHasShadow", "(J)Z", (void*) android_view_RenderNode_hasShadow }, - { "nSetShadowColor", "(JI)Z", (void*) android_view_RenderNode_setShadowColor }, + { "nSetSpotShadowColor", "(JI)Z", (void*) android_view_RenderNode_setSpotShadowColor }, + { "nGetSpotShadowColor", "(J)I", (void*) android_view_RenderNode_getSpotShadowColor }, + { "nSetAmbientShadowColor","(JI)Z", (void*) android_view_RenderNode_setAmbientShadowColor }, + { "nGetAmbientShadowColor","(J)I", (void*) android_view_RenderNode_getAmbientShadowColor }, { "nSetClipToOutline", "(JZ)Z", (void*) android_view_RenderNode_setClipToOutline }, { "nSetRevealClip", "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip }, diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 354d6581116..ef339a6517a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3036,6 +3036,28 @@ + + + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 9cdf5531225..bb7e0409452 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2866,6 +2866,8 @@ + + diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index 3d2c2520624..55f4d895265 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -507,12 +507,20 @@ public: getOutline().getAlpha() != 0.0f; } - SkColor getShadowColor() const { - return mPrimitiveFields.mShadowColor; + SkColor getSpotShadowColor() const { + return mPrimitiveFields.mSpotShadowColor; } - bool setShadowColor(SkColor shadowColor) { - return RP_SET(mPrimitiveFields.mShadowColor, shadowColor); + bool setSpotShadowColor(SkColor shadowColor) { + return RP_SET(mPrimitiveFields.mSpotShadowColor, shadowColor); + } + + SkColor getAmbientShadowColor() const { + return mPrimitiveFields.mAmbientShadowColor; + } + + bool setAmbientShadowColor(SkColor shadowColor) { + return RP_SET(mPrimitiveFields.mAmbientShadowColor, shadowColor); } bool fitsOnLayer() const { @@ -538,7 +546,8 @@ private: int mLeft = 0, mTop = 0, mRight = 0, mBottom = 0; int mWidth = 0, mHeight = 0; int mClippingFlags = CLIP_TO_BOUNDS; - SkColor mShadowColor = SK_ColorBLACK; + SkColor mSpotShadowColor = SK_ColorBLACK; + SkColor mAmbientShadowColor = SK_ColorBLACK; float mAlpha = 1; float mTranslationX = 0, mTranslationY = 0, mTranslationZ = 0; float mElevation = 0; diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp index 7b59ccf3eec..25c51f2716e 100644 --- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp +++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp @@ -111,6 +111,10 @@ void EndReorderBarrierDrawable::onDraw(SkCanvas* canvas) { } } +static SkColor multiplyAlpha(SkColor color, float alpha) { + return SkColorSetA(color, alpha * SkColorGetA(color)); +} + // copied from FrameBuilder::deferShadow void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* caster) { const RenderProperties& casterProperties = caster->getNodeProperties(); @@ -187,9 +191,11 @@ void EndReorderBarrierDrawable::drawShadow(SkCanvas* canvas, RenderNodeDrawable* } else { zParams = SkPoint3::Make(0, 0, casterProperties.getZ()); } + SkColor ambientColor = multiplyAlpha(casterProperties.getAmbientShadowColor(), ambientAlpha); + SkColor spotColor = multiplyAlpha(casterProperties.getSpotShadowColor(), spotAlpha); SkShadowUtils::DrawShadow( canvas, *casterPath, zParams, skiaLightPos, SkiaPipeline::getLightRadius(), - ambientAlpha, spotAlpha, casterProperties.getShadowColor(), + ambientColor, spotColor, casterAlpha < 1.0f ? SkShadowFlags::kTransparentOccluder_ShadowFlag : 0); } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index ebf5f6854c6..c8f96c9f067 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -285,7 +285,8 @@ + android:label="View/ColoredShadows" + android:theme="@style/ThemeColoredShadows"> diff --git a/tests/HwAccelerationTest/res/values/styles.xml b/tests/HwAccelerationTest/res/values/styles.xml index 108709bd76b..fa5437f38ac 100644 --- a/tests/HwAccelerationTest/res/values/styles.xml +++ b/tests/HwAccelerationTest/res/values/styles.xml @@ -34,4 +34,11 @@ 400dp true + + diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredShadowsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredShadowsActivity.java index 135c93c97af..901d90eed70 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredShadowsActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColoredShadowsActivity.java @@ -17,6 +17,9 @@ package com.android.test.hwui; import android.app.Activity; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; @@ -36,7 +39,9 @@ public class ColoredShadowsActivity extends Activity { private void setShadowColors(ViewGroup row, int rowIndex) { for (int i = 0; i < row.getChildCount(); i++) { View view = row.getChildAt(i); - view.setShadowColor(shadowColorFor(view)); + //view.setBackground(new MyHackyBackground()); + view.setOutlineSpotShadowColor(shadowColorFor(view)); + view.setOutlineAmbientShadowColor(shadowColorFor(view)); view.setElevation(6.0f * (rowIndex + 1)); } } @@ -44,12 +49,27 @@ public class ColoredShadowsActivity extends Activity { private int shadowColorFor(View view) { switch (view.getId()) { case R.id.grey: return 0xFF3C4043; - case R.id.blue: return 0xFF185ABC; - case R.id.red: return 0xFFB31412; - case R.id.yellow: return 0xFFEA8600; - case R.id.green: return 0xFF137333; + case R.id.blue: return Color.BLUE; + case R.id.red: return 0xFFEA4335; + case R.id.yellow: return 0xFFFBBC04; + case R.id.green: return 0xFF34A853; default: return 0xFF000000; } } + private static class MyHackyBackground extends ColorDrawable { + MyHackyBackground() { + super(0); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + @Override + public int getAlpha() { + return 254; + } + } } -- GitLab From eb5706183f62b9230fb1ae9eb22254a062e7869c Mon Sep 17 00:00:00 2001 From: Tarandeep Singh Date: Mon, 29 Jan 2018 16:20:32 -0800 Subject: [PATCH 185/416] Fix checks for showing InputMethod picker When user tries to switch IME, IMMS.showInputMethodPickerFromClient() is called. The call fails to validate in newly introduced canShowInputMethodPickerLocked() in I4f0fc21268200c64d12b31ca54416acfbf62f37b because mCurClient.client != client. This is happening since the new client never started input ever since we prevented calls to startInputUncheckedLocked in Ibf9dab3d9c138b5f04e053d41ee4fd248c78e4da. The fix is to update mCurFocusedWindowClient.client instead of mCurclient.client in canShowInputMethodPickerLocked() Fixes: 72557082 Test: manually using the steps in bug Test: atest InputMethodManagerTest Change-Id: I4e21625c32a0ca1abc740229efb3c7fcd97141cc --- api/test-current.txt | 8 +++++++ .../view/inputmethod/InputMethodManager.java | 21 +++++++++++++++++++ .../internal/view/IInputMethodManager.aidl | 1 + .../server/InputMethodManagerService.java | 16 +++++++++++--- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/api/test-current.txt b/api/test-current.txt index 4e8f904b96b..5ec0748beae 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1035,6 +1035,14 @@ package android.view.autofill { } +package android.view.inputmethod { + + public final class InputMethodManager { + method public boolean isInputMethodPickerShown(); + } + +} + package android.widget { public abstract class AbsListView extends android.widget.AdapterView implements android.widget.Filter.FilterListener android.text.TextWatcher android.view.ViewTreeObserver.OnGlobalLayoutListener android.view.ViewTreeObserver.OnTouchModeChangeListener { diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 7db5c320729..33c9f7a2671 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; @@ -2136,6 +2137,26 @@ public final class InputMethodManager { } } + /** + * A test API for CTS to make sure that {@link #showInputMethodPicker()} works as expected. + * + *

    When customizing the implementation of {@link #showInputMethodPicker()} API, make sure + * that this test API returns when and only while and only while + * {@link #showInputMethodPicker()} is showing UI. Otherwise your OS implementation may not + * pass CTS.

    + * + * @return {@code true} while and only while {@link #showInputMethodPicker()} is showing UI. + * @hide + */ + @TestApi + public boolean isInputMethodPickerShown() { + try { + return mService.isInputMethodPickerShownForTest(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Show the settings for enabling subtypes of the specified input method. * @param imiId An input method, whose subtypes settings will be shown. If imiId is null, diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 5e0a986b432..02822869f42 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -68,6 +68,7 @@ interface IInputMethodManager { void showInputMethodPickerFromClient(in IInputMethodClient client, int auxiliarySubtypeMode); void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId); + boolean isInputMethodPickerShownForTest(); void setInputMethod(in IBinder token, String id); void setInputMethodAndSubtype(in IBinder token, String id, in InputMethodSubtype subtype); void hideMySoftInput(in IBinder token, int flags); diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index fc91d0d7abf..2f425859fec 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -59,6 +59,7 @@ import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.TestApi; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; @@ -468,7 +469,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub int mCurFocusedWindowSoftInputMode; /** - * The client by which {@link #mCurFocusedWindow} was reported. Used only for debugging. + * The client by which {@link #mCurFocusedWindow} was reported. */ ClientState mCurFocusedWindowClient; @@ -2989,8 +2990,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final int uid = Binder.getCallingUid(); if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) { return true; - } else if (mCurClient != null && client != null - && mCurClient.client.asBinder() == client.asBinder()) { + } else if (mCurFocusedWindowClient != null && client != null + && mCurFocusedWindowClient.client.asBinder() == client.asBinder()) { return true; } else if (mCurIntent != null && InputMethodUtils.checkIfPackageBelongsToUid( mAppOpsManager, @@ -3026,6 +3027,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + public boolean isInputMethodPickerShownForTest() { + synchronized(mMethodMap) { + if (mSwitchingDialog == null) { + return false; + } + return mSwitchingDialog.isShowing(); + } + } + @Override public void setInputMethod(IBinder token, String id) { if (!calledFromValidUser()) { -- GitLab From 4bd8e05c1dbae46d94b731241252ddccff6d977c Mon Sep 17 00:00:00 2001 From: Amin Shaikh Date: Mon, 29 Jan 2018 09:52:15 -0500 Subject: [PATCH 186/416] Add alarm tile to QS. - Add an alarm tile to QS - Add the tile to QS the first time the user creates an alarm - Tapping on the tile navigates to alarm settings - Added unit tests for alarm tile - Updated AutoAddTracker to remove deprecated shared preferences values so the keys can be removed in a later release Bug: 70799533 Test: manual testing the alarm QS tile behavior Change-Id: I2b10468c41b4720b66c9e7bb32e22eb958c199f7 --- packages/SystemUI/res/values/config.xml | 2 +- packages/SystemUI/res/values/strings.xml | 2 + .../android/systemui/qs/AutoAddTracker.java | 3 +- .../systemui/qs/tileimpl/QSFactoryImpl.java | 2 + .../android/systemui/qs/tiles/AlarmTile.java | 102 ++++++++++++++++++ .../statusbar/phone/AutoTileManager.java | 45 ++++++-- .../systemui/qs/AutoAddTrackerTest.java | 10 +- .../statusbar/phone/AutoTileManagerTest.java | 63 ++++++++--- proto/src/metrics_constants.proto | 7 ++ 9 files changed, 206 insertions(+), 30 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 1cc1cc88326..ae910fe5708 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -124,7 +124,7 @@ - wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night + wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night,alarm diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 72615ce690e..5557f8eda00 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -739,6 +739,8 @@ Wi-Fi On No Wi-Fi networks available + + Alarm Cast diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java index f960dc5b4a4..2a2bc0923e1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java +++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java @@ -51,10 +51,11 @@ public class AutoAddTracker { public AutoAddTracker(Context context) { mContext = context; mAutoAdded = new ArraySet<>(getAdded()); + // TODO: remove migration code and shared preferences keys after P release for (String[] convertPref : CONVERT_PREFS) { if (Prefs.getBoolean(context, convertPref[0], false)) { setTileAdded(convertPref[1]); - Prefs.putBoolean(context, convertPref[0], false); + Prefs.remove(context, convertPref[0]); } } mContext.getContentResolver().registerContentObserver( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java index 77c3bfab8de..bf9746eafaf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java @@ -23,6 +23,7 @@ import com.android.systemui.plugins.qs.*; import com.android.systemui.plugins.qs.QSTileView; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tiles.AirplaneModeTile; +import com.android.systemui.qs.tiles.AlarmTile; import com.android.systemui.qs.tiles.BatterySaverTile; import com.android.systemui.qs.tiles.BluetoothTile; import com.android.systemui.qs.tiles.CastTile; @@ -69,6 +70,7 @@ public class QSFactoryImpl implements QSFactory { else if (tileSpec.equals("saver")) return new DataSaverTile(mHost); else if (tileSpec.equals("night")) return new NightDisplayTile(mHost); else if (tileSpec.equals("nfc")) return new NfcTile(mHost); + else if (tileSpec.equals("alarm")) return new AlarmTile(mHost); // Intent tiles. else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(mHost, tileSpec); else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java new file mode 100644 index 00000000000..ff3fe731ad4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import static android.service.quicksettings.Tile.STATE_ACTIVE; +import static android.service.quicksettings.Tile.STATE_UNAVAILABLE; + +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.QS_ALARM; +import static com.android.systemui.keyguard.KeyguardSliceProvider.formatNextAlarm; + +import android.app.AlarmManager.AlarmClockInfo; +import android.app.PendingIntent; +import android.content.Intent; +import android.provider.AlarmClock; + +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.qs.QSTileHost; +import com.android.systemui.qs.tileimpl.QSTileImpl; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; + +public class AlarmTile extends QSTileImpl implements NextAlarmChangeCallback { + private final NextAlarmController mController; + private String mNextAlarm; + private PendingIntent mIntent; + + public AlarmTile(QSTileHost host) { + super(host); + mController = Dependency.get(NextAlarmController.class); + } + + @Override + public State newTileState() { + return new BooleanState(); + } + + @Override + protected void handleClick() { + if (mIntent != null) { + Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(mIntent); + } + } + + @Override + protected void handleUpdateState(State state, Object arg) { + state.state = mNextAlarm != null ? STATE_ACTIVE : STATE_UNAVAILABLE; + state.label = getTileLabel(); + state.secondaryLabel = mNextAlarm; + state.icon = ResourceIcon.get(R.drawable.stat_sys_alarm); + ((BooleanState) state).value = mNextAlarm != null; + } + + @Override + public void onNextAlarmChanged(AlarmClockInfo nextAlarm) { + if (nextAlarm != null) { + mNextAlarm = formatNextAlarm(mContext, nextAlarm); + mIntent = nextAlarm.getShowIntent(); + } else { + mNextAlarm = null; + mIntent = null; + } + refreshState(); + } + + @Override + public int getMetricsCategory() { + return QS_ALARM; + } + + @Override + public Intent getLongClickIntent() { + return new Intent(AlarmClock.ACTION_SET_ALARM); + } + + @Override + protected void handleSetListening(boolean listening) { + if (listening) { + mController.addCallback(this); + } else { + mController.removeCallback(this); + } + } + + @Override + public CharSequence getTileLabel() { + return mContext.getString(R.string.status_bar_alarm); + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index 36f9f6b7941..b220686b305 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -14,16 +14,13 @@ package com.android.systemui.statusbar.phone; +import android.app.AlarmManager.AlarmClockInfo; import android.content.Context; import android.os.Handler; -import android.os.Looper; import android.provider.Settings.Secure; - import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.ColorDisplayController; import com.android.systemui.Dependency; -import com.android.systemui.Prefs; -import com.android.systemui.Prefs.Key; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.SecureSetting; @@ -31,27 +28,37 @@ import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DataSaverController.Listener; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.HotspotController.Callback; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; /** * Manages which tiles should be automatically added to QS. */ public class AutoTileManager { - public static final String HOTSPOT = "hotspot"; public static final String SAVER = "saver"; public static final String INVERSION = "inversion"; public static final String WORK = "work"; public static final String NIGHT = "night"; + public static final String ALARM = "alarm"; + private final Context mContext; private final QSTileHost mHost; private final Handler mHandler; private final AutoAddTracker mAutoTracker; public AutoTileManager(Context context, QSTileHost host) { - mAutoTracker = new AutoAddTracker(context); + this(context, new AutoAddTracker(context), host, + new Handler(Dependency.get(Dependency.BG_LOOPER))); + } + + @VisibleForTesting + AutoTileManager(Context context, AutoAddTracker autoAddTracker, QSTileHost host, + Handler handler) { + mAutoTracker = autoAddTracker; mContext = context; mHost = host; - mHandler = new Handler((Looper) Dependency.get(Dependency.BG_LOOPER)); + mHandler = handler; if (!mAutoTracker.isAdded(HOTSPOT)) { Dependency.get(HotspotController.class).addCallback(mHotspotCallback); } @@ -76,20 +83,25 @@ public class AutoTileManager { if (!mAutoTracker.isAdded(WORK)) { Dependency.get(ManagedProfileController.class).addCallback(mProfileCallback); } - if (!mAutoTracker.isAdded(NIGHT) - && ColorDisplayController.isAvailable(mContext)) { + && ColorDisplayController.isAvailable(mContext)) { Dependency.get(ColorDisplayController.class).setListener(mColorDisplayCallback); } + if (!mAutoTracker.isAdded(ALARM)) { + Dependency.get(NextAlarmController.class).addCallback(mNextAlarmChangeCallback); + } } public void destroy() { - mColorsSetting.setListening(false); + if (mColorsSetting != null) { + mColorsSetting.setListening(false); + } mAutoTracker.destroy(); Dependency.get(HotspotController.class).removeCallback(mHotspotCallback); Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener); Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback); Dependency.get(ColorDisplayController.class).setListener(null); + Dependency.get(NextAlarmController.class).removeCallback(mNextAlarmChangeCallback); } private final ManagedProfileController.Callback mProfileCallback = @@ -138,6 +150,19 @@ public class AutoTileManager { } }; + private final NextAlarmChangeCallback mNextAlarmChangeCallback = new NextAlarmChangeCallback() { + @Override + public void onNextAlarmChanged(AlarmClockInfo nextAlarm) { + if (mAutoTracker.isAdded(ALARM)) return; + if (nextAlarm != null) { + mHost.addTile(ALARM); + mAutoTracker.setTileAdded(ALARM); + mHandler.post(() -> Dependency.get(NextAlarmController.class) + .removeCallback(mNextAlarmChangeCallback)); + } + } + }; + @VisibleForTesting final ColorDisplayController.Callback mColorDisplayCallback = new ColorDisplayController.Callback() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java index 40f8059fecb..dfc1852502e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java @@ -30,6 +30,7 @@ import com.android.systemui.Prefs; import com.android.systemui.Prefs.Key; import com.android.systemui.SysuiTestCase; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +41,11 @@ public class AutoAddTrackerTest extends SysuiTestCase { private AutoAddTracker mAutoTracker; + @Before + public void setUp() { + Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, ""); + } + @Test public void testMigration() { Prefs.putBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true); @@ -50,7 +56,10 @@ public class AutoAddTrackerTest extends SysuiTestCase { assertTrue(mAutoTracker.isAdded(WORK)); assertFalse(mAutoTracker.isAdded(INVERSION)); + // These keys have been removed; retrieving their values should always return the default. + assertTrue(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true )); assertFalse(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, false)); + assertTrue(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, true)); assertFalse(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, false)); mAutoTracker.destroy(); @@ -96,5 +105,4 @@ public class AutoAddTrackerTest extends SysuiTestCase { mAutoTracker.destroy(); } - } \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index a95e3dbb1e3..2d2db1bba73 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -18,45 +18,48 @@ package com.android.systemui.statusbar.phone; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.app.AlarmManager.AlarmClockInfo; +import android.os.Handler; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; - import com.android.internal.app.ColorDisplayController; import com.android.systemui.Dependency; -import com.android.systemui.Prefs; -import com.android.systemui.Prefs.Key; import com.android.systemui.SysuiTestCase; +import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSTileHost; - -import org.junit.After; +import com.android.systemui.statusbar.policy.NextAlarmController; +import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest public class AutoTileManagerTest extends SysuiTestCase { - private QSTileHost mQsTileHost; + @Mock private QSTileHost mQsTileHost; + @Mock private AutoAddTracker mAutoAddTracker; + @Captor private ArgumentCaptor mAlarmCallback; + private AutoTileManager mAutoTileManager; @Before public void setUp() throws Exception { - mDependency.injectTestDependency(Dependency.BG_LOOPER, - TestableLooper.get(this).getLooper()); - Prefs.putBoolean(mContext, Key.QS_NIGHTDISPLAY_ADDED, false); - mQsTileHost = Mockito.mock(QSTileHost.class); - mAutoTileManager = new AutoTileManager(mContext, mQsTileHost); - } - - @After - public void tearDown() throws Exception { - mAutoTileManager = null; + MockitoAnnotations.initMocks(this); + mDependency.injectMockDependency(NextAlarmController.class); + mAutoTileManager = new AutoTileManager(mContext, mAutoAddTracker, + mQsTileHost, new Handler(TestableLooper.get(this).getLooper())); + verify(Dependency.get(NextAlarmController.class)) + .addCallback(mAlarmCallback.capture()); } @Test @@ -106,4 +109,30 @@ public class AutoTileManagerTest extends SysuiTestCase { ColorDisplayController.AUTO_MODE_DISABLED); verify(mQsTileHost, never()).addTile("night"); } + + @Test + public void alarmTileAdded_whenAlarmSet() { + mAlarmCallback.getValue().onNextAlarmChanged(new AlarmClockInfo(0, null)); + + verify(mQsTileHost).addTile("alarm"); + verify(mAutoAddTracker).setTileAdded("alarm"); + } + + @Test + public void alarmTileNotAdded_whenAlarmNotSet() { + mAlarmCallback.getValue().onNextAlarmChanged(null); + + verify(mQsTileHost, never()).addTile("alarm"); + verify(mAutoAddTracker, never()).setTileAdded("alarm"); + } + + @Test + public void alarmTileNotAdded_whenAlreadyAdded() { + when(mAutoAddTracker.isAdded("alarm")).thenReturn(true); + + mAlarmCallback.getValue().onNextAlarmChanged(new AlarmClockInfo(0, null)); + + verify(mQsTileHost, never()).addTile("alarm"); + verify(mAutoAddTracker, never()).setTileAdded("alarm"); + } } diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 7539d8847c2..a2d69a80b71 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5173,6 +5173,13 @@ message MetricsEvent { // OS: P AUTOFILL_INVALID_PERMISSION = 1289; + // OPEN: QS Alarm tile shown + // ACTION: QS Alarm tile tapped + // SUBTYPE: 0 is off, 1 is on + // CATEGORY: QUICK_SETTINGS + // OS: P + QS_ALARM = 1290; + // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS -- GitLab From 327b809ad11a5094248652014227470c4be329e6 Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Tue, 9 Jan 2018 17:53:04 -0800 Subject: [PATCH 187/416] Refactor and clean up USB, add tests Change UsbManager apis to use long instead of string, to match usb hal. Change UsbDeviceManager internals to match as well. Remove isFunctionEnabled and add getEnabledFunctions. Callers would often call isFunctionEnabled for every possible function to get the list of functions, so getEnabledFunctions reduces the number of aidl calls. Separate out dependencies between UsbHandler and UsbDeviceManager and staticize the UsbHandler classes. Add unit tests with mocked out dependencies to test state transitions for UsbHandler. Bug: 62876645 Test: atest UsbTests Change-Id: I785c4c24121a70e725de9742c6af50a6bf1baea0 --- Android.bp | 1 + .../com/android/commands/svc/UsbCommand.java | 43 +- .../android/hardware/usb/IUsbManager.aidl | 18 +- .../java/android/hardware/usb/UsbManager.java | 260 ++++-- .../server/connectivity/Tethering.java | 3 +- .../android/server/usb/UsbDeviceManager.java | 786 +++++++++--------- .../com/android/server/usb/UsbService.java | 65 +- tests/UsbTests/Android.mk | 44 + tests/UsbTests/AndroidManifest.xml | 30 + tests/UsbTests/AndroidTest.xml | 29 + .../android/server/usb/UsbHandlerTest.java | 316 +++++++ .../server/connectivity/TetheringTest.java | 2 +- 12 files changed, 1065 insertions(+), 532 deletions(-) create mode 100644 tests/UsbTests/Android.mk create mode 100644 tests/UsbTests/AndroidManifest.xml create mode 100644 tests/UsbTests/AndroidTest.xml create mode 100644 tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java diff --git a/Android.bp b/Android.bp index 129d6769cb9..d5e04f9f4e2 100644 --- a/Android.bp +++ b/Android.bp @@ -676,6 +676,7 @@ java_library { "android.hardware.vibrator-V1.1-java-constants", "android.hardware.wifi-V1.0-java-constants", "android.hardware.radio-V1.0-java", + "android.hardware.usb.gadget-V1.0-java", ], // Loaded with System.loadLibrary by android.view.textclassifier diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java index 34f6d7de0cc..3893be49e73 100644 --- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java +++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java @@ -21,7 +21,6 @@ import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbManager; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; public class UsbCommand extends Svc.Command { public UsbCommand() { @@ -37,41 +36,41 @@ public class UsbCommand extends Svc.Command { public String longHelp() { return shortHelp() + "\n" + "\n" - + "usage: svc usb setFunction [function] [usbDataUnlocked=false]\n" - + " Set the current usb function and optionally the data lock state.\n\n" + + "usage: svc usb setFunctions [function]\n" + + " Set the current usb function. If function is blank, sets to charging.\n" + " svc usb setScreenUnlockedFunctions [function]\n" - + " Sets the functions which, if the device was charging," - + " become current on screen unlock.\n" - + " svc usb getFunction\n" - + " Gets the list of currently enabled functions\n"; + + " Sets the functions which, if the device was charging, become current on" + + "screen unlock. If function is blank, turn off this feature.\n" + + " svc usb getFunctions\n" + + " Gets the list of currently enabled functions\n\n" + + "possible values of [function] are any of 'mtp', 'ptp', 'rndis', 'midi'\n"; } @Override public void run(String[] args) { - boolean validCommand = false; if (args.length >= 2) { - if ("setFunction".equals(args[1])) { - IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService( - Context.USB_SERVICE)); - boolean unlockData = false; - if (args.length >= 4) { - unlockData = Boolean.valueOf(args[3]); - } + IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService( + Context.USB_SERVICE)); + if ("setFunctions".equals(args[1])) { try { - usbMgr.setCurrentFunction((args.length >=3 ? args[2] : null), unlockData); + usbMgr.setCurrentFunctions(UsbManager.usbFunctionsFromString( + args.length >= 3 ? args[2] : "")); } catch (RemoteException e) { System.err.println("Error communicating with UsbManager: " + e); } return; - } else if ("getFunction".equals(args[1])) { - System.err.println(SystemProperties.get("sys.usb.config")); + } else if ("getFunctions".equals(args[1])) { + try { + System.err.println( + UsbManager.usbFunctionsToString(usbMgr.getCurrentFunctions())); + } catch (RemoteException e) { + System.err.println("Error communicating with UsbManager: " + e); + } return; } else if ("setScreenUnlockedFunctions".equals(args[1])) { - IUsbManager usbMgr = IUsbManager.Stub.asInterface(ServiceManager.getService( - Context.USB_SERVICE)); try { - usbMgr.setScreenUnlockedFunctions((args.length >= 3 ? args[2] : - UsbManager.USB_FUNCTION_NONE)); + usbMgr.setScreenUnlockedFunctions(UsbManager.usbFunctionsFromString( + args.length >= 3 ? args[2] : "")); } catch (RemoteException e) { System.err.println("Error communicating with UsbManager: " + e); } diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl index 398dda174ba..91bbdc7dde8 100644 --- a/core/java/android/hardware/usb/IUsbManager.aidl +++ b/core/java/android/hardware/usb/IUsbManager.aidl @@ -88,18 +88,22 @@ interface IUsbManager /* Returns true if the specified USB function is enabled. */ boolean isFunctionEnabled(String function); - /* Sets the current USB function as well as whether USB data - * (for example, MTP exposed pictures) should be made available - * on the USB connection. Unlocking data should only be done with - * user involvement, since exposing pictures or other data could - * leak sensitive user information. - */ + /* Sets the current USB function. */ + void setCurrentFunctions(long functions); + + /* Compatibility version of setCurrentFunctions(long). */ void setCurrentFunction(String function, boolean usbDataUnlocked); + /* Gets the current USB functions. */ + long getCurrentFunctions(); + /* Sets the screen unlocked USB function(s), which will be set automatically * when the screen is unlocked. */ - void setScreenUnlockedFunctions(String function); + void setScreenUnlockedFunctions(long functions); + + /* Gets the current screen unlocked functions. */ + long getScreenUnlockedFunctions(); /* Allow USB debugging from the attached host. If alwaysAllow is true, add the * the public key to list of host keys that the user has approved. diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 7617c2bd196..8daecac5a10 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -28,6 +28,7 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; +import android.hardware.usb.gadget.V1_0.GadgetFunction; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.Process; @@ -37,6 +38,8 @@ import android.util.Log; import com.android.internal.util.Preconditions; import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; /** * This class allows you to access the state of USB and communicate with USB devices. @@ -70,7 +73,7 @@ public class UsbManager { * MTP function is enabled *
  • {@link #USB_FUNCTION_PTP} boolean extra indicating whether the * PTP function is enabled - *
  • {@link #USB_FUNCTION_PTP} boolean extra indicating whether the + *
  • {@link #USB_FUNCTION_ACCESSORY} boolean extra indicating whether the * accessory function is enabled *
  • {@link #USB_FUNCTION_AUDIO_SOURCE} boolean extra indicating whether the * audio source function is enabled @@ -196,8 +199,7 @@ public class UsbManager { /** * A placeholder indicating that no USB function is being specified. - * Used to distinguish between selecting no function vs. the default function in - * {@link #setCurrentFunction(String)}. + * Used for compatibility with old init scripts to indicate no functions vs. charging function. * * {@hide} */ @@ -298,6 +300,69 @@ public class UsbManager { */ public static final String EXTRA_PERMISSION_GRANTED = "permission"; + /** + * Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)} + * {@hide} + */ + public static final long FUNCTION_NONE = 0; + + /** + * Code for the mtp usb function. Passed as a mask into {@link #setCurrentFunctions(long)} + * {@hide} + */ + public static final long FUNCTION_MTP = GadgetFunction.MTP; + + /** + * Code for the ptp usb function. Passed as a mask into {@link #setCurrentFunctions(long)} + * {@hide} + */ + public static final long FUNCTION_PTP = GadgetFunction.PTP; + + /** + * Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)} + * {@hide} + */ + public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS; + + /** + * Code for the midi usb function. Passed as a mask into {@link #setCurrentFunctions(long)} + * {@hide} + */ + public static final long FUNCTION_MIDI = GadgetFunction.MIDI; + + /** + * Code for the accessory usb function. + * {@hide} + */ + public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY; + + /** + * Code for the audio source usb function. + * {@hide} + */ + public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE; + + /** + * Code for the adb usb function. + * {@hide} + */ + public static final long FUNCTION_ADB = GadgetFunction.ADB; + + private static final long SETTABLE_FUNCTIONS = FUNCTION_MTP | FUNCTION_PTP | FUNCTION_RNDIS + | FUNCTION_MIDI; + + private static final Map FUNCTION_NAME_TO_CODE = new HashMap<>(); + + static { + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MTP, FUNCTION_MTP); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_PTP, FUNCTION_PTP); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_RNDIS, FUNCTION_RNDIS); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MIDI, FUNCTION_MIDI); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_ACCESSORY, FUNCTION_ACCESSORY); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_AUDIO_SOURCE, FUNCTION_AUDIO_SOURCE); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_ADB, FUNCTION_ADB); + } + private final Context mContext; private final IUsbManager mService; @@ -548,15 +613,14 @@ public class UsbManager { * services offered by the device. *

    * + * @deprecated use getCurrentFunctions() instead. * @param function name of the USB function * @return true if the USB function is enabled * * {@hide} */ + @Deprecated public boolean isFunctionEnabled(String function) { - if (mService == null) { - return false; - } try { return mService.isFunctionEnabled(function); } catch (RemoteException e) { @@ -565,7 +629,7 @@ public class UsbManager { } /** - * Sets the current USB function when in device mode. + * Sets the current USB functions when in device mode. *

    * USB functions represent interfaces which are published to the host to access * services offered by the device. @@ -574,27 +638,59 @@ public class UsbManager { * automatically activate additional functions such as {@link #USB_FUNCTION_ADB} * or {@link #USB_FUNCTION_ACCESSORY} based on other settings and states. *

    - * The allowed values are: {@link #USB_FUNCTION_NONE}, {@link #USB_FUNCTION_AUDIO_SOURCE}, - * {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP}, - * or {@link #USB_FUNCTION_RNDIS}. - *

    - * Also sets whether USB data (for example, MTP exposed pictures) should be made available - * on the USB connection when in device mode. Unlocking usb data should only be done with - * user involvement, since exposing pictures or other data could leak sensitive - * user information. + * An argument of 0 indicates that the device is charging, and can pick any + * appropriate function for that purpose. *

    * Note: This function is asynchronous and may fail silently without applying * the requested changes. *

    * - * @param function name of the USB function, or null to restore the default function - * @param usbDataUnlocked whether user data is accessible + * @param functions the USB function(s) to set, as a bitwise mask. + * Must satisfy {@link UsbManager#areSettableFunctions} + * + * {@hide} + */ + public void setCurrentFunctions(long functions) { + try { + mService.setCurrentFunctions(functions); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Sets the current USB functions when in device mode. + * + * @deprecated use setCurrentFunctions(long) instead. + * @param functions the USB function(s) to set. + * @param usbDataUnlocked unused + + * {@hide} + */ + @Deprecated + public void setCurrentFunction(String functions, boolean usbDataUnlocked) { + try { + mService.setCurrentFunction(functions, usbDataUnlocked); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the current USB functions in device mode. + *

    + * This function returns the state of primary USB functions and can return a + * mask containing any usb function(s) except for ADB. + *

    + * + * @return The currently enabled functions, in a bitwise mask. + * A zero mask indicates that the current function is the charging function. * * {@hide} */ - public void setCurrentFunction(String function, boolean usbDataUnlocked) { + public long getCurrentFunctions() { try { - mService.setCurrentFunction(function, usbDataUnlocked); + return mService.getCurrentFunctions(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -604,23 +700,37 @@ public class UsbManager { * Sets the screen unlocked functions, which are persisted and set as the current functions * whenever the screen is unlocked. *

    - * The allowed values are: {@link #USB_FUNCTION_NONE}, - * {@link #USB_FUNCTION_MIDI}, {@link #USB_FUNCTION_MTP}, {@link #USB_FUNCTION_PTP}, - * or {@link #USB_FUNCTION_RNDIS}. - * {@link #USB_FUNCTION_NONE} has the effect of switching off this feature, so functions + * A zero mask has the effect of switching off this feature, so functions * no longer change on screen unlock. *

    * Note: When the screen is on, this method will apply given functions as current functions, * which is asynchronous and may fail silently without applying the requested changes. *

    * - * @param function function to set as default + * @param functions functions to set, in a bitwise mask. + * Must satisfy {@link UsbManager#areSettableFunctions} * * {@hide} */ - public void setScreenUnlockedFunctions(String function) { + public void setScreenUnlockedFunctions(long functions) { try { - mService.setScreenUnlockedFunctions(function); + mService.setScreenUnlockedFunctions(functions); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Gets the current screen unlocked functions. + * + * @return The currently set screen enabled functions. + * A zero mask indicates that the screen unlocked functions feature is not enabled. + * + * {@hide} + */ + public long getScreenUnlockedFunctions() { + try { + return mService.getScreenUnlockedFunctions(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -719,51 +829,71 @@ public class UsbManager { } } - /** @hide */ - public static String addFunction(String functions, String function) { - if (USB_FUNCTION_NONE.equals(functions)) { - return function; - } - if (!containsFunction(functions, function)) { - if (functions.length() > 0) { - functions += ","; - } - functions += function; - } - return functions; + /** + * Returns whether the given functions are valid inputs to UsbManager. + * Currently the empty functions or any of MTP, PTP, RNDIS, MIDI are accepted. + * + * @return Whether the mask is settable. + * + * {@hide} + */ + public static boolean areSettableFunctions(long functions) { + return functions == FUNCTION_NONE + || ((~SETTABLE_FUNCTIONS & functions) == 0 && Long.bitCount(functions) == 1); } - /** @hide */ - public static String removeFunction(String functions, String function) { - String[] split = functions.split(","); - for (int i = 0; i < split.length; i++) { - if (function.equals(split[i])) { - split[i] = null; - } + /** + * Converts the given function mask to string. Maintains ordering with respect to init scripts. + * + * @return String representation of given mask + * + * {@hide} + */ + public static String usbFunctionsToString(long functions) { + StringJoiner joiner = new StringJoiner(","); + if ((functions & FUNCTION_MTP) != 0) { + joiner.add(UsbManager.USB_FUNCTION_MTP); } - if (split.length == 1 && split[0] == null) { - return USB_FUNCTION_NONE; + if ((functions & FUNCTION_PTP) != 0) { + joiner.add(UsbManager.USB_FUNCTION_PTP); } - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < split.length; i++) { - String s = split[i]; - if (s != null) { - if (builder.length() > 0) { - builder.append(","); - } - builder.append(s); - } + if ((functions & FUNCTION_RNDIS) != 0) { + joiner.add(UsbManager.USB_FUNCTION_RNDIS); + } + if ((functions & FUNCTION_MIDI) != 0) { + joiner.add(UsbManager.USB_FUNCTION_MIDI); + } + if ((functions & FUNCTION_ACCESSORY) != 0) { + joiner.add(UsbManager.USB_FUNCTION_ACCESSORY); + } + if ((functions & FUNCTION_AUDIO_SOURCE) != 0) { + joiner.add(UsbManager.USB_FUNCTION_AUDIO_SOURCE); } - return builder.toString(); + if ((functions & FUNCTION_ADB) != 0) { + joiner.add(UsbManager.USB_FUNCTION_ADB); + } + return joiner.toString(); } - /** @hide */ - public static boolean containsFunction(String functions, String function) { - int index = functions.indexOf(function); - if (index < 0) return false; - if (index > 0 && functions.charAt(index - 1) != ',') return false; - int charAfter = index + function.length(); - if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false; - return true; + /** + * Parses a string of usb functions that are comma separated. + * + * @return A mask of all valid functions in the string + * + * {@hide} + */ + public static long usbFunctionsFromString(String functions) { + if (functions == null || functions.equals(USB_FUNCTION_NONE)) { + return FUNCTION_NONE; + } + long ret = 0; + for (String function : functions.split(",")) { + if (FUNCTION_NAME_TO_CODE.containsKey(function)) { + ret |= FUNCTION_NAME_TO_CODE.get(function); + } else if (function.length() > 0) { + throw new IllegalArgumentException("Invalid usb function " + functions); + } + } + return ret; } } diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index be6c4a12d11..9a9cdbde398 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -1095,7 +1095,8 @@ public class Tethering extends BaseNetworkObserver { if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")"); UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE); synchronized (mPublicSync) { - usbManager.setCurrentFunction(enable ? UsbManager.USB_FUNCTION_RNDIS : null, false); + usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_RNDIS + : UsbManager.FUNCTION_NONE); } return ConnectivityManager.TETHER_ERROR_NO_ERROR; } diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index e3e5e3e1b10..1ea2f976b80 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -41,7 +41,6 @@ import android.hardware.usb.UsbInterface; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; -import android.hardware.usb.gadget.V1_0.GadgetFunction; import android.hardware.usb.gadget.V1_0.IUsbGadget; import android.hardware.usb.gadget.V1_0.IUsbGadgetCallback; import android.hardware.usb.gadget.V1_0.Status; @@ -68,6 +67,8 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.SomeArgs; @@ -86,21 +87,20 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Scanner; import java.util.Set; -import java.util.StringJoiner; /** * UsbDeviceManager manages USB state in device mode. */ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver { - private static final String TAG = "UsbDeviceManager"; + private static final String TAG = UsbDeviceManager.class.getSimpleName(); private static final boolean DEBUG = false; /** * The SharedPreference setting per user that stores the screen unlocked functions between * sessions. */ - private static final String UNLOCKED_CONFIG_PREF = "usb-screen-unlocked-config-%d"; + static final String UNLOCKED_CONFIG_PREF = "usb-screen-unlocked-config-%d"; /** * ro.bootmode value when phone boots into usual Android. @@ -156,8 +156,6 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver private static final String ADB_NOTIFICATION_CHANNEL_ID_TV = "usbdevicemanager.adb.tv"; private UsbHandler mHandler; - private boolean mBootCompleted; - private boolean mSystemReady; private final Object mLock = new Object(); @@ -165,22 +163,13 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver private final ContentResolver mContentResolver; @GuardedBy("mLock") private UsbProfileGroupSettingsManager mCurrentSettings; - private NotificationManager mNotificationManager; private final boolean mHasUsbAccessory; - private boolean mUseUsbNotification; - private boolean mAdbEnabled; - private boolean mAudioSourceEnabled; - private boolean mMidiEnabled; - private int mMidiCard; - private int mMidiDevice; + @GuardedBy("mLock") private String[] mAccessoryStrings; private UsbDebuggingManager mDebuggingManager; - private final UsbAlsaManager mUsbAlsaManager; - private final UsbSettingsManager mSettingsManager; - private Intent mBroadcastedIntent; - private boolean mPendingBootBroadcast; + private final UEventObserver mUEventObserver; + private static Set sBlackListedInterfaces; - private SharedPreferences mSettings; static { sBlackListedInterfaces = new HashSet<>(); @@ -213,7 +202,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver /* * Listens for uevent messages from the kernel to monitor the USB state */ - private final UEventObserver mUEventObserver = new UEventObserver() { + private final class UsbUEventObserver extends UEventObserver { @Override public void onUEvent(UEventObserver.UEvent event) { if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString()); @@ -227,7 +216,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver startAccessoryMode(); } } - }; + } @Override public void onKeyguardStateChanged(boolean isShowing) { @@ -257,8 +246,6 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver public UsbDeviceManager(Context context, UsbAlsaManager alsaManager, UsbSettingsManager settingsManager) { mContext = context; - mUsbAlsaManager = alsaManager; - mSettingsManager = settingsManager; mContentResolver = context.getContentResolver(); PackageManager pm = mContext.getPackageManager(); mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY); @@ -274,16 +261,24 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver Slog.i(TAG, "USB GADGET HAL not present in the device", e); } + boolean secureAdbEnabled = SystemProperties.getBoolean("ro.adb.secure", false); + boolean dataEncrypted = "1".equals(SystemProperties.get("vold.decrypt")); + if (secureAdbEnabled && !dataEncrypted) { + mDebuggingManager = new UsbDebuggingManager(context); + } + if (halNotPresent) { /** * Initialze the legacy UsbHandler */ - mHandler = new UsbHandlerLegacy(FgThread.get().getLooper(), mContext); + mHandler = new UsbHandlerLegacy(FgThread.get().getLooper(), mContext, this, + mDebuggingManager, alsaManager, settingsManager); } else { /** * Initialize HAL based UsbHandler */ - mHandler = new UsbHandlerHal(FgThread.get().getLooper()); + mHandler = new UsbHandlerHal(FgThread.get().getLooper(), mContext, this, + mDebuggingManager, alsaManager, settingsManager); } if (nativeIsStartRequested()) { @@ -291,12 +286,6 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver startAccessoryMode(); } - boolean secureAdbEnabled = SystemProperties.getBoolean("ro.adb.secure", false); - boolean dataEncrypted = "1".equals(SystemProperties.get("vold.decrypt")); - if (secureAdbEnabled && !dataEncrypted) { - mDebuggingManager = new UsbDebuggingManager(context); - } - BroadcastReceiver portReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -347,41 +336,35 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver mContext.registerReceiver(languageChangedReceiver, new IntentFilter(Intent.ACTION_LOCALE_CHANGED)); + + // Watch for USB configuration changes + mUEventObserver = new UsbUEventObserver(); + mUEventObserver.startObserving(USB_STATE_MATCH); + mUEventObserver.startObserving(ACCESSORY_START_MATCH); + + // register observer to listen for settings changes + mContentResolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.ADB_ENABLED), + false, new AdbSettingsObserver()); } - private UsbProfileGroupSettingsManager getCurrentSettings() { + UsbProfileGroupSettingsManager getCurrentSettings() { synchronized (mLock) { return mCurrentSettings; } } + String[] getAccessoryStrings() { + synchronized (mLock) { + return mAccessoryStrings; + } + } + public void systemReady() { if (DEBUG) Slog.d(TAG, "systemReady"); LocalServices.getService(ActivityManagerInternal.class).registerScreenObserver(this); - mNotificationManager = (NotificationManager) - mContext.getSystemService(Context.NOTIFICATION_SERVICE); - - // Ensure that the notification channels are set up - if (isTv()) { - // TV-specific notification channel - mNotificationManager.createNotificationChannel( - new NotificationChannel(ADB_NOTIFICATION_CHANNEL_ID_TV, - mContext.getString( - com.android.internal.R.string - .adb_debugging_notification_channel_tv), - NotificationManager.IMPORTANCE_HIGH)); - } - - // We do not show the USB notification if the primary volume supports mass storage. - // The legacy mass storage UI will be used instead. - boolean massStorageSupported; - final StorageManager storageManager = StorageManager.from(mContext); - final StorageVolume primary = storageManager.getPrimaryVolume(); - massStorageSupported = primary != null && primary.allowMassStorage(); - mUseUsbNotification = !massStorageSupported && mContext.getResources().getBoolean( - com.android.internal.R.bool.config_usbChargingMessage); mHandler.sendEmptyMessage(MSG_SYSTEM_READY); } @@ -410,21 +393,19 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver boolean enableAccessory = (mAccessoryStrings != null && mAccessoryStrings[UsbAccessory.MANUFACTURER_STRING] != null && mAccessoryStrings[UsbAccessory.MODEL_STRING] != null); - String functions = null; - if (enableAccessory && enableAudio) { - functions = UsbManager.USB_FUNCTION_ACCESSORY + "," - + UsbManager.USB_FUNCTION_AUDIO_SOURCE; - } else if (enableAccessory) { - functions = UsbManager.USB_FUNCTION_ACCESSORY; - } else if (enableAudio) { - functions = UsbManager.USB_FUNCTION_AUDIO_SOURCE; + long functions = UsbManager.FUNCTION_NONE; + if (enableAccessory) { + functions |= UsbManager.FUNCTION_ACCESSORY; + } + if (enableAudio) { + functions |= UsbManager.FUNCTION_AUDIO_SOURCE; } - if (functions != null) { + if (functions != UsbManager.FUNCTION_NONE) { mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_MODE_ENTER_TIMEOUT), ACCESSORY_REQUEST_TIMEOUT); - setCurrentFunctions(functions, false); + setCurrentFunctions(functions); } } @@ -451,19 +432,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } } - private boolean isTv() { - return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); - } - - private SharedPreferences getPinnedSharedPrefs(Context context) { - final File prefsFile = new File(new File( - Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL, - context.getUserId(), context.getPackageName()), "shared_prefs"), - UsbDeviceManager.class.getSimpleName() + ".xml"); - return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE); - } - - private abstract class UsbHandler extends Handler { + abstract static class UsbHandler extends Handler { // current USB state private boolean mConnected; @@ -471,21 +440,40 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver private boolean mSourcePower; private boolean mSinkPower; private boolean mConfigured; - protected boolean mUsbDataUnlocked; private boolean mAudioAccessoryConnected; private boolean mAudioAccessorySupported; - protected String mCurrentFunctions; - protected boolean mCurrentFunctionsApplied; + private UsbAccessory mCurrentAccessory; private int mUsbNotificationId; private boolean mAdbNotificationShown; - private int mCurrentUser; private boolean mUsbCharging; private boolean mHideUsbNotification; private boolean mSupportsAllCombinations; - private String mScreenUnlockedFunctions = UsbManager.USB_FUNCTION_NONE; private boolean mScreenLocked; - protected boolean mCurrentUsbFunctionsRequested; + private boolean mSystemReady; + private Intent mBroadcastedIntent; + private boolean mPendingBootBroadcast; + private boolean mAudioSourceEnabled; + private boolean mMidiEnabled; + private int mMidiCard; + private int mMidiDevice; + + private final Context mContext; + private final UsbDebuggingManager mDebuggingManager; + private final UsbAlsaManager mUsbAlsaManager; + private final UsbSettingsManager mSettingsManager; + private NotificationManager mNotificationManager; + + protected long mScreenUnlockedFunctions; + protected boolean mAdbEnabled; + protected boolean mBootCompleted; + protected boolean mCurrentFunctionsApplied; + protected boolean mUseUsbNotification; + protected long mCurrentFunctions; + protected final UsbDeviceManager mUsbDeviceManager; + protected final ContentResolver mContentResolver; + protected SharedPreferences mSettings; + protected int mCurrentUser; protected boolean mCurrentUsbFunctionsReceived; /** @@ -494,31 +482,36 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver */ protected static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config"; - public UsbHandler(Looper looper) { + UsbHandler(Looper looper, Context context, UsbDeviceManager deviceManager, + UsbDebuggingManager debuggingManager, UsbAlsaManager alsaManager, + UsbSettingsManager settingsManager) { super(looper); + mContext = context; + mDebuggingManager = debuggingManager; + mUsbDeviceManager = deviceManager; + mUsbAlsaManager = alsaManager; + mSettingsManager = settingsManager; + mContentResolver = context.getContentResolver(); mCurrentUser = ActivityManager.getCurrentUser(); + mScreenUnlockedFunctions = UsbManager.FUNCTION_NONE; mScreenLocked = true; /* * Use the normal bootmode persistent prop to maintain state of adb across * all boot modes. */ - mAdbEnabled = UsbManager.containsFunction( - SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY), - UsbManager.USB_FUNCTION_ADB); + mAdbEnabled = (UsbManager.usbFunctionsFromString(getSystemProperty( + USB_PERSISTENT_CONFIG_PROPERTY, "")) & UsbManager.FUNCTION_ADB) != 0; - /* - * Previous versions can set persist config to mtp/ptp but it does not - * get reset on OTA. Reset the property here instead. - */ - String persisted = SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY); - if (UsbManager.containsFunction(persisted, UsbManager.USB_FUNCTION_MTP) - || UsbManager.containsFunction(persisted, UsbManager.USB_FUNCTION_PTP)) { - SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY, - UsbManager.removeFunction(UsbManager.removeFunction(persisted, - UsbManager.USB_FUNCTION_MTP), UsbManager.USB_FUNCTION_PTP)); - } + // We do not show the USB notification if the primary volume supports mass storage. + // The legacy mass storage UI will be used instead. + final StorageManager storageManager = StorageManager.from(mContext); + final StorageVolume primary = storageManager.getPrimaryVolume(); + + boolean massStorageSupported = primary != null && primary.allowMassStorage(); + mUseUsbNotification = !massStorageSupported && mContext.getResources().getBoolean( + com.android.internal.R.bool.config_usbChargingMessage); } public void sendMessage(int what, boolean arg) { @@ -602,20 +595,14 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable); if (enable != mAdbEnabled) { mAdbEnabled = enable; - String oldFunctions = mCurrentFunctions; - - // Persist the adb setting - String newFunction = applyAdbFunction(SystemProperties.get( - USB_PERSISTENT_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_NONE)); - SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY, newFunction); - // Remove mtp from the config if file transfer is not enabled - if (oldFunctions.equals(UsbManager.USB_FUNCTION_MTP) && - !mUsbDataUnlocked && enable) { - oldFunctions = UsbManager.USB_FUNCTION_NONE; + if (enable) { + setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_ADB); + } else { + setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, ""); } - setEnabledFunctions(oldFunctions, true, mUsbDataUnlocked); + setEnabledFunctions(mCurrentFunctions, true); updateAdbNotification(false); } @@ -624,21 +611,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } } - protected String applyAdbFunction(String functions) { - // Do not pass null pointer to the UsbManager. - // There isnt a check there. - if (functions == null) { - functions = ""; - } - if (mAdbEnabled) { - functions = UsbManager.addFunction(functions, UsbManager.USB_FUNCTION_ADB); - } else { - functions = UsbManager.removeFunction(functions, UsbManager.USB_FUNCTION_ADB); - } - return functions; - } - - private boolean isUsbTransferAllowed() { + protected boolean isUsbTransferAllowed() { UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); return !userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER); } @@ -650,12 +623,13 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver if (mConfigured && enteringAccessoryMode) { // successfully entered accessory mode - if (mAccessoryStrings != null) { - mCurrentAccessory = new UsbAccessory(mAccessoryStrings); + String[] accessoryStrings = mUsbDeviceManager.getAccessoryStrings(); + if (accessoryStrings != null) { + mCurrentAccessory = new UsbAccessory(accessoryStrings); Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory); // defer accessoryAttached if system is not ready if (mBootCompleted) { - getCurrentSettings().accessoryAttached(mCurrentAccessory); + mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory); } // else handle in boot completed } else { Slog.e(TAG, "nativeGetAccessoryStrings failed"); @@ -673,17 +647,24 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver // make sure accessory mode is off // and restore default functions Slog.d(TAG, "exited USB accessory mode"); - setEnabledFunctions(null, false, false); + setEnabledFunctions(UsbManager.FUNCTION_NONE, false); if (mCurrentAccessory != null) { if (mBootCompleted) { mSettingsManager.usbAccessoryRemoved(mCurrentAccessory); } mCurrentAccessory = null; - mAccessoryStrings = null; } } + protected SharedPreferences getPinnedSharedPrefs(Context context) { + final File prefsFile = new File(new File( + Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL, + context.getUserId(), context.getPackageName()), "shared_prefs"), + UsbDeviceManager.class.getSimpleName() + ".xml"); + return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE); + } + private boolean isUsbStateChanged(Intent intent) { final Set keySet = intent.getExtras().keySet(); if (mBroadcastedIntent == null) { @@ -706,7 +687,8 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver return false; } - protected void updateUsbStateBroadcastIfNeeded(boolean configChanged) { + protected void updateUsbStateBroadcastIfNeeded(long functions, + boolean configChanged) { // send a sticky broadcast containing current USB state Intent intent = new Intent(UsbManager.ACTION_USB_STATE); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING @@ -716,18 +698,14 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver intent.putExtra(UsbManager.USB_HOST_CONNECTED, mHostConnected); intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured); intent.putExtra(UsbManager.USB_DATA_UNLOCKED, - isUsbTransferAllowed() && mUsbDataUnlocked); + isUsbTransferAllowed() && isUsbDataTransferActive(mCurrentFunctions)); intent.putExtra(UsbManager.USB_CONFIG_CHANGED, configChanged); - if (mCurrentFunctions != null) { - String[] functions = mCurrentFunctions.split(","); - for (int i = 0; i < functions.length; i++) { - final String function = functions[i]; - if (UsbManager.USB_FUNCTION_NONE.equals(function)) { - continue; - } - intent.putExtra(function, true); - } + long remainingFunctions = functions; + while (remainingFunctions != 0) { + intent.putExtra(UsbManager.usbFunctionsToString( + Long.highestOneBit(remainingFunctions)), true); + remainingFunctions -= Long.highestOneBit(remainingFunctions); } // send broadcast intent only if the USB state has changed @@ -739,18 +717,21 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } if (DEBUG) Slog.d(TAG, "broadcasting " + intent + " extras: " + intent.getExtras()); - mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + sendStickyBroadcast(intent); mBroadcastedIntent = intent; } + protected void sendStickyBroadcast(Intent intent) { + mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); + } + private void updateUsbFunctions() { updateAudioSourceFunction(); updateMidiFunction(); } private void updateAudioSourceFunction() { - boolean enabled = UsbManager.containsFunction(mCurrentFunctions, - UsbManager.USB_FUNCTION_AUDIO_SOURCE); + boolean enabled = (mCurrentFunctions & UsbManager.FUNCTION_AUDIO_SOURCE) != 0; if (enabled != mAudioSourceEnabled) { int card = -1; int device = -1; @@ -775,8 +756,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } private void updateMidiFunction() { - boolean enabled = UsbManager.containsFunction(mCurrentFunctions, - UsbManager.USB_FUNCTION_MIDI); + boolean enabled = (mCurrentFunctions & UsbManager.FUNCTION_MIDI) != 0; if (enabled != mMidiEnabled) { if (enabled) { Scanner scanner = null; @@ -800,11 +780,21 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } private void setScreenUnlockedFunctions() { - setEnabledFunctions(mScreenUnlockedFunctions, false, - UsbManager.containsFunction(mScreenUnlockedFunctions, - UsbManager.USB_FUNCTION_MTP) - || UsbManager.containsFunction(mScreenUnlockedFunctions, - UsbManager.USB_FUNCTION_PTP)); + setEnabledFunctions(mScreenUnlockedFunctions, false); + } + + /** + * Returns the functions that are passed down to the low level driver once adb and + * charging are accounted for. + */ + long getAppliedFunctions(long functions) { + if (functions == UsbManager.FUNCTION_NONE) { + return getChargingFunctions(); + } + if (mAdbEnabled) { + return functions | UsbManager.FUNCTION_ADB; + } + return functions; } @Override @@ -817,10 +807,10 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver updateUsbNotification(false); updateAdbNotification(false); if (mBootCompleted) { - updateUsbStateBroadcastIfNeeded(false); + updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions), + false); } - if (UsbManager.containsFunction(mCurrentFunctions, - UsbManager.USB_FUNCTION_ACCESSORY)) { + if ((mCurrentFunctions & UsbManager.FUNCTION_ACCESSORY) != 0) { updateCurrentAccessory(); } if (mBootCompleted) { @@ -828,11 +818,10 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver && !hasMessages(MSG_FUNCTION_SWITCH_TIMEOUT)) { // restore defaults when USB is disconnected if (!mScreenLocked - && !UsbManager.USB_FUNCTION_NONE.equals( - mScreenUnlockedFunctions)) { + && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) { setScreenUnlockedFunctions(); } else { - setEnabledFunctions(null, !mAdbEnabled, false); + setEnabledFunctions(UsbManager.FUNCTION_NONE, !mAdbEnabled); } } updateUsbFunctions(); @@ -867,7 +856,8 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver updateUsbNotification(false); if (mBootCompleted) { if (mHostConnected || prevHostConnected) { - updateUsbStateBroadcastIfNeeded(false); + updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions), + false); } } else { mPendingBootBroadcast = true; @@ -913,17 +903,17 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver setAdbEnabled(msg.arg1 == 1); break; case MSG_SET_CURRENT_FUNCTIONS: - String functions = (String) msg.obj; - setEnabledFunctions(functions, false, msg.arg1 == 1); + long functions = (Long) msg.obj; + setEnabledFunctions(functions, false); break; case MSG_SET_SCREEN_UNLOCKED_FUNCTIONS: - mScreenUnlockedFunctions = (String) msg.obj; + mScreenUnlockedFunctions = (Long) msg.obj; SharedPreferences.Editor editor = mSettings.edit(); editor.putString(String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, - mCurrentUser), mScreenUnlockedFunctions); + mCurrentUser), + UsbManager.usbFunctionsToString(mScreenUnlockedFunctions)); editor.commit(); - if (!mScreenLocked && !UsbManager.USB_FUNCTION_NONE.equals( - mScreenUnlockedFunctions)) { + if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) { // If the screen is unlocked, also set current functions. setScreenUnlockedFunctions(); } @@ -936,22 +926,21 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver if (mSettings == null && !mScreenLocked) { // Shared preferences aren't accessible until the user has been unlocked. mSettings = getPinnedSharedPrefs(mContext); - mScreenUnlockedFunctions = mSettings.getString( + mScreenUnlockedFunctions = UsbManager.usbFunctionsFromString( + mSettings.getString( String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, mCurrentUser), - UsbManager.USB_FUNCTION_NONE); + "")); } if (!mBootCompleted) { break; } if (mScreenLocked) { if (!mConnected) { - setEnabledFunctions(null, false, false); + setEnabledFunctions(UsbManager.FUNCTION_NONE, false); } } else { - if (!UsbManager.USB_FUNCTION_NONE.equals(mScreenUnlockedFunctions) - && (UsbManager.USB_FUNCTION_ADB.equals(mCurrentFunctions) - || (UsbManager.USB_FUNCTION_MTP.equals(mCurrentFunctions) - && !mUsbDataUnlocked))) { + if (mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE + && mCurrentFunctions == UsbManager.FUNCTION_NONE) { // Set the screen unlocked functions if current function is charging. setScreenUnlockedFunctions(); } @@ -959,13 +948,24 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver break; case MSG_UPDATE_USER_RESTRICTIONS: // Restart the USB stack if USB transfer is enabled but no longer allowed. - final boolean forceRestart = mUsbDataUnlocked - && isUsbDataTransferActive() - && !isUsbTransferAllowed(); - setEnabledFunctions( - mCurrentFunctions, forceRestart, mUsbDataUnlocked && !forceRestart); + if (isUsbDataTransferActive(mCurrentFunctions) && !isUsbTransferAllowed()) { + setEnabledFunctions(UsbManager.FUNCTION_NONE, true); + } break; case MSG_SYSTEM_READY: + mNotificationManager = (NotificationManager) + mContext.getSystemService(Context.NOTIFICATION_SERVICE); + + // Ensure that the notification channels are set up + if (isTv()) { + // TV-specific notification channel + mNotificationManager.createNotificationChannel( + new NotificationChannel(ADB_NOTIFICATION_CHANNEL_ID_TV, + mContext.getString( + com.android.internal.R.string + .adb_debugging_notification_channel_tv), + NotificationManager.IMPORTANCE_HIGH)); + } mSystemReady = true; finishBoot(); break; @@ -984,10 +984,14 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } mCurrentUser = msg.arg1; mScreenLocked = true; - mScreenUnlockedFunctions = mSettings.getString( - String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, mCurrentUser), - UsbManager.USB_FUNCTION_NONE); - setEnabledFunctions(null, false, false); + if (mSettings != null) { + mScreenUnlockedFunctions = UsbManager.usbFunctionsFromString( + mSettings.getString( + String.format(Locale.ENGLISH, UNLOCKED_CONFIG_PREF, + mCurrentUser), + "")); + } + setEnabledFunctions(UsbManager.FUNCTION_NONE, false); } break; } @@ -995,9 +999,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver if (DEBUG) { Slog.v(TAG, "Accessory mode enter timeout: " + mConnected); } - if (!mConnected || !UsbManager.containsFunction( - mCurrentFunctions, - UsbManager.USB_FUNCTION_ACCESSORY)) { + if (!mConnected || (mCurrentFunctions & UsbManager.FUNCTION_ACCESSORY) == 0) { notifyAccessoryModeExit(); } break; @@ -1008,17 +1010,17 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver protected void finishBoot() { if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) { if (mPendingBootBroadcast) { - updateUsbStateBroadcastIfNeeded(false); + updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions), false); mPendingBootBroadcast = false; } if (!mScreenLocked - && !UsbManager.USB_FUNCTION_NONE.equals(mScreenUnlockedFunctions)) { + && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) { setScreenUnlockedFunctions(); } else { - setEnabledFunctions(null, false, false); + setEnabledFunctions(UsbManager.FUNCTION_NONE, false); } if (mCurrentAccessory != null) { - getCurrentSettings().accessoryAttached(mCurrentAccessory); + mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory); } if (mDebuggingManager != null) { mDebuggingManager.setAdbEnabled(mAdbEnabled); @@ -1026,8 +1028,8 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver // make sure the ADB_ENABLED setting value matches the current state try { - Settings.Global.putInt(mContentResolver, - Settings.Global.ADB_ENABLED, mAdbEnabled ? 1 : 0); + putGlobalSettings(mContentResolver, Settings.Global.ADB_ENABLED, + mAdbEnabled ? 1 : 0); } catch (SecurityException e) { // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't // be changed. @@ -1040,9 +1042,9 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } } - private boolean isUsbDataTransferActive() { - return UsbManager.containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP) - || UsbManager.containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP); + protected boolean isUsbDataTransferActive(long functions) { + return (functions & UsbManager.FUNCTION_MTP) != 0 + || (functions & UsbManager.FUNCTION_PTP) != 0; } public UsbAccessory getCurrentAccessory() { @@ -1051,7 +1053,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver protected void updateUsbNotification(boolean force) { if (mNotificationManager == null || !mUseUsbNotification - || ("0".equals(SystemProperties.get("persist.charging.notify")))) { + || ("0".equals(getSystemProperty("persist.charging.notify", "")))) { return; } @@ -1074,20 +1076,16 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver titleRes = com.android.internal.R.string.usb_unsupported_audio_accessory_title; id = SystemMessage.NOTE_USB_AUDIO_ACCESSORY_NOT_SUPPORTED; } else if (mConnected) { - if (UsbManager.containsFunction(mCurrentFunctions, - UsbManager.USB_FUNCTION_MTP) && mUsbDataUnlocked) { + if (mCurrentFunctions == UsbManager.FUNCTION_MTP) { titleRes = com.android.internal.R.string.usb_mtp_notification_title; id = SystemMessage.NOTE_USB_MTP; - } else if (UsbManager.containsFunction(mCurrentFunctions, - UsbManager.USB_FUNCTION_PTP) && mUsbDataUnlocked) { + } else if (mCurrentFunctions == UsbManager.FUNCTION_PTP) { titleRes = com.android.internal.R.string.usb_ptp_notification_title; id = SystemMessage.NOTE_USB_PTP; - } else if (UsbManager.containsFunction(mCurrentFunctions, - UsbManager.USB_FUNCTION_MIDI)) { + } else if (mCurrentFunctions == UsbManager.FUNCTION_MIDI) { titleRes = com.android.internal.R.string.usb_midi_notification_title; id = SystemMessage.NOTE_USB_MIDI; - } else if (UsbManager.containsFunction(mCurrentFunctions, - UsbManager.USB_FUNCTION_ACCESSORY)) { + } else if (mCurrentFunctions == UsbManager.FUNCTION_ACCESSORY) { titleRes = com.android.internal.R.string.usb_accessory_notification_title; id = SystemMessage.NOTE_USB_ACCESSORY; } else if (mSourcePower) { @@ -1184,7 +1182,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver final int titleRes = com.android.internal.R.string.adb_active_notification_title; if (mAdbEnabled && mConnected) { - if ("0".equals(SystemProperties.get("persist.adb.notify"))) return; + if ("0".equals(getSystemProperty("persist.adb.notify", ""))) return; if (force && mAdbNotificationShown) { mAdbNotificationShown = false; @@ -1230,20 +1228,41 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } } - protected String getChargingFunctions() { + private boolean isTv() { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); + } + + protected long getChargingFunctions() { // if ADB is enabled, reset functions to ADB // else enable MTP as usual. if (mAdbEnabled) { - return UsbManager.USB_FUNCTION_ADB; + return UsbManager.FUNCTION_ADB; } else { - return UsbManager.USB_FUNCTION_MTP; + return UsbManager.FUNCTION_MTP; } } - public boolean isFunctionEnabled(String function) { - return UsbManager.containsFunction(mCurrentFunctions, function); + protected void setSystemProperty(String prop, String val) { + SystemProperties.set(prop, val); + } + + protected String getSystemProperty(String prop, String def) { + return SystemProperties.get(prop, def); + } + + protected void putGlobalSettings(ContentResolver contentResolver, String setting, int val) { + Settings.Global.putInt(contentResolver, setting, val); + } + + public long getEnabledFunctions() { + return mCurrentFunctions; } + public long getScreenUnlockedFunctions() { + return mScreenUnlockedFunctions; + } + + public void dump(IndentingPrintWriter pw) { pw.println("USB Device State:"); pw.println(" mCurrentFunctions: " + mCurrentFunctions); @@ -1252,7 +1271,6 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver pw.println(" mScreenLocked: " + mScreenLocked); pw.println(" mConnected: " + mConnected); pw.println(" mConfigured: " + mConfigured); - pw.println(" mUsbDataUnlocked: " + mUsbDataUnlocked); pw.println(" mCurrentAccessory: " + mCurrentAccessory); pw.println(" mHostConnected: " + mHostConnected); pw.println(" mSourcePower: " + mSourcePower); @@ -1275,12 +1293,10 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver /** * Evaluates USB function policies and applies the change accordingly. */ - protected abstract void setEnabledFunctions(String functions, boolean forceRestart, - boolean usbDataUnlocked); - + protected abstract void setEnabledFunctions(long functions, boolean forceRestart); } - private final class UsbHandlerLegacy extends UsbHandler { + private static final class UsbHandlerLegacy extends UsbHandler { /** * The non-persistent property which stores the current USB settings. */ @@ -1293,46 +1309,44 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver private HashMap>> mOemModeMap; private String mCurrentOemFunctions; + private String mCurrentFunctionsStr; + private boolean mUsbDataUnlocked; - UsbHandlerLegacy(Looper looper, Context context) { - super(looper); + UsbHandlerLegacy(Looper looper, Context context, UsbDeviceManager deviceManager, + UsbDebuggingManager debuggingManager, UsbAlsaManager alsaManager, + UsbSettingsManager settingsManager) { + super(looper, context, deviceManager, debuggingManager, alsaManager, settingsManager); try { readOemUsbOverrideConfig(context); // Restore default functions. - mCurrentOemFunctions = SystemProperties.get(getPersistProp(false), + mCurrentOemFunctions = getSystemProperty(getPersistProp(false), UsbManager.USB_FUNCTION_NONE); if (isNormalBoot()) { - mCurrentFunctions = SystemProperties.get(USB_CONFIG_PROPERTY, + mCurrentFunctionsStr = getSystemProperty(USB_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_NONE); - mCurrentFunctionsApplied = mCurrentFunctions.equals( - SystemProperties.get(USB_STATE_PROPERTY)); + mCurrentFunctionsApplied = mCurrentFunctionsStr.equals( + getSystemProperty(USB_STATE_PROPERTY, UsbManager.USB_FUNCTION_NONE)); } else { - mCurrentFunctions = SystemProperties.get(getPersistProp(true), + mCurrentFunctionsStr = getSystemProperty(getPersistProp(true), UsbManager.USB_FUNCTION_NONE); - mCurrentFunctionsApplied = SystemProperties.get(USB_CONFIG_PROPERTY, + mCurrentFunctionsApplied = getSystemProperty(USB_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_NONE).equals( - SystemProperties.get(USB_STATE_PROPERTY)); + getSystemProperty(USB_STATE_PROPERTY, UsbManager.USB_FUNCTION_NONE)); } + // Mask out adb, since it is stored in mAdbEnabled + mCurrentFunctions = UsbManager.usbFunctionsFromString(mCurrentFunctionsStr) + & ~UsbManager.FUNCTION_ADB; mCurrentUsbFunctionsReceived = true; String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim(); updateState(state); - - // register observer to listen for settings changes - mContentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ADB_ENABLED), - false, new AdbSettingsObserver()); - - // Watch for USB configuration changes - mUEventObserver.startObserving(USB_STATE_MATCH); - mUEventObserver.startObserving(ACCESSORY_START_MATCH); } catch (Exception e) { Slog.e(TAG, "Error initializing UsbHandler", e); } } private void readOemUsbOverrideConfig(Context context) { - String[] configList = mContext.getResources().getStringArray( + String[] configList = context.getResources().getStringArray( com.android.internal.R.array.config_oemUsbModeOverride); if (configList != null) { @@ -1367,7 +1381,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver return usbFunctions; } - String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown"); + String bootMode = getSystemProperty(BOOT_MODE_PROPERTY, "unknown"); Slog.d(TAG, "applyOemOverride usbfunctions=" + usbFunctions + " bootmode=" + bootMode); Map> overridesMap = @@ -1386,25 +1400,22 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver if (!overrideFunctions.second.equals("")) { String newFunction; if (mAdbEnabled) { - newFunction = UsbManager.addFunction(overrideFunctions.second, + newFunction = addFunction(overrideFunctions.second, UsbManager.USB_FUNCTION_ADB); } else { newFunction = overrideFunctions.second; } Slog.d(TAG, "OEM USB override persisting: " + newFunction + "in prop: " + getPersistProp(false)); - SystemProperties.set(getPersistProp(false), - newFunction); + setSystemProperty(getPersistProp(false), newFunction); } return overrideFunctions.first; } else if (mAdbEnabled) { - String newFunction = UsbManager.addFunction(UsbManager.USB_FUNCTION_NONE, + String newFunction = addFunction(UsbManager.USB_FUNCTION_NONE, UsbManager.USB_FUNCTION_ADB); - SystemProperties.set(getPersistProp(false), - newFunction); + setSystemProperty(getPersistProp(false), newFunction); } else { - SystemProperties.set(getPersistProp(false), - UsbManager.USB_FUNCTION_NONE); + setSystemProperty(getPersistProp(false), UsbManager.USB_FUNCTION_NONE); } } // return passed in functions as is. @@ -1417,7 +1428,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver String value = null; for (int i = 0; i < 20; i++) { // State transition is done when sys.usb.state is set to the new configuration - value = SystemProperties.get(USB_STATE_PROPERTY); + value = getSystemProperty(USB_STATE_PROPERTY, ""); if (state.equals(value)) return true; SystemClock.sleep(50); } @@ -1432,14 +1443,14 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver * we always set it due to b/23631400, where adbd was getting killed * and not restarted due to property timeouts on some devices */ - SystemProperties.set(USB_CONFIG_PROPERTY, config); + setSystemProperty(USB_CONFIG_PROPERTY, config); } @Override - protected void setEnabledFunctions(String functions, boolean forceRestart, - boolean usbDataUnlocked) { + protected void setEnabledFunctions(long usbFunctions, boolean forceRestart) { + boolean usbDataUnlocked = isUsbDataTransferActive(usbFunctions); if (DEBUG) { - Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", " + Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions + ", " + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked); } @@ -1452,9 +1463,9 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver /** * Try to set the enabled functions. */ - final String oldFunctions = mCurrentFunctions; + final long oldFunctions = mCurrentFunctions; final boolean oldFunctionsApplied = mCurrentFunctionsApplied; - if (trySetEnabledFunctions(functions, forceRestart)) { + if (trySetEnabledFunctions(usbFunctions, forceRestart)) { return; } @@ -1464,7 +1475,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver * user restrictions independently of any other new functions we were * trying to activate. */ - if (oldFunctionsApplied && !oldFunctions.equals(functions)) { + if (oldFunctionsApplied && oldFunctions != usbFunctions) { Slog.e(TAG, "Failsafe 1: Restoring previous USB functions."); if (trySetEnabledFunctions(oldFunctions, false)) { return; @@ -1475,7 +1486,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver * Still didn't work. Try to restore the default functions. */ Slog.e(TAG, "Failsafe 2: Restoring default USB functions."); - if (trySetEnabledFunctions(null, false)) { + if (trySetEnabledFunctions(UsbManager.FUNCTION_NONE, false)) { return; } @@ -1484,7 +1495,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver * Try to get ADB working if enabled. */ Slog.e(TAG, "Failsafe 3: Restoring empty function list (with ADB if enabled)."); - if (trySetEnabledFunctions(UsbManager.USB_FUNCTION_NONE, false)) { + if (trySetEnabledFunctions(UsbManager.FUNCTION_NONE, false)) { return; } @@ -1495,30 +1506,49 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } private boolean isNormalBoot() { - String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown"); + String bootMode = getSystemProperty(BOOT_MODE_PROPERTY, "unknown"); return bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown"); } - private boolean trySetEnabledFunctions(String functions, boolean forceRestart) { + protected String applyAdbFunction(String functions) { + // Do not pass null pointer to the UsbManager. + // There isn't a check there. + if (functions == null) { + functions = ""; + } + if (mAdbEnabled) { + functions = addFunction(functions, UsbManager.USB_FUNCTION_ADB); + } else { + functions = removeFunction(functions, UsbManager.USB_FUNCTION_ADB); + } + return functions; + } + + private boolean trySetEnabledFunctions(long usbFunctions, boolean forceRestart) { + String functions = null; + if (usbFunctions != UsbManager.FUNCTION_NONE) { + functions = UsbManager.usbFunctionsToString(usbFunctions); + } + mCurrentFunctions = usbFunctions; if (functions == null || applyAdbFunction(functions) .equals(UsbManager.USB_FUNCTION_NONE)) { - functions = getChargingFunctions(); + functions = UsbManager.usbFunctionsToString(getChargingFunctions()); } functions = applyAdbFunction(functions); String oemFunctions = applyOemOverrideFunction(functions); - if (!isNormalBoot() && !mCurrentFunctions.equals(functions)) { - SystemProperties.set(getPersistProp(true), functions); + if (!isNormalBoot() && !mCurrentFunctionsStr.equals(functions)) { + setSystemProperty(getPersistProp(true), functions); } if ((!functions.equals(oemFunctions) && !mCurrentOemFunctions.equals(oemFunctions)) - || !mCurrentFunctions.equals(functions) + || !mCurrentFunctionsStr.equals(functions) || !mCurrentFunctionsApplied || forceRestart) { Slog.i(TAG, "Setting USB config to " + functions); - mCurrentFunctions = functions; + mCurrentFunctionsStr = functions; mCurrentOemFunctions = oemFunctions; mCurrentFunctionsApplied = false; @@ -1538,12 +1568,12 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver setUsbConfig(oemFunctions); if (mBootCompleted - && (UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_MTP) - || UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) { + && (containsFunction(functions, UsbManager.USB_FUNCTION_MTP) + || containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) { /** * Start up dependent services. */ - updateUsbStateBroadcastIfNeeded(true); + updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions), true); } if (!waitForState(oemFunctions)) { @@ -1557,7 +1587,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } private String getPersistProp(boolean functions) { - String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown"); + String bootMode = getSystemProperty(BOOT_MODE_PROPERTY, "unknown"); String persistProp = USB_PERSISTENT_CONFIG_PROPERTY; if (!(bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown"))) { if (functions) { @@ -1568,9 +1598,54 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } return persistProp; } + + private String addFunction(String functions, String function) { + if (UsbManager.USB_FUNCTION_NONE.equals(functions)) { + return function; + } + if (!containsFunction(functions, function)) { + if (functions.length() > 0) { + functions += ","; + } + functions += function; + } + return functions; + } + + private String removeFunction(String functions, String function) { + String[] split = functions.split(","); + for (int i = 0; i < split.length; i++) { + if (function.equals(split[i])) { + split[i] = null; + } + } + if (split.length == 1 && split[0] == null) { + return UsbManager.USB_FUNCTION_NONE; + } + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < split.length; i++) { + String s = split[i]; + if (s != null) { + if (builder.length() > 0) { + builder.append(","); + } + builder.append(s); + } + } + return builder.toString(); + } + + private boolean containsFunction(String functions, String function) { + int index = functions.indexOf(function); + if (index < 0) return false; + if (index > 0 && functions.charAt(index - 1) != ',') return false; + int charAfter = index + function.length(); + if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false; + return true; + } } - private final class UsbHandlerHal extends UsbHandler { + private static final class UsbHandlerHal extends UsbHandler { /** * Proxy object for the usb gadget hal daemon. @@ -1627,9 +1702,12 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver */ protected static final String ADBD = "adbd"; + protected boolean mCurrentUsbFunctionsRequested; - UsbHandlerHal(Looper looper) { - super(looper); + UsbHandlerHal(Looper looper, Context context, UsbDeviceManager deviceManager, + UsbDebuggingManager debuggingManager, UsbAlsaManager alsaManager, + UsbSettingsManager settingsManager) { + super(looper, context, deviceManager, debuggingManager, alsaManager, settingsManager); try { ServiceNotification serviceNotification = new ServiceNotification(); @@ -1645,25 +1723,12 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver mGadgetProxy = IUsbGadget.getService(true); mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(), USB_GADGET_HAL_DEATH_COOKIE); - mCurrentFunctions = UsbManager.USB_FUNCTION_NONE; + mCurrentFunctions = UsbManager.FUNCTION_NONE; mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback()); mCurrentUsbFunctionsRequested = true; } String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim(); updateState(state); - - /** - * Register observer to listen for settings changes. - */ - mContentResolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.ADB_ENABLED), - false, new AdbSettingsObserver()); - - /** - * Watch for USB configuration changes. - */ - mUEventObserver.startObserving(USB_STATE_MATCH); - mUEventObserver.startObserving(ACCESSORY_START_MATCH); } catch (NoSuchElementException e) { Slog.e(TAG, "Usb gadget hal not found", e); } catch (RemoteException e) { @@ -1696,7 +1761,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(), USB_GADGET_HAL_DEATH_COOKIE); if (!mCurrentFunctionsApplied) { - setCurrentFunctions(mCurrentFunctions, mUsbDataUnlocked); + setEnabledFunctions(mCurrentFunctions, false); } } catch (NoSuchElementException e) { Slog.e(TAG, "Usb gadget hal not found", e); @@ -1711,12 +1776,12 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver public void handleMessage(Message msg) { switch (msg.what) { case MSG_SET_CHARGING_FUNCTIONS: - setEnabledFunctions(null, false, mUsbDataUnlocked); + setEnabledFunctions(UsbManager.FUNCTION_NONE, false); break; case MSG_SET_FUNCTIONS_TIMEOUT: Slog.e(TAG, "Set functions timed out! no reply from usb hal"); if (msg.arg1 != 1) { - setEnabledFunctions(null, false, mUsbDataUnlocked); + setEnabledFunctions(UsbManager.FUNCTION_NONE, false); } break; case MSG_GET_CURRENT_USB_FUNCTIONS: @@ -1725,7 +1790,8 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver if (mCurrentUsbFunctionsRequested) { Slog.e(TAG, "updating mCurrentFunctions"); - mCurrentFunctions = functionListToString((Long) msg.obj); + // Mask out adb, since it is stored in mAdbEnabled + mCurrentFunctions = ((Long) msg.obj) & ~UsbManager.FUNCTION_ADB; Slog.e(TAG, "mCurrentFunctions:" + mCurrentFunctions + "applied:" + msg.arg1); mCurrentFunctionsApplied = msg.arg1 == 1; @@ -1737,7 +1803,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver * Dont force to default when the configuration is already set to default. */ if (msg.arg1 != 1) { - setEnabledFunctions(null, !mAdbEnabled, false); + setEnabledFunctions(UsbManager.FUNCTION_NONE, !mAdbEnabled); } break; default: @@ -1789,70 +1855,7 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } } - private long stringToFunctionList(String config) { - long functionsMask = 0; - String[] functions = config.split(","); - for (int i = 0; i < functions.length; i++) { - switch (functions[i]) { - case "none": - functionsMask |= GadgetFunction.NONE; - break; - case "adb": - functionsMask |= GadgetFunction.ADB; - break; - case "mtp": - functionsMask |= GadgetFunction.MTP; - break; - case "ptp": - functionsMask |= GadgetFunction.PTP; - break; - case "midi": - functionsMask |= GadgetFunction.MIDI; - break; - case "accessory": - functionsMask |= GadgetFunction.ACCESSORY; - break; - case "rndis": - functionsMask |= GadgetFunction.RNDIS; - break; - } - } - return functionsMask; - } - - private String functionListToString(Long functionList) { - StringJoiner functions = new StringJoiner(","); - if (functionList == GadgetFunction.NONE) { - functions.add("none"); - return functions.toString(); - } - if ((functionList & GadgetFunction.ADB) != 0) { - functions.add("adb"); - } - if ((functionList & GadgetFunction.MTP) != 0) { - functions.add("mtp"); - } - if ((functionList & GadgetFunction.PTP) != 0) { - functions.add("ptp"); - } - if ((functionList & GadgetFunction.MIDI) != 0) { - functions.add("midi"); - } - if ((functionList & GadgetFunction.ACCESSORY) != 0) { - functions.add("accessory"); - } - if ((functionList & GadgetFunction.RNDIS) != 0) { - functions.add("rndis"); - } - /** - * Remove the trailing comma. - */ - return functions.toString(); - } - - - private void setUsbConfig(String config, boolean chargingFunctions) { - Long functions = stringToFunctionList(config); + private void setUsbConfig(long config, boolean chargingFunctions) { if (true) Slog.d(TAG, "setUsbConfig(" + config + ") request:" + ++mCurrentRequest); /** * Cancel any ongoing requests, if present. @@ -1867,20 +1870,20 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver return; } try { - if ((functions & GadgetFunction.ADB) != 0) { + if ((config & UsbManager.FUNCTION_ADB) != 0) { /** * Start adbd if ADB function is included in the configuration. */ - SystemProperties.set(CTL_START, ADBD); + setSystemProperty(CTL_START, ADBD); } else { /** * Stop adbd otherwise. */ - SystemProperties.set(CTL_STOP, ADBD); + setSystemProperty(CTL_STOP, ADBD); } UsbGadgetCallback usbGadgetCallback = new UsbGadgetCallback(mCurrentRequest, - functions, chargingFunctions); - mGadgetProxy.setCurrentUsbFunctions(functions, usbGadgetCallback, + config, chargingFunctions); + mGadgetProxy.setCurrentUsbFunctions(config, usbGadgetCallback, SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS); sendMessageDelayed(MSG_SET_FUNCTIONS_TIMEOUT, chargingFunctions, SET_FUNCTIONS_TIMEOUT_MS); @@ -1894,49 +1897,29 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } @Override - protected void setEnabledFunctions(String functions, boolean forceRestart, - boolean usbDataUnlocked) { + protected void setEnabledFunctions(long functions, boolean forceRestart) { if (DEBUG) { Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", " - + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked); - } - - if (usbDataUnlocked != mUsbDataUnlocked) { - mUsbDataUnlocked = usbDataUnlocked; - updateUsbNotification(false); - forceRestart = true; + + "forceRestart=" + forceRestart); } - - trySetEnabledFunctions(functions, forceRestart); - } - - private void trySetEnabledFunctions(String functions, boolean forceRestart) { - boolean chargingFunctions = false; - - if (functions == null || applyAdbFunction(functions) - .equals(UsbManager.USB_FUNCTION_NONE)) { - functions = getChargingFunctions(); - chargingFunctions = true; - } - functions = applyAdbFunction(functions); - - if (!mCurrentFunctions.equals(functions) + if (mCurrentFunctions != functions || !mCurrentFunctionsApplied || forceRestart) { - Slog.i(TAG, "Setting USB config to " + functions); + Slog.i(TAG, "Setting USB config to " + UsbManager.usbFunctionsToString(functions)); mCurrentFunctions = functions; mCurrentFunctionsApplied = false; // set the flag to false as that would be stale value mCurrentUsbFunctionsRequested = false; + boolean chargingFunctions = functions == UsbManager.FUNCTION_NONE; + functions = getAppliedFunctions(functions); + // Set the new USB configuration. - setUsbConfig(mCurrentFunctions, chargingFunctions); + setUsbConfig(functions, chargingFunctions); - if (mBootCompleted - && (UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_MTP) - || UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) { + if (mBootCompleted && isUsbDataTransferActive(functions)) { // Start up dependent services. - updateUsbStateBroadcastIfNeeded(true); + updateUsbStateBroadcastIfNeeded(functions, true); } } } @@ -1968,27 +1951,37 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver return nativeOpenAccessory(); } - /** - * Checks whether the function is present in the USB configuration. - * - * @param function function to be checked. - */ - public boolean isFunctionEnabled(String function) { - return mHandler.isFunctionEnabled(function); + public long getCurrentFunctions() { + return mHandler.getEnabledFunctions(); + } + + public long getScreenUnlockedFunctions() { + return mHandler.getScreenUnlockedFunctions(); } /** * Adds function to the current USB configuration. * - * @param functions name of the USB function, or null to restore the default function. - * @param usbDataUnlocked whether user data is accessible. + * @param functions The functions to set, or empty to set the charging function. */ - public void setCurrentFunctions(String functions, boolean usbDataUnlocked) { + public void setCurrentFunctions(long functions) { if (DEBUG) { - Slog.d(TAG, "setCurrentFunctions(" + functions + ", " - + usbDataUnlocked + ")"); - } - mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, usbDataUnlocked); + Slog.d(TAG, "setCurrentFunctions(" + UsbManager.usbFunctionsToString(functions) + ")"); + } + if (functions == UsbManager.FUNCTION_NONE) { + MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_CHARGING); + } else if (functions == UsbManager.FUNCTION_MTP) { + MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_MTP); + } else if (functions == UsbManager.FUNCTION_PTP) { + MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_PTP); + } else if (functions == UsbManager.FUNCTION_MIDI) { + MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_MIDI); + } else if (functions == UsbManager.FUNCTION_RNDIS) { + MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_RNDIS); + } else if (functions == UsbManager.FUNCTION_ACCESSORY) { + MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_ACCESSORY); + } + mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions); } /** @@ -1996,9 +1989,10 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver * * @param functions Functions to set. */ - public void setScreenUnlockedFunctions(String functions) { + public void setScreenUnlockedFunctions(long functions) { if (DEBUG) { - Slog.d(TAG, "setScreenUnlockedFunctions(" + functions + ")"); + Slog.d(TAG, "setScreenUnlockedFunctions(" + + UsbManager.usbFunctionsToString(functions) + ")"); } mHandler.sendMessage(MSG_SET_SCREEN_UNLOCKED_FUNCTIONS, functions); } diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 1a20819b9a8..2f6e5314331 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -382,59 +382,44 @@ public class UsbService extends IUsbManager.Stub { } @Override - public boolean isFunctionEnabled(String function) { + public void setCurrentFunctions(long functions) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - return mDeviceManager != null && mDeviceManager.isFunctionEnabled(function); + Preconditions.checkArgument(UsbManager.areSettableFunctions(functions)); + Preconditions.checkState(mDeviceManager != null); + mDeviceManager.setCurrentFunctions(functions); } @Override - public void setCurrentFunction(String function, boolean usbDataUnlocked) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - - if (!isSupportedCurrentFunction(function)) { - Slog.w(TAG, "Caller of setCurrentFunction() requested unsupported USB function: " - + function); - function = UsbManager.USB_FUNCTION_NONE; - } + public void setCurrentFunction(String functions, boolean usbDataUnlocked) { + setCurrentFunctions(UsbManager.usbFunctionsFromString(functions)); + } - if (mDeviceManager != null) { - mDeviceManager.setCurrentFunctions(function, usbDataUnlocked); - } else { - throw new IllegalStateException("USB device mode not supported"); - } + @Override + public boolean isFunctionEnabled(String function) { + return (getCurrentFunctions() & UsbManager.usbFunctionsFromString(function)) != 0; } @Override - public void setScreenUnlockedFunctions(String function) { + public long getCurrentFunctions() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - - if (!isSupportedCurrentFunction(function)) { - Slog.w(TAG, "Caller of setScreenUnlockedFunctions() requested unsupported USB function:" - + function); - function = UsbManager.USB_FUNCTION_NONE; - } - - if (mDeviceManager != null) { - mDeviceManager.setScreenUnlockedFunctions(function); - } else { - throw new IllegalStateException("USB device mode not supported"); - } + Preconditions.checkState(mDeviceManager != null); + return mDeviceManager.getCurrentFunctions(); } - private static boolean isSupportedCurrentFunction(String function) { - if (function == null) return true; + @Override + public void setScreenUnlockedFunctions(long functions) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + Preconditions.checkArgument(UsbManager.areSettableFunctions(functions)); + Preconditions.checkState(mDeviceManager != null); - switch (function) { - case UsbManager.USB_FUNCTION_NONE: - case UsbManager.USB_FUNCTION_AUDIO_SOURCE: - case UsbManager.USB_FUNCTION_MIDI: - case UsbManager.USB_FUNCTION_MTP: - case UsbManager.USB_FUNCTION_PTP: - case UsbManager.USB_FUNCTION_RNDIS: - return true; - } + mDeviceManager.setScreenUnlockedFunctions(functions); + } - return false; + @Override + public long getScreenUnlockedFunctions() { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); + Preconditions.checkState(mDeviceManager != null); + return mDeviceManager.getScreenUnlockedFunctions(); } @Override diff --git a/tests/UsbTests/Android.mk b/tests/UsbTests/Android.mk new file mode 100644 index 00000000000..a04f32a6d71 --- /dev/null +++ b/tests/UsbTests/Android.mk @@ -0,0 +1,44 @@ +# +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_STATIC_JAVA_LIBRARIES := \ + frameworks-base-testutils \ + android-support-test \ + mockito-target-inline-minus-junit4 \ + platform-test-annotations \ + services.core \ + services.net \ + services.usb \ + truth-prebuilt \ + +LOCAL_JNI_SHARED_LIBRARIES := \ + libdexmakerjvmtiagent \ + +LOCAL_CERTIFICATE := platform + +LOCAL_PACKAGE_NAME := UsbTests + +LOCAL_COMPATIBILITY_SUITE := device-tests + +include $(BUILD_PACKAGE) diff --git a/tests/UsbTests/AndroidManifest.xml b/tests/UsbTests/AndroidManifest.xml new file mode 100644 index 00000000000..5d606951bb5 --- /dev/null +++ b/tests/UsbTests/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/tests/UsbTests/AndroidTest.xml b/tests/UsbTests/AndroidTest.xml new file mode 100644 index 00000000000..0b623fbf201 --- /dev/null +++ b/tests/UsbTests/AndroidTest.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java new file mode 100644 index 00000000000..c491b465899 --- /dev/null +++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.hardware.usb.UsbManager; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.server.FgThread; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Tests for UsbHandler state changes. + */ +@RunWith(AndroidJUnit4.class) +public class UsbHandlerTest { + private static final String TAG = UsbHandlerTest.class.getSimpleName(); + + @Mock + private UsbDeviceManager mUsbDeviceManager; + @Mock + private UsbDebuggingManager mUsbDebuggingManager; + @Mock + private UsbAlsaManager mUsbAlsaManager; + @Mock + private UsbSettingsManager mUsbSettingsManager; + @Mock + private SharedPreferences mSharedPreferences; + @Mock + private SharedPreferences.Editor mEditor; + + private MockUsbHandler mUsbHandler; + + private static final int MSG_UPDATE_STATE = 0; + private static final int MSG_ENABLE_ADB = 1; + private static final int MSG_SET_CURRENT_FUNCTIONS = 2; + private static final int MSG_SYSTEM_READY = 3; + private static final int MSG_BOOT_COMPLETED = 4; + private static final int MSG_USER_SWITCHED = 5; + private static final int MSG_UPDATE_USER_RESTRICTIONS = 6; + private static final int MSG_SET_SCREEN_UNLOCKED_FUNCTIONS = 12; + private static final int MSG_UPDATE_SCREEN_LOCK = 13; + + private Map mMockProperties; + private Map mMockGlobalSettings; + + private class MockUsbHandler extends UsbDeviceManager.UsbHandler { + boolean mIsUsbTransferAllowed; + Intent mBroadcastedIntent; + + MockUsbHandler(Looper looper, Context context, UsbDeviceManager deviceManager, + UsbDebuggingManager debuggingManager, UsbAlsaManager alsaManager, + UsbSettingsManager settingsManager) { + super(looper, context, deviceManager, debuggingManager, alsaManager, settingsManager); + mUseUsbNotification = false; + mIsUsbTransferAllowed = true; + mCurrentUsbFunctionsReceived = true; + } + + @Override + protected void setEnabledFunctions(long functions, boolean force) { + mCurrentFunctions = functions; + } + + @Override + protected void setSystemProperty(String property, String value) { + mMockProperties.put(property, value); + } + + @Override + protected void putGlobalSettings(ContentResolver resolver, String setting, int val) { + mMockGlobalSettings.put(setting, val); + } + + @Override + protected String getSystemProperty(String property, String def) { + if (mMockProperties.containsKey(property)) { + return mMockProperties.get(property); + } + return def; + } + + @Override + protected boolean isUsbTransferAllowed() { + return mIsUsbTransferAllowed; + } + + @Override + protected SharedPreferences getPinnedSharedPrefs(Context context) { + return mSharedPreferences; + } + + @Override + protected void sendStickyBroadcast(Intent intent) { + mBroadcastedIntent = intent; + } + } + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + mMockProperties = new HashMap<>(); + mMockGlobalSettings = new HashMap<>(); + when(mSharedPreferences.edit()).thenReturn(mEditor); + + mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(), + InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbDebuggingManager, + mUsbAlsaManager, mUsbSettingsManager); + } + + @SmallTest + public void setFunctionsMtp() { + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS, + UsbManager.FUNCTION_MTP)); + assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0); + } + + @SmallTest + public void setFunctionsPtp() { + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS, + UsbManager.FUNCTION_PTP)); + assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_PTP, 0); + } + + @SmallTest + public void setFunctionsMidi() { + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS, + UsbManager.FUNCTION_MIDI)); + assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MIDI, 0); + } + + @SmallTest + public void setFunctionsRndis() { + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS, + UsbManager.FUNCTION_RNDIS)); + assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_RNDIS, 0); + } + + @SmallTest + public void enableAdb() { + sendBootCompleteMessages(mUsbHandler); + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_ENABLE_ADB, 1)); + assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE); + assertTrue(mUsbHandler.mAdbEnabled); + assertEquals(mMockProperties.get(UsbDeviceManager.UsbHandler + .USB_PERSISTENT_CONFIG_PROPERTY), UsbManager.USB_FUNCTION_ADB); + verify(mUsbDebuggingManager).setAdbEnabled(true); + + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_STATE, 1, 1)); + + assertTrue(mUsbHandler.mBroadcastedIntent.getBooleanExtra(UsbManager.USB_CONNECTED, false)); + assertTrue(mUsbHandler.mBroadcastedIntent + .getBooleanExtra(UsbManager.USB_CONFIGURED, false)); + assertTrue(mUsbHandler.mBroadcastedIntent + .getBooleanExtra(UsbManager.USB_FUNCTION_ADB, false)); + } + + @SmallTest + public void disableAdb() { + mMockProperties.put(UsbDeviceManager.UsbHandler.USB_PERSISTENT_CONFIG_PROPERTY, + UsbManager.USB_FUNCTION_ADB); + mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(), + InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbDebuggingManager, + mUsbAlsaManager, mUsbSettingsManager); + + sendBootCompleteMessages(mUsbHandler); + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_ENABLE_ADB, 0)); + assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE); + assertFalse(mUsbHandler.mAdbEnabled); + assertEquals(mMockProperties.get(UsbDeviceManager.UsbHandler + .USB_PERSISTENT_CONFIG_PROPERTY), ""); + verify(mUsbDebuggingManager).setAdbEnabled(false); + } + + @SmallTest + public void bootCompletedCharging() { + sendBootCompleteMessages(mUsbHandler); + assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE); + } + + @Test + @SmallTest + public void bootCompletedAdbEnabled() { + mMockProperties.put(UsbDeviceManager.UsbHandler.USB_PERSISTENT_CONFIG_PROPERTY, "adb"); + mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(), + InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbDebuggingManager, + mUsbAlsaManager, mUsbSettingsManager); + + sendBootCompleteMessages(mUsbHandler); + assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE); + assertEquals(mMockGlobalSettings.get(Settings.Global.ADB_ENABLED).intValue(), 1); + assertTrue(mUsbHandler.mAdbEnabled); + verify(mUsbDebuggingManager).setAdbEnabled(true); + } + + @SmallTest + public void userSwitchedDisablesMtp() { + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS, + UsbManager.FUNCTION_MTP)); + assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0); + + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_USER_SWITCHED, + UserHandle.getCallingUserId() + 1)); + assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE); + } + + @SmallTest + public void changedRestrictionsDisablesMtp() { + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS, + UsbManager.FUNCTION_MTP)); + assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0); + + mUsbHandler.mIsUsbTransferAllowed = false; + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_USER_RESTRICTIONS)); + assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE); + } + + @SmallTest + public void disconnectResetsCharging() { + sendBootCompleteMessages(mUsbHandler); + + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS, + UsbManager.FUNCTION_MTP)); + assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0); + + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_STATE, 0, 0)); + + assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE); + } + + @SmallTest + public void configuredSendsBroadcast() { + sendBootCompleteMessages(mUsbHandler); + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS, + UsbManager.FUNCTION_MTP)); + assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0); + + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_STATE, 1, 1)); + + assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0); + assertTrue(mUsbHandler.mBroadcastedIntent.getBooleanExtra(UsbManager.USB_CONNECTED, false)); + assertTrue(mUsbHandler.mBroadcastedIntent + .getBooleanExtra(UsbManager.USB_CONFIGURED, false)); + assertTrue(mUsbHandler.mBroadcastedIntent + .getBooleanExtra(UsbManager.USB_FUNCTION_MTP, false)); + } + + @SmallTest + public void setScreenUnlockedFunctions() { + sendBootCompleteMessages(mUsbHandler); + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_SCREEN_LOCK, 0)); + + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_SCREEN_UNLOCKED_FUNCTIONS, + UsbManager.FUNCTION_MTP)); + assertNotEquals(mUsbHandler.getScreenUnlockedFunctions() & UsbManager.FUNCTION_MTP, 0); + assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0); + verify(mEditor).putString(String.format(Locale.ENGLISH, + UsbDeviceManager.UNLOCKED_CONFIG_PREF, mUsbHandler.mCurrentUser), + UsbManager.USB_FUNCTION_MTP); + } + + @SmallTest + public void unlockScreen() { + when(mSharedPreferences.getString(String.format(Locale.ENGLISH, + UsbDeviceManager.UNLOCKED_CONFIG_PREF, mUsbHandler.mCurrentUser), "")) + .thenReturn(UsbManager.USB_FUNCTION_MTP); + sendBootCompleteMessages(mUsbHandler); + mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_SCREEN_LOCK, 0)); + + assertNotEquals(mUsbHandler.getScreenUnlockedFunctions() & UsbManager.FUNCTION_MTP, 0); + assertNotEquals(mUsbHandler.getEnabledFunctions() & UsbManager.FUNCTION_MTP, 0); + } + + private static void sendBootCompleteMessages(Handler handler) { + handler.handleMessage(handler.obtainMessage(MSG_BOOT_COMPLETED)); + handler.handleMessage(handler.obtainMessage(MSG_SYSTEM_READY)); + } +} \ No newline at end of file diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java index 099cfd45716..e692652c7ea 100644 --- a/tests/net/java/com/android/server/connectivity/TetheringTest.java +++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java @@ -311,7 +311,7 @@ public class TetheringTest { // Emulate pressing the USB tethering button in Settings UI. mTethering.startTethering(TETHERING_USB, null, false); mLooper.dispatchAll(); - verify(mUsbManager, times(1)).setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS, false); + verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS); // Pretend we receive a USB connected broadcast. Here we also pretend // that the RNDIS function is somehow enabled, so that we see if we -- GitLab From 28b6fc9c25cd55c500426cdcbe233a123736fa0a Mon Sep 17 00:00:00 2001 From: Jerry Zhang Date: Wed, 17 Jan 2018 17:18:12 -0800 Subject: [PATCH 188/416] Usb changes and strings for connected devices 2.0 New metrics constant and usb strings for the new notification / details page. Bug: 69333961 Test: Check notification Change-Id: If9bde7f787e40e42bb991a99b032e1ff968a0a41 --- core/res/res/values/strings.xml | 16 ++++++++----- core/res/res/values/symbols.xml | 2 ++ proto/src/metrics_constants.proto | 5 ++++ proto/src/system_messages.proto | 4 ++++ .../android/server/usb/UsbDeviceManager.java | 24 ++++++++++++------- 5 files changed, 37 insertions(+), 14 deletions(-) diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3cde765e04c..224503bf6b3 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3222,19 +3222,23 @@ OK - USB charging this device + Charging this device via USB - USB supplying power to attached device + Charging connected device via USB - USB for file transfer + USB file transfer turned on - USB for photo transfer + PTP via USB turned on + + USB tethering turned on - USB for MIDI + MIDI via USB turned on - Connected to a USB accessory + USB accessory mode turned on Tap for more options. + + Charging connected device. Tap for more options. Analog audio accessory detected diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a62d49ee7bf..a338f89af20 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2026,8 +2026,10 @@ + + diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 7539d8847c2..83657bb754a 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -5172,6 +5172,11 @@ message MetricsEvent { // Package: Package of the autofill service // OS: P AUTOFILL_INVALID_PERMISSION = 1289; + + // OPEN: Settings->Connected Devices->USB->(click on details link) + // CATEGORY: SETTINGS + // OS: P + USB_DEVICE_DETAILS = 1290; // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index db70184e9e9..08fdb9775d0 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -200,6 +200,10 @@ message SystemMessage { // Package: android NOTE_CARRIER_NETWORK_AVAILABLE = 46; + // Inform that USB is configured for Tethering + // Package: android + NOTE_USB_TETHER = 47; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 1ea2f976b80..d3022b4cf99 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -1072,6 +1072,8 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver int id = 0; int titleRes = 0; Resources r = mContext.getResources(); + CharSequence message = r.getText( + com.android.internal.R.string.usb_notification_message); if (mAudioAccessoryConnected && !mAudioAccessorySupported) { titleRes = com.android.internal.R.string.usb_unsupported_audio_accessory_title; id = SystemMessage.NOTE_USB_AUDIO_ACCESSORY_NOT_SUPPORTED; @@ -1085,13 +1087,22 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver } else if (mCurrentFunctions == UsbManager.FUNCTION_MIDI) { titleRes = com.android.internal.R.string.usb_midi_notification_title; id = SystemMessage.NOTE_USB_MIDI; + } else if (mCurrentFunctions == UsbManager.FUNCTION_RNDIS) { + titleRes = com.android.internal.R.string.usb_tether_notification_title; + id = SystemMessage.NOTE_USB_TETHER; } else if (mCurrentFunctions == UsbManager.FUNCTION_ACCESSORY) { titleRes = com.android.internal.R.string.usb_accessory_notification_title; id = SystemMessage.NOTE_USB_ACCESSORY; - } else if (mSourcePower) { - titleRes = com.android.internal.R.string.usb_supplying_notification_title; - id = SystemMessage.NOTE_USB_SUPPLYING; - } else { + } + if (mSourcePower) { + if (titleRes != 0) { + message = r.getText( + com.android.internal.R.string.usb_power_notification_message); + } else { + titleRes = com.android.internal.R.string.usb_supplying_notification_title; + id = SystemMessage.NOTE_USB_SUPPLYING; + } + } else if (titleRes == 0) { titleRes = com.android.internal.R.string.usb_charging_notification_title; id = SystemMessage.NOTE_USB_CHARGING; } @@ -1111,7 +1122,6 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver mUsbNotificationId = 0; } if (id != 0) { - CharSequence message; CharSequence title = r.getText(titleRes); PendingIntent pi; String channel; @@ -1121,12 +1131,10 @@ public class UsbDeviceManager implements ActivityManagerInternal.ScreenObserver .usb_unsupported_audio_accessory_title) { Intent intent = Intent.makeRestartActivityTask( new ComponentName("com.android.settings", - "com.android.settings.deviceinfo.UsbModeChooserActivity")); + "com.android.settings.Settings$UsbDetailsActivity")); pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT); channel = SystemNotificationChannels.USB; - message = r.getText( - com.android.internal.R.string.usb_notification_message); } else { final Intent intent = new Intent(); intent.setClassName("com.android.settings", -- GitLab From cc29db6572b8cf88e5aeece7089df490ab734658 Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Wed, 31 Jan 2018 11:48:45 -0800 Subject: [PATCH 189/416] Update api files from change in doclava stubs generator A change in the doclava stubs API generator omits the 'final' keyword from methods within a final class, since they already cannot be overridden. Bug: 72747497 Test: none Change-Id: I193781c281fbde13ccdaedeaee30f8d7c3b5f33e --- api/current.txt | 939 ++++++++++++++++++++--------------------- api/system-current.txt | 22 +- api/test-current.txt | 4 +- 3 files changed, 476 insertions(+), 489 deletions(-) diff --git a/api/current.txt b/api/current.txt index 63db31254a4..e7785815e06 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6085,16 +6085,16 @@ package android.app { method public android.os.ParcelFileDescriptor executeShellCommand(java.lang.String); method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow(); - method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo(); + method public android.accessibilityservice.AccessibilityServiceInfo getServiceInfo(); method public android.view.WindowAnimationFrameStats getWindowAnimationFrameStats(); method public android.view.WindowContentFrameStats getWindowContentFrameStats(int); method public java.util.List getWindows(); method public boolean injectInputEvent(android.view.InputEvent, boolean); - method public final boolean performGlobalAction(int); + method public boolean performGlobalAction(int); method public void setOnAccessibilityEventListener(android.app.UiAutomation.OnAccessibilityEventListener); method public boolean setRotation(int); method public void setRunAsMonkey(boolean); - method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); + method public void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo); method public android.graphics.Bitmap takeScreenshot(); method public void waitForIdle(long, long) throws java.util.concurrent.TimeoutException; field public static final int FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES = 1; // 0x1 @@ -11628,15 +11628,9 @@ package android.content.res { } public final class AssetManager.AssetInputStream extends java.io.InputStream { - method public final int available() throws java.io.IOException; - method public final void close() throws java.io.IOException; - method public final void mark(int); - method public final boolean markSupported(); - method public final int read() throws java.io.IOException; - method public final int read(byte[]) throws java.io.IOException; - method public final int read(byte[], int, int) throws java.io.IOException; - method public final void reset() throws java.io.IOException; - method public final long skip(long) throws java.io.IOException; + method public void mark(int); + method public int read() throws java.io.IOException; + method public void reset() throws java.io.IOException; } public class ColorStateList implements android.os.Parcelable { @@ -12398,7 +12392,7 @@ package android.database.sqlite { method public java.util.List> getAttachedDbs(); method public long getMaximumSize(); method public long getPageSize(); - method public final java.lang.String getPath(); + method public java.lang.String getPath(); method public deprecated java.util.Map getSyncedTables(); method public int getVersion(); method public boolean inTransaction(); @@ -13055,29 +13049,29 @@ package android.graphics { method public void eraseColor(int); method public android.graphics.Bitmap extractAlpha(); method public android.graphics.Bitmap extractAlpha(android.graphics.Paint, int[]); - method public final int getAllocationByteCount(); - method public final int getByteCount(); - method public final android.graphics.ColorSpace getColorSpace(); - method public final android.graphics.Bitmap.Config getConfig(); + method public int getAllocationByteCount(); + method public int getByteCount(); + method public android.graphics.ColorSpace getColorSpace(); + method public android.graphics.Bitmap.Config getConfig(); method public int getDensity(); method public int getGenerationId(); - method public final int getHeight(); + method public int getHeight(); method public byte[] getNinePatchChunk(); method public int getPixel(int, int); method public void getPixels(int[], int, int, int, int, int, int); - method public final int getRowBytes(); + method public int getRowBytes(); method public int getScaledHeight(android.graphics.Canvas); method public int getScaledHeight(android.util.DisplayMetrics); method public int getScaledHeight(int); method public int getScaledWidth(android.graphics.Canvas); method public int getScaledWidth(android.util.DisplayMetrics); method public int getScaledWidth(int); - method public final int getWidth(); - method public final boolean hasAlpha(); - method public final boolean hasMipMap(); - method public final boolean isMutable(); - method public final boolean isPremultiplied(); - method public final boolean isRecycled(); + method public int getWidth(); + method public boolean hasAlpha(); + method public boolean hasMipMap(); + method public boolean isMutable(); + method public boolean isPremultiplied(); + method public boolean isRecycled(); method public void prepareToDraw(); method public void reconfigure(int, int, android.graphics.Bitmap.Config); method public void recycle(); @@ -13085,11 +13079,11 @@ package android.graphics { method public void setConfig(android.graphics.Bitmap.Config); method public void setDensity(int); method public void setHasAlpha(boolean); - method public final void setHasMipMap(boolean); + method public void setHasMipMap(boolean); method public void setHeight(int); method public void setPixel(int, int, int); method public void setPixels(int[], int, int, int, int, int, int); - method public final void setPremultiplied(boolean); + method public void setPremultiplied(boolean); method public void setWidth(int); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; @@ -13161,7 +13155,7 @@ package android.graphics { method public android.graphics.Bitmap decodeRegion(android.graphics.Rect, android.graphics.BitmapFactory.Options); method public int getHeight(); method public int getWidth(); - method public final boolean isRecycled(); + method public boolean isRecycled(); method public static android.graphics.BitmapRegionDecoder newInstance(byte[], int, int, boolean) throws java.io.IOException; method public static android.graphics.BitmapRegionDecoder newInstance(java.io.FileDescriptor, boolean) throws java.io.IOException; method public static android.graphics.BitmapRegionDecoder newInstance(java.io.InputStream, boolean) throws java.io.IOException; @@ -14221,22 +14215,22 @@ package android.graphics { ctor public Rect(); ctor public Rect(int, int, int, int); ctor public Rect(android.graphics.Rect); - method public final int centerX(); - method public final int centerY(); + method public int centerX(); + method public int centerY(); method public boolean contains(int, int); method public boolean contains(int, int, int, int); method public boolean contains(android.graphics.Rect); method public int describeContents(); - method public final float exactCenterX(); - method public final float exactCenterY(); + method public float exactCenterX(); + method public float exactCenterY(); method public java.lang.String flattenToString(); - method public final int height(); + method public int height(); method public void inset(int, int); method public boolean intersect(int, int, int, int); method public boolean intersect(android.graphics.Rect); method public boolean intersects(int, int, int, int); method public static boolean intersects(android.graphics.Rect, android.graphics.Rect); - method public final boolean isEmpty(); + method public boolean isEmpty(); method public void offset(int, int); method public void offsetTo(int, int); method public void readFromParcel(android.os.Parcel); @@ -14250,7 +14244,7 @@ package android.graphics { method public void union(int, int, int, int); method public void union(android.graphics.Rect); method public void union(int, int); - method public final int width(); + method public int width(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public int bottom; @@ -15819,9 +15813,7 @@ package android.hardware.camera2 { } public static final class CameraCharacteristics.Key { - method public final boolean equals(java.lang.Object); method public java.lang.String getName(); - method public final int hashCode(); } public abstract class CameraConstrainedHighSpeedCaptureSession extends android.hardware.camera2.CameraCaptureSession { @@ -16183,9 +16175,7 @@ package android.hardware.camera2 { } public static final class CaptureRequest.Key { - method public final boolean equals(java.lang.Object); method public java.lang.String getName(); - method public final int hashCode(); } public class CaptureResult extends android.hardware.camera2.CameraMetadata { @@ -16278,9 +16268,7 @@ package android.hardware.camera2 { } public static final class CaptureResult.Key { - method public final boolean equals(java.lang.Object); method public java.lang.String getName(); - method public final int hashCode(); } public final class DngCreator implements java.lang.AutoCloseable { @@ -16392,7 +16380,7 @@ package android.hardware.camera2.params { method public float getComponent(int); method public float getGreenEven(); method public float getGreenOdd(); - method public final float getRed(); + method public float getRed(); field public static final int BLUE = 3; // 0x3 field public static final int COUNT = 4; // 0x4 field public static final int GREEN_EVEN = 1; // 0x1 @@ -16420,16 +16408,16 @@ package android.hardware.camera2.params { method public android.util.Range[] getHighSpeedVideoFpsRangesFor(android.util.Size); method public android.util.Size[] getHighSpeedVideoSizes(); method public android.util.Size[] getHighSpeedVideoSizesFor(android.util.Range); - method public final int[] getInputFormats(); + method public int[] getInputFormats(); method public android.util.Size[] getInputSizes(int); - method public final int[] getOutputFormats(); + method public int[] getOutputFormats(); method public long getOutputMinFrameDuration(int, android.util.Size); method public long getOutputMinFrameDuration(java.lang.Class, android.util.Size); method public android.util.Size[] getOutputSizes(java.lang.Class); method public android.util.Size[] getOutputSizes(int); method public long getOutputStallDuration(int, android.util.Size); method public long getOutputStallDuration(java.lang.Class, android.util.Size); - method public final int[] getValidOutputFormatsForInput(int); + method public int[] getValidOutputFormatsForInput(int); method public boolean isOutputSupportedFor(int); method public static boolean isOutputSupportedFor(java.lang.Class); method public boolean isOutputSupportedFor(android.view.Surface); @@ -16737,12 +16725,12 @@ package android.icu.lang { public final class UCharacter implements android.icu.lang.UCharacterEnums.ECharacterCategory android.icu.lang.UCharacterEnums.ECharacterDirection { method public static int charCount(int); - method public static final int codePointAt(java.lang.CharSequence, int); - method public static final int codePointAt(char[], int); - method public static final int codePointAt(char[], int, int); - method public static final int codePointBefore(java.lang.CharSequence, int); - method public static final int codePointBefore(char[], int); - method public static final int codePointBefore(char[], int, int); + method public static int codePointAt(java.lang.CharSequence, int); + method public static int codePointAt(char[], int); + method public static int codePointAt(char[], int, int); + method public static int codePointBefore(java.lang.CharSequence, int); + method public static int codePointBefore(char[], int); + method public static int codePointBefore(char[], int, int); method public static int codePointCount(java.lang.CharSequence, int, int); method public static int codePointCount(char[], int, int); method public static int digit(int, int); @@ -16750,7 +16738,7 @@ package android.icu.lang { method public static int foldCase(int, boolean); method public static java.lang.String foldCase(java.lang.String, boolean); method public static int foldCase(int, int); - method public static final java.lang.String foldCase(java.lang.String, int); + method public static java.lang.String foldCase(java.lang.String, int); method public static char forDigit(int, int); method public static android.icu.util.VersionInfo getAge(int); method public static int getBidiPairedBracket(int); @@ -16802,8 +16790,8 @@ package android.icu.lang { method public static boolean isPrintable(int); method public static boolean isSpaceChar(int); method public static boolean isSupplementary(int); - method public static final boolean isSupplementaryCodePoint(int); - method public static final boolean isSurrogatePair(char, char); + method public static boolean isSupplementaryCodePoint(int); + method public static boolean isSurrogatePair(char, char); method public static boolean isTitleCase(int); method public static boolean isUAlphabetic(int); method public static boolean isULowercase(int); @@ -16812,13 +16800,13 @@ package android.icu.lang { method public static boolean isUnicodeIdentifierPart(int); method public static boolean isUnicodeIdentifierStart(int); method public static boolean isUpperCase(int); - method public static final boolean isValidCodePoint(int); + method public static boolean isValidCodePoint(int); method public static boolean isWhitespace(int); method public static int offsetByCodePoints(java.lang.CharSequence, int, int); method public static int offsetByCodePoints(char[], int, int, int, int); - method public static final int toChars(int, char[], int); - method public static final char[] toChars(int); - method public static final int toCodePoint(char, char); + method public static int toChars(int, char[], int); + method public static char[] toChars(int); + method public static int toCodePoint(char, char); method public static int toLowerCase(int); method public static java.lang.String toLowerCase(java.lang.String); method public static java.lang.String toLowerCase(java.util.Locale, java.lang.String); @@ -17108,7 +17096,7 @@ package android.icu.lang { } public static final class UCharacter.UnicodeBlock extends java.lang.Character.Subset { - method public static final android.icu.lang.UCharacter.UnicodeBlock forName(java.lang.String); + method public static android.icu.lang.UCharacter.UnicodeBlock forName(java.lang.String); method public int getID(); method public static android.icu.lang.UCharacter.UnicodeBlock getInstance(int); method public static android.icu.lang.UCharacter.UnicodeBlock of(int); @@ -17915,20 +17903,20 @@ package android.icu.lang { } public final class UScript { - method public static final boolean breaksBetweenLetters(int); - method public static final int[] getCode(java.util.Locale); - method public static final int[] getCode(android.icu.util.ULocale); - method public static final int[] getCode(java.lang.String); - method public static final int getCodeFromName(java.lang.String); - method public static final java.lang.String getName(int); - method public static final java.lang.String getSampleString(int); - method public static final int getScript(int); - method public static final int getScriptExtensions(int, java.util.BitSet); - method public static final java.lang.String getShortName(int); - method public static final android.icu.lang.UScript.ScriptUsage getUsage(int); - method public static final boolean hasScript(int, int); - method public static final boolean isCased(int); - method public static final boolean isRightToLeft(int); + method public static boolean breaksBetweenLetters(int); + method public static int[] getCode(java.util.Locale); + method public static int[] getCode(android.icu.util.ULocale); + method public static int[] getCode(java.lang.String); + method public static int getCodeFromName(java.lang.String); + method public static java.lang.String getName(int); + method public static java.lang.String getSampleString(int); + method public static int getScript(int); + method public static int getScriptExtensions(int, java.util.BitSet); + method public static java.lang.String getShortName(int); + method public static android.icu.lang.UScript.ScriptUsage getUsage(int); + method public static boolean hasScript(int, int); + method public static boolean isCased(int); + method public static boolean isRightToLeft(int); field public static final int ADLAM = 167; // 0xa7 field public static final int AFAKA = 147; // 0x93 field public static final int AHOM = 161; // 0xa1 @@ -18343,14 +18331,14 @@ package android.icu.text { method public deprecated int hashCode(); method public int next(); method public int previous(); - method public static final int primaryOrder(int); + method public static int primaryOrder(int); method public void reset(); - method public static final int secondaryOrder(int); + method public static int secondaryOrder(int); method public void setOffset(int); method public void setText(java.lang.String); method public void setText(android.icu.text.UCharacterIterator); method public void setText(java.text.CharacterIterator); - method public static final int tertiaryOrder(int); + method public static int tertiaryOrder(int); field public static final int IGNORABLE = 0; // 0x0 field public static final int NULLORDER = -1; // 0xffffffff } @@ -19577,7 +19565,7 @@ package android.icu.text { method public boolean isUpperCaseFirst(); method public void setAlternateHandlingDefault(); method public void setAlternateHandlingShifted(boolean); - method public final void setCaseFirstDefault(); + method public void setCaseFirstDefault(); method public void setCaseLevel(boolean); method public void setCaseLevelDefault(); method public void setDecompositionDefault(); @@ -20550,11 +20538,11 @@ package android.icu.util { public final class LocaleData { method public static android.icu.util.VersionInfo getCLDRVersion(); method public java.lang.String getDelimiter(int); - method public static final android.icu.util.LocaleData getInstance(android.icu.util.ULocale); - method public static final android.icu.util.LocaleData getInstance(); - method public static final android.icu.util.LocaleData.MeasurementSystem getMeasurementSystem(android.icu.util.ULocale); + method public static android.icu.util.LocaleData getInstance(android.icu.util.ULocale); + method public static android.icu.util.LocaleData getInstance(); + method public static android.icu.util.LocaleData.MeasurementSystem getMeasurementSystem(android.icu.util.ULocale); method public boolean getNoSubstitute(); - method public static final android.icu.util.LocaleData.PaperSize getPaperSize(android.icu.util.ULocale); + method public static android.icu.util.LocaleData.PaperSize getPaperSize(android.icu.util.ULocale); method public void setNoSubstitute(boolean); field public static final int ALT_QUOTATION_END = 3; // 0x3 field public static final int ALT_QUOTATION_START = 2; // 0x2 @@ -22826,40 +22814,40 @@ package android.media { method public static android.media.MediaCodec createByCodecName(java.lang.String) throws java.io.IOException; method public static android.media.MediaCodec createDecoderByType(java.lang.String) throws java.io.IOException; method public static android.media.MediaCodec createEncoderByType(java.lang.String) throws java.io.IOException; - method public final android.view.Surface createInputSurface(); + method public android.view.Surface createInputSurface(); method public static android.view.Surface createPersistentInputSurface(); - method public final int dequeueInputBuffer(long); - method public final int dequeueOutputBuffer(android.media.MediaCodec.BufferInfo, long); + method public int dequeueInputBuffer(long); + method public int dequeueOutputBuffer(android.media.MediaCodec.BufferInfo, long); method protected void finalize(); - method public final void flush(); + method public void flush(); method public android.media.MediaCodecInfo getCodecInfo(); method public java.nio.ByteBuffer getInputBuffer(int); method public deprecated java.nio.ByteBuffer[] getInputBuffers(); - method public final android.media.MediaFormat getInputFormat(); + method public android.media.MediaFormat getInputFormat(); method public android.media.Image getInputImage(int); method public android.os.PersistableBundle getMetrics(); - method public final java.lang.String getName(); + method public java.lang.String getName(); method public java.nio.ByteBuffer getOutputBuffer(int); method public deprecated java.nio.ByteBuffer[] getOutputBuffers(); - method public final android.media.MediaFormat getOutputFormat(); - method public final android.media.MediaFormat getOutputFormat(int); + method public android.media.MediaFormat getOutputFormat(); + method public android.media.MediaFormat getOutputFormat(int); method public android.media.Image getOutputImage(int); - method public final void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException; - method public final void queueSecureInputBuffer(int, int, android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException; - method public final void release(); - method public final void releaseOutputBuffer(int, boolean); - method public final void releaseOutputBuffer(int, long); - method public final void reset(); + method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException; + method public void queueSecureInputBuffer(int, int, android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException; + method public void release(); + method public void releaseOutputBuffer(int, boolean); + method public void releaseOutputBuffer(int, long); + method public void reset(); method public void setCallback(android.media.MediaCodec.Callback, android.os.Handler); method public void setCallback(android.media.MediaCodec.Callback); method public void setInputSurface(android.view.Surface); method public void setOnFrameRenderedListener(android.media.MediaCodec.OnFrameRenderedListener, android.os.Handler); method public void setOutputSurface(android.view.Surface); - method public final void setParameters(android.os.Bundle); - method public final void setVideoScalingMode(int); - method public final void signalEndOfInputStream(); - method public final void start(); - method public final void stop(); + method public void setParameters(android.os.Bundle); + method public void setVideoScalingMode(int); + method public void signalEndOfInputStream(); + method public void start(); + method public void stop(); field public static final int BUFFER_FLAG_CODEC_CONFIG = 2; // 0x2 field public static final int BUFFER_FLAG_END_OF_STREAM = 4; // 0x4 field public static final int BUFFER_FLAG_KEY_FRAME = 1; // 0x1 @@ -22953,10 +22941,10 @@ package android.media { } public final class MediaCodecInfo { - method public final android.media.MediaCodecInfo.CodecCapabilities getCapabilitiesForType(java.lang.String); - method public final java.lang.String getName(); - method public final java.lang.String[] getSupportedTypes(); - method public final boolean isEncoder(); + method public android.media.MediaCodecInfo.CodecCapabilities getCapabilitiesForType(java.lang.String); + method public java.lang.String getName(); + method public java.lang.String[] getSupportedTypes(); + method public boolean isEncoder(); } public static final class MediaCodecInfo.AudioCapabilities { @@ -22976,9 +22964,9 @@ package android.media { method public int getMaxSupportedInstances(); method public java.lang.String getMimeType(); method public android.media.MediaCodecInfo.VideoCapabilities getVideoCapabilities(); - method public final boolean isFeatureRequired(java.lang.String); - method public final boolean isFeatureSupported(java.lang.String); - method public final boolean isFormatSupported(android.media.MediaFormat); + method public boolean isFeatureRequired(java.lang.String); + method public boolean isFeatureSupported(java.lang.String); + method public boolean isFormatSupported(android.media.MediaFormat); field public static final deprecated int COLOR_Format12bitRGB444 = 3; // 0x3 field public static final deprecated int COLOR_Format16bitARGB1555 = 5; // 0x5 field public static final deprecated int COLOR_Format16bitARGB4444 = 4; // 0x4 @@ -23236,11 +23224,11 @@ package android.media { public final class MediaCodecList { ctor public MediaCodecList(int); - method public final java.lang.String findDecoderForFormat(android.media.MediaFormat); - method public final java.lang.String findEncoderForFormat(android.media.MediaFormat); - method public static final deprecated int getCodecCount(); - method public static final deprecated android.media.MediaCodecInfo getCodecInfoAt(int); - method public final android.media.MediaCodecInfo[] getCodecInfos(); + method public java.lang.String findDecoderForFormat(android.media.MediaFormat); + method public java.lang.String findEncoderForFormat(android.media.MediaFormat); + method public static deprecated int getCodecCount(); + method public static deprecated android.media.MediaCodecInfo getCodecInfoAt(int); + method public android.media.MediaCodecInfo[] getCodecInfos(); field public static final int ALL_CODECS = 1; // 0x1 field public static final int REGULAR_CODECS = 0; // 0x0 } @@ -23248,10 +23236,10 @@ package android.media { public final class MediaCrypto { ctor public MediaCrypto(java.util.UUID, byte[]) throws android.media.MediaCryptoException; method protected void finalize(); - method public static final boolean isCryptoSchemeSupported(java.util.UUID); - method public final void release(); - method public final boolean requiresSecureDecoderComponent(java.lang.String); - method public final void setMediaDrmSession(byte[]) throws android.media.MediaCryptoException; + method public static boolean isCryptoSchemeSupported(java.util.UUID); + method public void release(); + method public boolean requiresSecureDecoderComponent(java.lang.String); + method public void setMediaDrmSession(byte[]) throws android.media.MediaCryptoException; } public final class MediaCryptoException extends java.lang.Exception { @@ -23267,10 +23255,10 @@ package android.media { public final class MediaDescrambler implements java.lang.AutoCloseable { ctor public MediaDescrambler(int) throws android.media.MediaCasException.UnsupportedCasException; method public void close(); - method public final int descramble(java.nio.ByteBuffer, java.nio.ByteBuffer, android.media.MediaCodec.CryptoInfo); + method public int descramble(java.nio.ByteBuffer, java.nio.ByteBuffer, android.media.MediaCodec.CryptoInfo); method protected void finalize(); - method public final boolean requiresSecureDecoderComponent(java.lang.String); - method public final void setMediaCasSession(android.media.MediaCas.Session); + method public boolean requiresSecureDecoderComponent(java.lang.String); + method public void setMediaCasSession(android.media.MediaCas.Session); } public class MediaDescription implements android.os.Parcelable { @@ -23326,8 +23314,8 @@ package android.media { method public java.util.List getSecureStopIds(); method public java.util.List getSecureStops(); method public int getSecurityLevel(byte[]); - method public static final boolean isCryptoSchemeSupported(java.util.UUID); - method public static final boolean isCryptoSchemeSupported(java.util.UUID, java.lang.String); + method public static boolean isCryptoSchemeSupported(java.util.UUID); + method public static boolean isCryptoSchemeSupported(java.util.UUID, java.lang.String); method public byte[] openSession() throws android.media.NotProvisionedException, android.media.ResourceBusyException; method public byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.NotProvisionedException; method public void provideProvisionResponse(byte[]) throws android.media.DeniedByServerException; @@ -23488,21 +23476,21 @@ package android.media { method public long getSampleSize(); method public long getSampleTime(); method public int getSampleTrackIndex(); - method public final int getTrackCount(); + method public int getTrackCount(); method public android.media.MediaFormat getTrackFormat(int); method public boolean hasCacheReachedEndOfStream(); method public int readSampleData(java.nio.ByteBuffer, int); - method public final void release(); + method public void release(); method public void seekTo(long, int); method public void selectTrack(int); - method public final void setDataSource(android.media.MediaDataSource) throws java.io.IOException; - method public final void setDataSource(android.content.Context, android.net.Uri, java.util.Map) throws java.io.IOException; - method public final void setDataSource(java.lang.String, java.util.Map) throws java.io.IOException; - method public final void setDataSource(java.lang.String) throws java.io.IOException; - method public final void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; - method public final void setDataSource(java.io.FileDescriptor) throws java.io.IOException; - method public final void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException; - method public final void setMediaCas(android.media.MediaCas); + method public void setDataSource(android.media.MediaDataSource) throws java.io.IOException; + method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map) throws java.io.IOException; + method public void setDataSource(java.lang.String, java.util.Map) throws java.io.IOException; + method public void setDataSource(java.lang.String) throws java.io.IOException; + method public void setDataSource(android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException; + method public void setDataSource(java.io.FileDescriptor) throws java.io.IOException; + method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException; + method public void setMediaCas(android.media.MediaCas); method public void unselectTrack(int); field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2 field public static final int SAMPLE_FLAG_PARTIAL_FRAME = 4; // 0x4 @@ -23525,22 +23513,22 @@ package android.media { public final class MediaFormat { ctor public MediaFormat(); - method public final boolean containsKey(java.lang.String); - method public static final android.media.MediaFormat createAudioFormat(java.lang.String, int, int); - method public static final android.media.MediaFormat createSubtitleFormat(java.lang.String, java.lang.String); - method public static final android.media.MediaFormat createVideoFormat(java.lang.String, int, int); - method public final java.nio.ByteBuffer getByteBuffer(java.lang.String); + method public boolean containsKey(java.lang.String); + method public static android.media.MediaFormat createAudioFormat(java.lang.String, int, int); + method public static android.media.MediaFormat createSubtitleFormat(java.lang.String, java.lang.String); + method public static android.media.MediaFormat createVideoFormat(java.lang.String, int, int); + method public java.nio.ByteBuffer getByteBuffer(java.lang.String); method public boolean getFeatureEnabled(java.lang.String); - method public final float getFloat(java.lang.String); - method public final int getInteger(java.lang.String); - method public final long getLong(java.lang.String); - method public final java.lang.String getString(java.lang.String); - method public final void setByteBuffer(java.lang.String, java.nio.ByteBuffer); + method public float getFloat(java.lang.String); + method public int getInteger(java.lang.String); + method public long getLong(java.lang.String); + method public java.lang.String getString(java.lang.String); + method public void setByteBuffer(java.lang.String, java.nio.ByteBuffer); method public void setFeatureEnabled(java.lang.String, boolean); - method public final void setFloat(java.lang.String, float); - method public final void setInteger(java.lang.String, int); - method public final void setLong(java.lang.String, long); - method public final void setString(java.lang.String, java.lang.String); + method public void setFloat(java.lang.String, float); + method public void setInteger(java.lang.String, int); + method public void setLong(java.lang.String, long); + method public void setString(java.lang.String, java.lang.String); field public static final int COLOR_RANGE_FULL = 1; // 0x1 field public static final int COLOR_RANGE_LIMITED = 2; // 0x2 field public static final int COLOR_STANDARD_BT2020 = 6; // 0x6 @@ -24439,14 +24427,14 @@ package android.media { public final class MediaSync { ctor public MediaSync(); - method public final android.view.Surface createInputSurface(); + method public android.view.Surface createInputSurface(); method protected void finalize(); method public void flush(); method public android.media.PlaybackParams getPlaybackParams(); method public android.media.SyncParams getSyncParams(); method public android.media.MediaTimestamp getTimestamp(); method public void queueAudio(java.nio.ByteBuffer, int, long); - method public final void release(); + method public void release(); method public void setAudioTrack(android.media.AudioTrack); method public void setCallback(android.media.MediaSync.Callback, android.os.Handler); method public void setOnErrorListener(android.media.MediaSync.OnErrorListener, android.os.Handler); @@ -25401,7 +25389,7 @@ package android.media.midi { public final class MidiInputPort extends android.media.midi.MidiReceiver implements java.io.Closeable { method public void close() throws java.io.IOException; - method public final int getPortNumber(); + method public int getPortNumber(); method public void onSend(byte[], int, int, long) throws java.io.IOException; } @@ -25426,7 +25414,7 @@ package android.media.midi { public final class MidiOutputPort extends android.media.midi.MidiSender implements java.io.Closeable { method public void close() throws java.io.IOException; - method public final int getPortNumber(); + method public int getPortNumber(); method public void onConnect(android.media.midi.MidiReceiver); method public void onDisconnect(android.media.midi.MidiReceiver); } @@ -25701,7 +25689,7 @@ package android.media.session { package android.media.tv { public final class TvContentRating { - method public final boolean contains(android.media.tv.TvContentRating); + method public boolean contains(android.media.tv.TvContentRating); method public static android.media.tv.TvContentRating createRating(java.lang.String, java.lang.String, java.lang.String, java.lang.String...); method public java.lang.String flattenToString(); method public java.lang.String getDomain(); @@ -25751,7 +25739,7 @@ package android.media.tv { } public static final class TvContract.Channels implements android.media.tv.TvContract.BaseTvColumns { - method public static final java.lang.String getVideoResolution(java.lang.String); + method public static java.lang.String getVideoResolution(java.lang.String); field public static final java.lang.String COLUMN_APP_LINK_COLOR = "app_link_color"; field public static final java.lang.String COLUMN_APP_LINK_ICON_URI = "app_link_icon_uri"; field public static final java.lang.String COLUMN_APP_LINK_INTENT_URI = "app_link_intent_uri"; @@ -26276,18 +26264,18 @@ package android.media.tv { public final class TvTrackInfo implements android.os.Parcelable { method public int describeContents(); - method public final int getAudioChannelCount(); - method public final int getAudioSampleRate(); - method public final java.lang.CharSequence getDescription(); - method public final android.os.Bundle getExtra(); - method public final java.lang.String getId(); - method public final java.lang.String getLanguage(); - method public final int getType(); - method public final byte getVideoActiveFormatDescription(); - method public final float getVideoFrameRate(); - method public final int getVideoHeight(); - method public final float getVideoPixelAspectRatio(); - method public final int getVideoWidth(); + method public int getAudioChannelCount(); + method public int getAudioSampleRate(); + method public java.lang.CharSequence getDescription(); + method public android.os.Bundle getExtra(); + method public java.lang.String getId(); + method public java.lang.String getLanguage(); + method public int getType(); + method public byte getVideoActiveFormatDescription(); + method public float getVideoFrameRate(); + method public int getVideoHeight(); + method public float getVideoPixelAspectRatio(); + method public int getVideoWidth(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; field public static final int TYPE_AUDIO = 0; // 0x0 @@ -26298,16 +26286,16 @@ package android.media.tv { public static final class TvTrackInfo.Builder { ctor public TvTrackInfo.Builder(int, java.lang.String); method public android.media.tv.TvTrackInfo build(); - method public final android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int); - method public final android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int); - method public final android.media.tv.TvTrackInfo.Builder setDescription(java.lang.CharSequence); - method public final android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle); - method public final android.media.tv.TvTrackInfo.Builder setLanguage(java.lang.String); - method public final android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte); - method public final android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float); - method public final android.media.tv.TvTrackInfo.Builder setVideoHeight(int); - method public final android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float); - method public final android.media.tv.TvTrackInfo.Builder setVideoWidth(int); + method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int); + method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int); + method public android.media.tv.TvTrackInfo.Builder setDescription(java.lang.CharSequence); + method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle); + method public android.media.tv.TvTrackInfo.Builder setLanguage(java.lang.String); + method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte); + method public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float); + method public android.media.tv.TvTrackInfo.Builder setVideoHeight(int); + method public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float); + method public android.media.tv.TvTrackInfo.Builder setVideoWidth(int); } public class TvView extends android.view.ViewGroup { @@ -26538,34 +26526,34 @@ package android.mtp { } public final class MtpObjectInfo { - method public final int getAssociationDesc(); - method public final int getAssociationType(); - method public final int getCompressedSize(); - method public final long getCompressedSizeLong(); - method public final long getDateCreated(); - method public final long getDateModified(); - method public final int getFormat(); - method public final int getImagePixDepth(); - method public final long getImagePixDepthLong(); - method public final int getImagePixHeight(); - method public final long getImagePixHeightLong(); - method public final int getImagePixWidth(); - method public final long getImagePixWidthLong(); - method public final java.lang.String getKeywords(); - method public final java.lang.String getName(); - method public final int getObjectHandle(); - method public final int getParent(); - method public final int getProtectionStatus(); - method public final int getSequenceNumber(); - method public final long getSequenceNumberLong(); - method public final int getStorageId(); - method public final int getThumbCompressedSize(); - method public final long getThumbCompressedSizeLong(); - method public final int getThumbFormat(); - method public final int getThumbPixHeight(); - method public final long getThumbPixHeightLong(); - method public final int getThumbPixWidth(); - method public final long getThumbPixWidthLong(); + method public int getAssociationDesc(); + method public int getAssociationType(); + method public int getCompressedSize(); + method public long getCompressedSizeLong(); + method public long getDateCreated(); + method public long getDateModified(); + method public int getFormat(); + method public int getImagePixDepth(); + method public long getImagePixDepthLong(); + method public int getImagePixHeight(); + method public long getImagePixHeightLong(); + method public int getImagePixWidth(); + method public long getImagePixWidthLong(); + method public java.lang.String getKeywords(); + method public java.lang.String getName(); + method public int getObjectHandle(); + method public int getParent(); + method public int getProtectionStatus(); + method public int getSequenceNumber(); + method public long getSequenceNumberLong(); + method public int getStorageId(); + method public int getThumbCompressedSize(); + method public long getThumbCompressedSizeLong(); + method public int getThumbFormat(); + method public int getThumbPixHeight(); + method public long getThumbPixHeightLong(); + method public int getThumbPixWidth(); + method public long getThumbPixWidthLong(); } public static class MtpObjectInfo.Builder { @@ -26595,11 +26583,11 @@ package android.mtp { } public final class MtpStorageInfo { - method public final java.lang.String getDescription(); - method public final long getFreeSpace(); - method public final long getMaxCapacity(); - method public final int getStorageId(); - method public final java.lang.String getVolumeIdentifier(); + method public java.lang.String getDescription(); + method public long getFreeSpace(); + method public long getMaxCapacity(); + method public int getStorageId(); + method public java.lang.String getVolumeIdentifier(); } } @@ -27032,10 +27020,10 @@ package android.net { public final class Proxy { ctor public Proxy(); - method public static final deprecated java.lang.String getDefaultHost(); - method public static final deprecated int getDefaultPort(); - method public static final deprecated java.lang.String getHost(android.content.Context); - method public static final deprecated int getPort(android.content.Context); + method public static deprecated java.lang.String getDefaultHost(); + method public static deprecated int getDefaultPort(); + method public static deprecated java.lang.String getHost(android.content.Context); + method public static deprecated int getPort(android.content.Context); field public static final deprecated java.lang.String EXTRA_PROXY_INFO = "android.intent.extra.PROXY_INFO"; field public static final java.lang.String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; } @@ -32062,9 +32050,9 @@ package android.os { method public static void dumpHprofData(java.lang.String) throws java.io.IOException; method public static boolean dumpService(java.lang.String, java.io.FileDescriptor, java.lang.String[]); method public static void enableEmulatorTraceOutput(); - method public static final int getBinderDeathObjectCount(); - method public static final int getBinderLocalObjectCount(); - method public static final int getBinderProxyObjectCount(); + method public static int getBinderDeathObjectCount(); + method public static int getBinderLocalObjectCount(); + method public static int getBinderProxyObjectCount(); method public static int getBinderReceivedTransactions(); method public static int getBinderSentTransactions(); method public static deprecated int getGlobalAllocCount(); @@ -32473,114 +32461,114 @@ package android.os { } public final class Parcel { - method public final void appendFrom(android.os.Parcel, int, int); - method public final android.os.IBinder[] createBinderArray(); - method public final java.util.ArrayList createBinderArrayList(); - method public final boolean[] createBooleanArray(); - method public final byte[] createByteArray(); - method public final char[] createCharArray(); - method public final double[] createDoubleArray(); - method public final float[] createFloatArray(); - method public final int[] createIntArray(); - method public final long[] createLongArray(); - method public final java.lang.String[] createStringArray(); - method public final java.util.ArrayList createStringArrayList(); - method public final T[] createTypedArray(android.os.Parcelable.Creator); - method public final java.util.ArrayList createTypedArrayList(android.os.Parcelable.Creator); - method public final int dataAvail(); - method public final int dataCapacity(); - method public final int dataPosition(); - method public final int dataSize(); - method public final void enforceInterface(java.lang.String); - method public final boolean hasFileDescriptors(); - method public final byte[] marshall(); + method public void appendFrom(android.os.Parcel, int, int); + method public android.os.IBinder[] createBinderArray(); + method public java.util.ArrayList createBinderArrayList(); + method public boolean[] createBooleanArray(); + method public byte[] createByteArray(); + method public char[] createCharArray(); + method public double[] createDoubleArray(); + method public float[] createFloatArray(); + method public int[] createIntArray(); + method public long[] createLongArray(); + method public java.lang.String[] createStringArray(); + method public java.util.ArrayList createStringArrayList(); + method public T[] createTypedArray(android.os.Parcelable.Creator); + method public java.util.ArrayList createTypedArrayList(android.os.Parcelable.Creator); + method public int dataAvail(); + method public int dataCapacity(); + method public int dataPosition(); + method public int dataSize(); + method public void enforceInterface(java.lang.String); + method public boolean hasFileDescriptors(); + method public byte[] marshall(); method public static android.os.Parcel obtain(); - method public final java.lang.Object[] readArray(java.lang.ClassLoader); - method public final java.util.ArrayList readArrayList(java.lang.ClassLoader); - method public final void readBinderArray(android.os.IBinder[]); - method public final void readBinderList(java.util.List); - method public final void readBooleanArray(boolean[]); - method public final android.os.Bundle readBundle(); - method public final android.os.Bundle readBundle(java.lang.ClassLoader); - method public final byte readByte(); - method public final void readByteArray(byte[]); - method public final void readCharArray(char[]); - method public final double readDouble(); - method public final void readDoubleArray(double[]); - method public final void readException(); - method public final void readException(int, java.lang.String); - method public final android.os.ParcelFileDescriptor readFileDescriptor(); - method public final float readFloat(); - method public final void readFloatArray(float[]); - method public final java.util.HashMap readHashMap(java.lang.ClassLoader); - method public final int readInt(); - method public final void readIntArray(int[]); - method public final void readList(java.util.List, java.lang.ClassLoader); - method public final long readLong(); - method public final void readLongArray(long[]); - method public final void readMap(java.util.Map, java.lang.ClassLoader); - method public final T readParcelable(java.lang.ClassLoader); - method public final android.os.Parcelable[] readParcelableArray(java.lang.ClassLoader); - method public final android.os.PersistableBundle readPersistableBundle(); - method public final android.os.PersistableBundle readPersistableBundle(java.lang.ClassLoader); - method public final java.io.Serializable readSerializable(); - method public final android.util.Size readSize(); - method public final android.util.SizeF readSizeF(); - method public final android.util.SparseArray readSparseArray(java.lang.ClassLoader); - method public final android.util.SparseBooleanArray readSparseBooleanArray(); - method public final java.lang.String readString(); - method public final void readStringArray(java.lang.String[]); - method public final void readStringList(java.util.List); - method public final android.os.IBinder readStrongBinder(); - method public final void readTypedArray(T[], android.os.Parcelable.Creator); - method public final void readTypedList(java.util.List, android.os.Parcelable.Creator); - method public final T readTypedObject(android.os.Parcelable.Creator); - method public final java.lang.Object readValue(java.lang.ClassLoader); - method public final void recycle(); - method public final void setDataCapacity(int); - method public final void setDataPosition(int); - method public final void setDataSize(int); - method public final void unmarshall(byte[], int, int); - method public final void writeArray(java.lang.Object[]); - method public final void writeBinderArray(android.os.IBinder[]); - method public final void writeBinderList(java.util.List); - method public final void writeBooleanArray(boolean[]); - method public final void writeBundle(android.os.Bundle); - method public final void writeByte(byte); - method public final void writeByteArray(byte[]); - method public final void writeByteArray(byte[], int, int); - method public final void writeCharArray(char[]); - method public final void writeDouble(double); - method public final void writeDoubleArray(double[]); - method public final void writeException(java.lang.Exception); - method public final void writeFileDescriptor(java.io.FileDescriptor); - method public final void writeFloat(float); - method public final void writeFloatArray(float[]); - method public final void writeInt(int); - method public final void writeIntArray(int[]); - method public final void writeInterfaceToken(java.lang.String); - method public final void writeList(java.util.List); - method public final void writeLong(long); - method public final void writeLongArray(long[]); - method public final void writeMap(java.util.Map); - method public final void writeNoException(); - method public final void writeParcelable(android.os.Parcelable, int); - method public final void writeParcelableArray(T[], int); - method public final void writePersistableBundle(android.os.PersistableBundle); - method public final void writeSerializable(java.io.Serializable); - method public final void writeSize(android.util.Size); - method public final void writeSizeF(android.util.SizeF); - method public final void writeSparseArray(android.util.SparseArray); - method public final void writeSparseBooleanArray(android.util.SparseBooleanArray); - method public final void writeString(java.lang.String); - method public final void writeStringArray(java.lang.String[]); - method public final void writeStringList(java.util.List); - method public final void writeStrongBinder(android.os.IBinder); - method public final void writeStrongInterface(android.os.IInterface); - method public final void writeTypedArray(T[], int); - method public final void writeTypedList(java.util.List); - method public final void writeTypedObject(T, int); - method public final void writeValue(java.lang.Object); + method public java.lang.Object[] readArray(java.lang.ClassLoader); + method public java.util.ArrayList readArrayList(java.lang.ClassLoader); + method public void readBinderArray(android.os.IBinder[]); + method public void readBinderList(java.util.List); + method public void readBooleanArray(boolean[]); + method public android.os.Bundle readBundle(); + method public android.os.Bundle readBundle(java.lang.ClassLoader); + method public byte readByte(); + method public void readByteArray(byte[]); + method public void readCharArray(char[]); + method public double readDouble(); + method public void readDoubleArray(double[]); + method public void readException(); + method public void readException(int, java.lang.String); + method public android.os.ParcelFileDescriptor readFileDescriptor(); + method public float readFloat(); + method public void readFloatArray(float[]); + method public java.util.HashMap readHashMap(java.lang.ClassLoader); + method public int readInt(); + method public void readIntArray(int[]); + method public void readList(java.util.List, java.lang.ClassLoader); + method public long readLong(); + method public void readLongArray(long[]); + method public void readMap(java.util.Map, java.lang.ClassLoader); + method public T readParcelable(java.lang.ClassLoader); + method public android.os.Parcelable[] readParcelableArray(java.lang.ClassLoader); + method public android.os.PersistableBundle readPersistableBundle(); + method public android.os.PersistableBundle readPersistableBundle(java.lang.ClassLoader); + method public java.io.Serializable readSerializable(); + method public android.util.Size readSize(); + method public android.util.SizeF readSizeF(); + method public android.util.SparseArray readSparseArray(java.lang.ClassLoader); + method public android.util.SparseBooleanArray readSparseBooleanArray(); + method public java.lang.String readString(); + method public void readStringArray(java.lang.String[]); + method public void readStringList(java.util.List); + method public android.os.IBinder readStrongBinder(); + method public void readTypedArray(T[], android.os.Parcelable.Creator); + method public void readTypedList(java.util.List, android.os.Parcelable.Creator); + method public T readTypedObject(android.os.Parcelable.Creator); + method public java.lang.Object readValue(java.lang.ClassLoader); + method public void recycle(); + method public void setDataCapacity(int); + method public void setDataPosition(int); + method public void setDataSize(int); + method public void unmarshall(byte[], int, int); + method public void writeArray(java.lang.Object[]); + method public void writeBinderArray(android.os.IBinder[]); + method public void writeBinderList(java.util.List); + method public void writeBooleanArray(boolean[]); + method public void writeBundle(android.os.Bundle); + method public void writeByte(byte); + method public void writeByteArray(byte[]); + method public void writeByteArray(byte[], int, int); + method public void writeCharArray(char[]); + method public void writeDouble(double); + method public void writeDoubleArray(double[]); + method public void writeException(java.lang.Exception); + method public void writeFileDescriptor(java.io.FileDescriptor); + method public void writeFloat(float); + method public void writeFloatArray(float[]); + method public void writeInt(int); + method public void writeIntArray(int[]); + method public void writeInterfaceToken(java.lang.String); + method public void writeList(java.util.List); + method public void writeLong(long); + method public void writeLongArray(long[]); + method public void writeMap(java.util.Map); + method public void writeNoException(); + method public void writeParcelable(android.os.Parcelable, int); + method public void writeParcelableArray(T[], int); + method public void writePersistableBundle(android.os.PersistableBundle); + method public void writeSerializable(java.io.Serializable); + method public void writeSize(android.util.Size); + method public void writeSizeF(android.util.SizeF); + method public void writeSparseArray(android.util.SparseArray); + method public void writeSparseBooleanArray(android.util.SparseBooleanArray); + method public void writeString(java.lang.String); + method public void writeStringArray(java.lang.String[]); + method public void writeStringList(java.util.List); + method public void writeStrongBinder(android.os.IBinder); + method public void writeStrongInterface(android.os.IInterface); + method public void writeTypedArray(T[], int); + method public void writeTypedList(java.util.List); + method public void writeTypedObject(T, int); + method public void writeValue(java.lang.Object); field public static final android.os.Parcelable.Creator STRING_CREATOR; } @@ -34234,7 +34222,7 @@ package android.provider { } public static final class CalendarContract.Attendees implements android.provider.BaseColumns android.provider.CalendarContract.AttendeesColumns android.provider.CalendarContract.EventsColumns { - method public static final android.database.Cursor query(android.content.ContentResolver, long, java.lang.String[]); + method public static android.database.Cursor query(android.content.ContentResolver, long, java.lang.String[]); field public static final android.net.Uri CONTENT_URI; } @@ -34363,7 +34351,7 @@ package android.provider { } public static final class CalendarContract.EventDays implements android.provider.CalendarContract.EventDaysColumns { - method public static final android.database.Cursor query(android.content.ContentResolver, int, int, java.lang.String[]); + method public static android.database.Cursor query(android.content.ContentResolver, int, int, java.lang.String[]); field public static final android.net.Uri CONTENT_URI; } @@ -34456,8 +34444,8 @@ package android.provider { } public static final class CalendarContract.Instances implements android.provider.BaseColumns android.provider.CalendarContract.CalendarColumns android.provider.CalendarContract.EventsColumns { - method public static final android.database.Cursor query(android.content.ContentResolver, java.lang.String[], long, long); - method public static final android.database.Cursor query(android.content.ContentResolver, java.lang.String[], long, long, java.lang.String); + method public static android.database.Cursor query(android.content.ContentResolver, java.lang.String[], long, long); + method public static android.database.Cursor query(android.content.ContentResolver, java.lang.String[], long, long, java.lang.String); field public static final java.lang.String BEGIN = "begin"; field public static final android.net.Uri CONTENT_BY_DAY_URI; field public static final android.net.Uri CONTENT_SEARCH_BY_DAY_URI; @@ -34472,7 +34460,7 @@ package android.provider { } public static final class CalendarContract.Reminders implements android.provider.BaseColumns android.provider.CalendarContract.EventsColumns android.provider.CalendarContract.RemindersColumns { - method public static final android.database.Cursor query(android.content.ContentResolver, long, java.lang.String[]); + method public static android.database.Cursor query(android.content.ContentResolver, long, java.lang.String[]); field public static final android.net.Uri CONTENT_URI; } @@ -34581,7 +34569,7 @@ package android.provider { method public static deprecated java.lang.Object decodeImProtocol(java.lang.String); method public static deprecated java.lang.String encodeCustomImProtocol(java.lang.String); method public static deprecated java.lang.String encodePredefinedImProtocol(int); - method public static final deprecated java.lang.CharSequence getDisplayLabel(android.content.Context, int, int, java.lang.CharSequence); + method public static deprecated java.lang.CharSequence getDisplayLabel(android.content.Context, int, int, java.lang.CharSequence); field public static final deprecated java.lang.String CONTENT_EMAIL_ITEM_TYPE = "vnd.android.cursor.item/email"; field public static final deprecated java.lang.String CONTENT_EMAIL_TYPE = "vnd.android.cursor.dir/email"; field public static final deprecated android.net.Uri CONTENT_EMAIL_URI; @@ -34731,7 +34719,7 @@ package android.provider { } public static final deprecated class Contacts.Organizations implements android.provider.BaseColumns android.provider.Contacts.OrganizationColumns { - method public static final deprecated java.lang.CharSequence getDisplayLabel(android.content.Context, int, java.lang.CharSequence); + method public static deprecated java.lang.CharSequence getDisplayLabel(android.content.Context, int, java.lang.CharSequence); field public static final deprecated java.lang.String CONTENT_DIRECTORY = "organizations"; field public static final deprecated android.net.Uri CONTENT_URI; field public static final deprecated java.lang.String DEFAULT_SORT_ORDER = "company, title, isprimary ASC"; @@ -34788,8 +34776,8 @@ package android.provider { } public static final deprecated class Contacts.Phones implements android.provider.BaseColumns android.provider.Contacts.PeopleColumns android.provider.Contacts.PhonesColumns { - method public static final deprecated java.lang.CharSequence getDisplayLabel(android.content.Context, int, java.lang.CharSequence, java.lang.CharSequence[]); - method public static final deprecated java.lang.CharSequence getDisplayLabel(android.content.Context, int, java.lang.CharSequence); + method public static deprecated java.lang.CharSequence getDisplayLabel(android.content.Context, int, java.lang.CharSequence, java.lang.CharSequence[]); + method public static deprecated java.lang.CharSequence getDisplayLabel(android.content.Context, int, java.lang.CharSequence); field public static final deprecated android.net.Uri CONTENT_FILTER_URL; field public static final deprecated java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/phone"; field public static final deprecated java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/phone"; @@ -34929,8 +34917,8 @@ package android.provider { } public static final class ContactsContract.CommonDataKinds.Email implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins { - method public static final java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); - method public static final int getTypeLabelResource(int); + method public static java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); + method public static int getTypeLabelResource(int); field public static final java.lang.String ADDRESS = "data1"; field public static final android.net.Uri CONTENT_FILTER_URI; field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/email_v2"; @@ -34950,7 +34938,7 @@ package android.provider { } public static final class ContactsContract.CommonDataKinds.Event implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins { - method public static final java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); + method public static java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); method public static int getTypeResource(java.lang.Integer); field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/contact_event"; field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX"; @@ -34981,10 +34969,10 @@ package android.provider { } public static final class ContactsContract.CommonDataKinds.Im implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins { - method public static final java.lang.CharSequence getProtocolLabel(android.content.res.Resources, int, java.lang.CharSequence); - method public static final int getProtocolLabelResource(int); - method public static final java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); - method public static final int getTypeLabelResource(int); + method public static java.lang.CharSequence getProtocolLabel(android.content.res.Resources, int, java.lang.CharSequence); + method public static int getProtocolLabelResource(int); + method public static java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); + method public static int getTypeLabelResource(int); field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im"; field public static final java.lang.String CUSTOM_PROTOCOL = "data6"; field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX"; @@ -35029,8 +35017,8 @@ package android.provider { } public static final class ContactsContract.CommonDataKinds.Organization implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins { - method public static final java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); - method public static final int getTypeLabelResource(int); + method public static java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); + method public static int getTypeLabelResource(int); field public static final java.lang.String COMPANY = "data1"; field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/organization"; field public static final java.lang.String DEPARTMENT = "data5"; @@ -35048,8 +35036,8 @@ package android.provider { } public static final class ContactsContract.CommonDataKinds.Phone implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins { - method public static final java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); - method public static final int getTypeLabelResource(int); + method public static java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); + method public static int getTypeLabelResource(int); field public static final android.net.Uri CONTENT_FILTER_URI; field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/phone_v2"; field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/phone_v2"; @@ -35094,8 +35082,8 @@ package android.provider { } public static final class ContactsContract.CommonDataKinds.Relation implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins { - method public static final java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); - method public static final int getTypeLabelResource(int); + method public static java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); + method public static int getTypeLabelResource(int); field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/relation"; field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX"; field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS"; @@ -35118,8 +35106,8 @@ package android.provider { } public static final class ContactsContract.CommonDataKinds.SipAddress implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins { - method public static final java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); - method public static final int getTypeLabelResource(int); + method public static java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); + method public static int getTypeLabelResource(int); field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address"; field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX"; field public static final java.lang.String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS"; @@ -35149,8 +35137,8 @@ package android.provider { } public static final class ContactsContract.CommonDataKinds.StructuredPostal implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins { - method public static final java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); - method public static final int getTypeLabelResource(int); + method public static java.lang.CharSequence getTypeLabel(android.content.res.Resources, int, java.lang.CharSequence); + method public static int getTypeLabelResource(int); field public static final java.lang.String CITY = "data7"; field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/postal-address_v2"; field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/postal-address_v2"; @@ -35977,7 +35965,7 @@ package android.provider { public static final class MediaStore.Audio.Artists.Albums implements android.provider.MediaStore.Audio.AlbumColumns { ctor public MediaStore.Audio.Artists.Albums(); - method public static final android.net.Uri getContentUri(java.lang.String, long); + method public static android.net.Uri getContentUri(java.lang.String, long); } public static abstract interface MediaStore.Audio.AudioColumns implements android.provider.MediaStore.MediaColumns { @@ -36013,7 +36001,7 @@ package android.provider { public static final class MediaStore.Audio.Genres.Members implements android.provider.MediaStore.Audio.AudioColumns { ctor public MediaStore.Audio.Genres.Members(); - method public static final android.net.Uri getContentUri(java.lang.String, long); + method public static android.net.Uri getContentUri(java.lang.String, long); field public static final java.lang.String AUDIO_ID = "audio_id"; field public static final java.lang.String CONTENT_DIRECTORY = "members"; field public static final java.lang.String DEFAULT_SORT_ORDER = "title_key"; @@ -36049,8 +36037,8 @@ package android.provider { public static final class MediaStore.Audio.Playlists.Members implements android.provider.MediaStore.Audio.AudioColumns { ctor public MediaStore.Audio.Playlists.Members(); - method public static final android.net.Uri getContentUri(java.lang.String, long); - method public static final boolean moveItem(android.content.ContentResolver, long, int, int); + method public static android.net.Uri getContentUri(java.lang.String, long); + method public static boolean moveItem(android.content.ContentResolver, long, int, int); field public static final java.lang.String AUDIO_ID = "audio_id"; field public static final java.lang.String CONTENT_DIRECTORY = "members"; field public static final java.lang.String DEFAULT_SORT_ORDER = "play_order"; @@ -36073,7 +36061,7 @@ package android.provider { public static final class MediaStore.Files { ctor public MediaStore.Files(); method public static android.net.Uri getContentUri(java.lang.String); - method public static final android.net.Uri getContentUri(java.lang.String, long); + method public static android.net.Uri getContentUri(java.lang.String, long); } public static abstract interface MediaStore.Files.FileColumns implements android.provider.MediaStore.MediaColumns { @@ -36107,13 +36095,13 @@ package android.provider { public static final class MediaStore.Images.Media implements android.provider.MediaStore.Images.ImageColumns { ctor public MediaStore.Images.Media(); - method public static final android.graphics.Bitmap getBitmap(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException, java.io.IOException; + method public static android.graphics.Bitmap getBitmap(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException, java.io.IOException; method public static android.net.Uri getContentUri(java.lang.String); - method public static final java.lang.String insertImage(android.content.ContentResolver, java.lang.String, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; - method public static final java.lang.String insertImage(android.content.ContentResolver, android.graphics.Bitmap, java.lang.String, java.lang.String); - method public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, java.lang.String[]); - method public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, java.lang.String[], java.lang.String, java.lang.String); - method public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); + method public static java.lang.String insertImage(android.content.ContentResolver, java.lang.String, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public static java.lang.String insertImage(android.content.ContentResolver, android.graphics.Bitmap, java.lang.String, java.lang.String); + method public static android.database.Cursor query(android.content.ContentResolver, android.net.Uri, java.lang.String[]); + method public static android.database.Cursor query(android.content.ContentResolver, android.net.Uri, java.lang.String[], java.lang.String, java.lang.String); + method public static android.database.Cursor query(android.content.ContentResolver, android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/image"; field public static final java.lang.String DEFAULT_SORT_ORDER = "bucket_display_name"; field public static final android.net.Uri EXTERNAL_CONTENT_URI; @@ -36158,7 +36146,7 @@ package android.provider { public static final class MediaStore.Video { ctor public MediaStore.Video(); - method public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, java.lang.String[]); + method public static android.database.Cursor query(android.content.ContentResolver, android.net.Uri, java.lang.String[]); field public static final java.lang.String DEFAULT_SORT_ORDER = "_display_name"; } @@ -36390,12 +36378,12 @@ package android.provider { method public static long getLong(android.content.ContentResolver, java.lang.String) throws android.provider.Settings.SettingNotFoundException; method public static java.lang.String getString(android.content.ContentResolver, java.lang.String); method public static android.net.Uri getUriFor(java.lang.String); - method public static final deprecated boolean isLocationProviderEnabled(android.content.ContentResolver, java.lang.String); + method public static deprecated boolean isLocationProviderEnabled(android.content.ContentResolver, java.lang.String); method public static boolean putFloat(android.content.ContentResolver, java.lang.String, float); method public static boolean putInt(android.content.ContentResolver, java.lang.String, int); method public static boolean putLong(android.content.ContentResolver, java.lang.String, long); method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String); - method public static final deprecated void setLocationProviderEnabled(android.content.ContentResolver, java.lang.String, boolean); + method public static deprecated void setLocationProviderEnabled(android.content.ContentResolver, java.lang.String, boolean); field public static final java.lang.String ACCESSIBILITY_DISPLAY_INVERSION_ENABLED = "accessibility_display_inversion_enabled"; field public static final java.lang.String ACCESSIBILITY_ENABLED = "accessibility_enabled"; field public static final deprecated java.lang.String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password"; @@ -40427,12 +40415,12 @@ package android.telecom { method public void playDtmfTone(char); method public void postDialContinue(boolean); method public void pullExternalCall(); - method public final void putExtras(android.os.Bundle); + method public void putExtras(android.os.Bundle); method public void registerCallback(android.telecom.Call.Callback); method public void registerCallback(android.telecom.Call.Callback, android.os.Handler); method public void reject(boolean, java.lang.String); - method public final void removeExtras(java.util.List); - method public final void removeExtras(java.lang.String...); + method public void removeExtras(java.util.List); + method public void removeExtras(java.lang.String...); method public void respondToRttRequest(int, boolean); method public void sendCallEvent(java.lang.String, android.os.Bundle); method public void sendRttRequest(); @@ -41001,23 +40989,23 @@ package android.telecom { public final class RemoteConference { method public void disconnect(); method public java.util.List getConferenceableConnections(); - method public final int getConnectionCapabilities(); - method public final int getConnectionProperties(); - method public final java.util.List getConnections(); + method public int getConnectionCapabilities(); + method public int getConnectionProperties(); + method public java.util.List getConnections(); method public android.telecom.DisconnectCause getDisconnectCause(); - method public final android.os.Bundle getExtras(); - method public final int getState(); + method public android.os.Bundle getExtras(); + method public int getState(); method public void hold(); method public void merge(); method public void playDtmfTone(char); - method public final void registerCallback(android.telecom.RemoteConference.Callback); - method public final void registerCallback(android.telecom.RemoteConference.Callback, android.os.Handler); + method public void registerCallback(android.telecom.RemoteConference.Callback); + method public void registerCallback(android.telecom.RemoteConference.Callback, android.os.Handler); method public void separate(android.telecom.RemoteConnection); method public void setCallAudioState(android.telecom.CallAudioState); method public void stopDtmfTone(); method public void swap(); method public void unhold(); - method public final void unregisterCallback(android.telecom.RemoteConference.Callback); + method public void unregisterCallback(android.telecom.RemoteConference.Callback); } public static abstract class RemoteConference.Callback { @@ -41046,10 +41034,10 @@ package android.telecom { method public int getConnectionCapabilities(); method public int getConnectionProperties(); method public android.telecom.DisconnectCause getDisconnectCause(); - method public final android.os.Bundle getExtras(); + method public android.os.Bundle getExtras(); method public int getState(); method public android.telecom.StatusHints getStatusHints(); - method public final android.telecom.RemoteConnection.VideoProvider getVideoProvider(); + method public android.telecom.RemoteConnection.VideoProvider getVideoProvider(); method public int getVideoState(); method public void hold(); method public boolean isRingbackRequested(); @@ -42502,11 +42490,11 @@ package android.telephony.gsm { } public final deprecated class SmsManager { - method public final deprecated java.util.ArrayList divideMessage(java.lang.String); - method public static final deprecated android.telephony.gsm.SmsManager getDefault(); - method public final deprecated void sendDataMessage(java.lang.String, java.lang.String, short, byte[], android.app.PendingIntent, android.app.PendingIntent); - method public final deprecated void sendMultipartTextMessage(java.lang.String, java.lang.String, java.util.ArrayList, java.util.ArrayList, java.util.ArrayList); - method public final deprecated void sendTextMessage(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent); + method public deprecated java.util.ArrayList divideMessage(java.lang.String); + method public static deprecated android.telephony.gsm.SmsManager getDefault(); + method public deprecated void sendDataMessage(java.lang.String, java.lang.String, short, byte[], android.app.PendingIntent, android.app.PendingIntent); + method public deprecated void sendMultipartTextMessage(java.lang.String, java.lang.String, java.util.ArrayList, java.util.ArrayList, java.util.ArrayList); + method public deprecated void sendTextMessage(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent); field public static final deprecated int RESULT_ERROR_GENERIC_FAILURE = 1; // 0x1 field public static final deprecated int RESULT_ERROR_NO_SERVICE = 4; // 0x4 field public static final deprecated int RESULT_ERROR_NULL_PDU = 3; // 0x3 @@ -46565,76 +46553,76 @@ package android.view { public final class MotionEvent extends android.view.InputEvent implements android.os.Parcelable { method public static java.lang.String actionToString(int); - method public final void addBatch(long, float, float, float, float, int); - method public final void addBatch(long, android.view.MotionEvent.PointerCoords[], int); + method public void addBatch(long, float, float, float, float, int); + method public void addBatch(long, android.view.MotionEvent.PointerCoords[], int); method public static int axisFromString(java.lang.String); method public static java.lang.String axisToString(int); - method public final int findPointerIndex(int); - method public final int getAction(); - method public final int getActionButton(); - method public final int getActionIndex(); - method public final int getActionMasked(); - method public final float getAxisValue(int); - method public final float getAxisValue(int, int); - method public final int getButtonState(); - method public final int getDeviceId(); - method public final long getDownTime(); - method public final int getEdgeFlags(); - method public final long getEventTime(); - method public final int getFlags(); - method public final float getHistoricalAxisValue(int, int); - method public final float getHistoricalAxisValue(int, int, int); - method public final long getHistoricalEventTime(int); - method public final float getHistoricalOrientation(int); - method public final float getHistoricalOrientation(int, int); - method public final void getHistoricalPointerCoords(int, int, android.view.MotionEvent.PointerCoords); - method public final float getHistoricalPressure(int); - method public final float getHistoricalPressure(int, int); - method public final float getHistoricalSize(int); - method public final float getHistoricalSize(int, int); - method public final float getHistoricalToolMajor(int); - method public final float getHistoricalToolMajor(int, int); - method public final float getHistoricalToolMinor(int); - method public final float getHistoricalToolMinor(int, int); - method public final float getHistoricalTouchMajor(int); - method public final float getHistoricalTouchMajor(int, int); - method public final float getHistoricalTouchMinor(int); - method public final float getHistoricalTouchMinor(int, int); - method public final float getHistoricalX(int); - method public final float getHistoricalX(int, int); - method public final float getHistoricalY(int); - method public final float getHistoricalY(int, int); - method public final int getHistorySize(); - method public final int getMetaState(); - method public final float getOrientation(); - method public final float getOrientation(int); - method public final void getPointerCoords(int, android.view.MotionEvent.PointerCoords); - method public final int getPointerCount(); - method public final int getPointerId(int); - method public final void getPointerProperties(int, android.view.MotionEvent.PointerProperties); - method public final float getPressure(); - method public final float getPressure(int); - method public final float getRawX(); - method public final float getRawY(); - method public final float getSize(); - method public final float getSize(int); - method public final int getSource(); - method public final float getToolMajor(); - method public final float getToolMajor(int); - method public final float getToolMinor(); - method public final float getToolMinor(int); - method public final int getToolType(int); - method public final float getTouchMajor(); - method public final float getTouchMajor(int); - method public final float getTouchMinor(); - method public final float getTouchMinor(int); - method public final float getX(); - method public final float getX(int); - method public final float getXPrecision(); - method public final float getY(); - method public final float getY(int); - method public final float getYPrecision(); - method public final boolean isButtonPressed(int); + method public int findPointerIndex(int); + method public int getAction(); + method public int getActionButton(); + method public int getActionIndex(); + method public int getActionMasked(); + method public float getAxisValue(int); + method public float getAxisValue(int, int); + method public int getButtonState(); + method public int getDeviceId(); + method public long getDownTime(); + method public int getEdgeFlags(); + method public long getEventTime(); + method public int getFlags(); + method public float getHistoricalAxisValue(int, int); + method public float getHistoricalAxisValue(int, int, int); + method public long getHistoricalEventTime(int); + method public float getHistoricalOrientation(int); + method public float getHistoricalOrientation(int, int); + method public void getHistoricalPointerCoords(int, int, android.view.MotionEvent.PointerCoords); + method public float getHistoricalPressure(int); + method public float getHistoricalPressure(int, int); + method public float getHistoricalSize(int); + method public float getHistoricalSize(int, int); + method public float getHistoricalToolMajor(int); + method public float getHistoricalToolMajor(int, int); + method public float getHistoricalToolMinor(int); + method public float getHistoricalToolMinor(int, int); + method public float getHistoricalTouchMajor(int); + method public float getHistoricalTouchMajor(int, int); + method public float getHistoricalTouchMinor(int); + method public float getHistoricalTouchMinor(int, int); + method public float getHistoricalX(int); + method public float getHistoricalX(int, int); + method public float getHistoricalY(int); + method public float getHistoricalY(int, int); + method public int getHistorySize(); + method public int getMetaState(); + method public float getOrientation(); + method public float getOrientation(int); + method public void getPointerCoords(int, android.view.MotionEvent.PointerCoords); + method public int getPointerCount(); + method public int getPointerId(int); + method public void getPointerProperties(int, android.view.MotionEvent.PointerProperties); + method public float getPressure(); + method public float getPressure(int); + method public float getRawX(); + method public float getRawY(); + method public float getSize(); + method public float getSize(int); + method public int getSource(); + method public float getToolMajor(); + method public float getToolMajor(int); + method public float getToolMinor(); + method public float getToolMinor(int); + method public int getToolType(int); + method public float getTouchMajor(); + method public float getTouchMajor(int); + method public float getTouchMinor(); + method public float getTouchMinor(int); + method public float getX(); + method public float getX(int); + method public float getXPrecision(); + method public float getY(); + method public float getY(int); + method public float getYPrecision(); + method public boolean isButtonPressed(int); method public static android.view.MotionEvent obtain(long, long, int, int, android.view.MotionEvent.PointerProperties[], android.view.MotionEvent.PointerCoords[], int, int, float, float, int, int, int, int); method public static deprecated android.view.MotionEvent obtain(long, long, int, int, int[], android.view.MotionEvent.PointerCoords[], int, float, float, int, int, int, int); method public static android.view.MotionEvent obtain(long, long, int, float, float, float, float, int, float, float, int, int); @@ -46642,13 +46630,13 @@ package android.view { method public static android.view.MotionEvent obtain(long, long, int, float, float, int); method public static android.view.MotionEvent obtain(android.view.MotionEvent); method public static android.view.MotionEvent obtainNoHistory(android.view.MotionEvent); - method public final void offsetLocation(float, float); - method public final void recycle(); - method public final void setAction(int); - method public final void setEdgeFlags(int); - method public final void setLocation(float, float); - method public final void setSource(int); - method public final void transform(android.graphics.Matrix); + method public void offsetLocation(float, float); + method public void recycle(); + method public void setAction(int); + method public void setEdgeFlags(int); + method public void setLocation(float, float); + method public void setSource(int); + method public void transform(android.graphics.Matrix); method public void writeToParcel(android.os.Parcel, int); field public static final int ACTION_BUTTON_PRESS = 11; // 0xb field public static final int ACTION_BUTTON_RELEASE = 12; // 0xc @@ -48366,9 +48354,9 @@ package android.view { method public void addOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener); method public void addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener); method public void addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener); - method public final void dispatchOnDraw(); - method public final void dispatchOnGlobalLayout(); - method public final boolean dispatchOnPreDraw(); + method public void dispatchOnDraw(); + method public void dispatchOnGlobalLayout(); + method public boolean dispatchOnPreDraw(); method public boolean isAlive(); method public deprecated void removeGlobalOnLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener); method public void removeOnDrawListener(android.view.ViewTreeObserver.OnDrawListener); @@ -50629,7 +50617,7 @@ package android.webkit { ctor public URLUtil(); method public static java.lang.String composeSearchUrl(java.lang.String, java.lang.String, java.lang.String); method public static byte[] decode(byte[]) throws java.lang.IllegalArgumentException; - method public static final java.lang.String guessFileName(java.lang.String, java.lang.String, java.lang.String); + method public static java.lang.String guessFileName(java.lang.String, java.lang.String, java.lang.String); method public static java.lang.String guessUrl(java.lang.String); method public static boolean isAboutUrl(java.lang.String); method public static boolean isAssetUrl(java.lang.String); @@ -55698,7 +55686,7 @@ package java.lang { } public static final class Character.UnicodeBlock extends java.lang.Character.Subset { - method public static final java.lang.Character.UnicodeBlock forName(java.lang.String); + method public static java.lang.Character.UnicodeBlock forName(java.lang.String); method public static java.lang.Character.UnicodeBlock of(char); method public static java.lang.Character.UnicodeBlock of(int); field public static final java.lang.Character.UnicodeBlock AEGEAN_NUMBERS; @@ -55925,7 +55913,7 @@ package java.lang { } public static final class Character.UnicodeScript extends java.lang.Enum { - method public static final java.lang.Character.UnicodeScript forName(java.lang.String); + method public static java.lang.Character.UnicodeScript forName(java.lang.String); method public static java.lang.Character.UnicodeScript of(int); method public static java.lang.Character.UnicodeScript valueOf(java.lang.String); method public static final java.lang.Character.UnicodeScript[] values(); @@ -58741,8 +58729,8 @@ package java.net { ctor public URL(java.net.URL, java.lang.String) throws java.net.MalformedURLException; ctor public URL(java.net.URL, java.lang.String, java.net.URLStreamHandler) throws java.net.MalformedURLException; method public java.lang.String getAuthority(); - method public final java.lang.Object getContent() throws java.io.IOException; - method public final java.lang.Object getContent(java.lang.Class[]) throws java.io.IOException; + method public java.lang.Object getContent() throws java.io.IOException; + method public java.lang.Object getContent(java.lang.Class[]) throws java.io.IOException; method public int getDefaultPort(); method public java.lang.String getFile(); method public java.lang.String getHost(); @@ -58755,7 +58743,7 @@ package java.net { method public synchronized int hashCode(); method public java.net.URLConnection openConnection() throws java.io.IOException; method public java.net.URLConnection openConnection(java.net.Proxy) throws java.io.IOException; - method public final java.io.InputStream openStream() throws java.io.IOException; + method public java.io.InputStream openStream() throws java.io.IOException; method public boolean sameFile(java.net.URL); method public static void setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory); method public java.lang.String toExternalForm(); @@ -63411,13 +63399,13 @@ package java.text { method public int getOffset(); method public int next(); method public int previous(); - method public static final int primaryOrder(int); + method public static int primaryOrder(int); method public void reset(); - method public static final short secondaryOrder(int); + method public static short secondaryOrder(int); method public void setOffset(int); method public void setText(java.lang.String); method public void setText(java.text.CharacterIterator); - method public static final short tertiaryOrder(int); + method public static short tertiaryOrder(int); field public static final int NULLORDER = -1; // 0xffffffff } @@ -64698,7 +64686,7 @@ package java.time.chrono { } public final class HijrahDate implements java.time.chrono.ChronoLocalDate java.io.Serializable java.time.temporal.Temporal java.time.temporal.TemporalAdjuster { - method public final java.time.chrono.ChronoLocalDateTime atTime(java.time.LocalTime); + method public java.time.chrono.ChronoLocalDateTime atTime(java.time.LocalTime); method public static java.time.chrono.HijrahDate from(java.time.temporal.TemporalAccessor); method public java.time.chrono.HijrahChronology getChronology(); method public java.time.chrono.HijrahEra getEra(); @@ -64785,7 +64773,7 @@ package java.time.chrono { } public final class JapaneseDate implements java.time.chrono.ChronoLocalDate java.io.Serializable java.time.temporal.Temporal java.time.temporal.TemporalAdjuster { - method public final java.time.chrono.ChronoLocalDateTime atTime(java.time.LocalTime); + method public java.time.chrono.ChronoLocalDateTime atTime(java.time.LocalTime); method public static java.time.chrono.JapaneseDate from(java.time.temporal.TemporalAccessor); method public java.time.chrono.JapaneseChronology getChronology(); method public java.time.chrono.JapaneseEra getEra(); @@ -64841,7 +64829,7 @@ package java.time.chrono { } public final class MinguoDate implements java.time.chrono.ChronoLocalDate java.io.Serializable java.time.temporal.Temporal java.time.temporal.TemporalAdjuster { - method public final java.time.chrono.ChronoLocalDateTime atTime(java.time.LocalTime); + method public java.time.chrono.ChronoLocalDateTime atTime(java.time.LocalTime); method public static java.time.chrono.MinguoDate from(java.time.temporal.TemporalAccessor); method public java.time.chrono.MinguoChronology getChronology(); method public java.time.chrono.MinguoEra getEra(); @@ -64894,7 +64882,7 @@ package java.time.chrono { } public final class ThaiBuddhistDate implements java.time.chrono.ChronoLocalDate java.io.Serializable java.time.temporal.Temporal java.time.temporal.TemporalAdjuster { - method public final java.time.chrono.ChronoLocalDateTime atTime(java.time.LocalTime); + method public java.time.chrono.ChronoLocalDateTime atTime(java.time.LocalTime); method public static java.time.chrono.ThaiBuddhistDate from(java.time.temporal.TemporalAccessor); method public java.time.chrono.ThaiBuddhistChronology getChronology(); method public java.time.chrono.ThaiBuddhistEra getEra(); @@ -64946,8 +64934,8 @@ package java.time.format { method public T parse(java.lang.CharSequence, java.time.temporal.TemporalQuery); method public java.time.temporal.TemporalAccessor parseBest(java.lang.CharSequence, java.time.temporal.TemporalQuery...); method public java.time.temporal.TemporalAccessor parseUnresolved(java.lang.CharSequence, java.text.ParsePosition); - method public static final java.time.temporal.TemporalQuery parsedExcessDays(); - method public static final java.time.temporal.TemporalQuery parsedLeapSecond(); + method public static java.time.temporal.TemporalQuery parsedExcessDays(); + method public static java.time.temporal.TemporalQuery parsedLeapSecond(); method public java.text.Format toFormat(); method public java.text.Format toFormat(java.time.temporal.TemporalQuery); method public java.time.format.DateTimeFormatter withChronology(java.time.chrono.Chronology); @@ -66396,15 +66384,15 @@ package java.util { method public java.lang.String getCountry(); method public static java.util.Locale getDefault(); method public static java.util.Locale getDefault(java.util.Locale.Category); - method public final java.lang.String getDisplayCountry(); + method public java.lang.String getDisplayCountry(); method public java.lang.String getDisplayCountry(java.util.Locale); - method public final java.lang.String getDisplayLanguage(); + method public java.lang.String getDisplayLanguage(); method public java.lang.String getDisplayLanguage(java.util.Locale); - method public final java.lang.String getDisplayName(); + method public java.lang.String getDisplayName(); method public java.lang.String getDisplayName(java.util.Locale); method public java.lang.String getDisplayScript(); method public java.lang.String getDisplayScript(java.util.Locale); - method public final java.lang.String getDisplayVariant(); + method public java.lang.String getDisplayVariant(); method public java.lang.String getDisplayVariant(java.util.Locale); method public java.lang.String getExtension(char); method public java.util.Set getExtensionKeys(); @@ -66425,7 +66413,6 @@ package java.util { method public static synchronized void setDefault(java.util.Locale.Category, java.util.Locale); method public java.util.Locale stripExtensions(); method public java.lang.String toLanguageTag(); - method public final java.lang.String toString(); field public static final java.util.Locale CANADA; field public static final java.util.Locale CANADA_FRENCH; field public static final java.util.Locale CHINA; diff --git a/api/system-current.txt b/api/system-current.txt index 034ee3090fc..b9f17d2a004 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -2673,10 +2673,10 @@ package android.media.soundtrigger { package android.media.tv { public final class TvContentRatingSystemInfo implements android.os.Parcelable { - method public static final android.media.tv.TvContentRatingSystemInfo createTvContentRatingSystemInfo(int, android.content.pm.ApplicationInfo); + method public static android.media.tv.TvContentRatingSystemInfo createTvContentRatingSystemInfo(int, android.content.pm.ApplicationInfo); method public int describeContents(); - method public final android.net.Uri getXmlUri(); - method public final boolean isSystemDefined(); + method public android.net.Uri getXmlUri(); + method public boolean isSystemDefined(); method public void writeToParcel(android.os.Parcel, int); } @@ -4657,15 +4657,15 @@ package android.telecom { } public final deprecated class Phone { - method public final void addListener(android.telecom.Phone.Listener); - method public final boolean canAddCall(); - method public final deprecated android.telecom.AudioState getAudioState(); - method public final android.telecom.CallAudioState getCallAudioState(); - method public final java.util.List getCalls(); - method public final void removeListener(android.telecom.Phone.Listener); + method public void addListener(android.telecom.Phone.Listener); + method public boolean canAddCall(); + method public deprecated android.telecom.AudioState getAudioState(); + method public android.telecom.CallAudioState getCallAudioState(); + method public java.util.List getCalls(); + method public void removeListener(android.telecom.Phone.Listener); method public void requestBluetoothAudio(java.lang.String); - method public final void setAudioRoute(int); - method public final void setMuted(boolean); + method public void setAudioRoute(int); + method public void setMuted(boolean); } public static abstract class Phone.Listener { diff --git a/api/test-current.txt b/api/test-current.txt index d834cf7d62a..bf00343be3a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -995,8 +995,8 @@ package android.view { } public final class MotionEvent extends android.view.InputEvent implements android.os.Parcelable { - method public final void setActionButton(int); - method public final void setButtonState(int); + method public void setActionButton(int); + method public void setButtonState(int); } public class View implements android.view.accessibility.AccessibilityEventSource android.graphics.drawable.Drawable.Callback android.view.KeyEvent.Callback { -- GitLab From 7073c075fb97f9813a56e022bc67712d5db23396 Mon Sep 17 00:00:00 2001 From: Mohamed Abdalkader Date: Mon, 22 Jan 2018 15:43:37 -0800 Subject: [PATCH 190/416] Remove internal version of SMS apis Test: manual BUG=69846044 Merged-In: Ie084ea67c460c686bd587e4b36f8c1579517ea7f Change-Id: Ie084ea67c460c686bd587e4b36f8c1579517ea7f --- Android.bp | 7 +- .../telephony/ims/internal/SmsImplBase.java | 273 ------------------ .../ims/internal/aidl/IImsMmTelFeature.aidl | 8 - .../ims/internal/aidl/IImsSmsListener.aidl | 28 -- .../ims/internal/feature/MmTelFeature.java | 69 ----- 5 files changed, 3 insertions(+), 382 deletions(-) delete mode 100644 telephony/java/android/telephony/ims/internal/SmsImplBase.java delete mode 100644 telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl diff --git a/Android.bp b/Android.bp index d14a43306eb..0cd985d0459 100644 --- a/Android.bp +++ b/Android.bp @@ -460,8 +460,8 @@ java_library { "telecomm/java/com/android/internal/telecom/IInCallService.aidl", "telecomm/java/com/android/internal/telecom/ITelecomService.aidl", "telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl", - "telephony/java/android/telephony/data/IDataService.aidl", - "telephony/java/android/telephony/data/IDataServiceCallback.aidl", + "telephony/java/android/telephony/data/IDataService.aidl", + "telephony/java/android/telephony/data/IDataServiceCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl", @@ -471,8 +471,7 @@ java_library { "telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl", "telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl", - "telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl", - "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", + "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl", "telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl", "telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl", "telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl", diff --git a/telephony/java/android/telephony/ims/internal/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/SmsImplBase.java deleted file mode 100644 index 33b23d94ad3..00000000000 --- a/telephony/java/android/telephony/ims/internal/SmsImplBase.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * 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.telephony.ims.internal; - -import android.annotation.IntDef; -import android.os.RemoteException; -import android.telephony.SmsManager; -import android.telephony.SmsMessage; -import android.telephony.ims.internal.aidl.IImsSmsListener; -import android.telephony.ims.internal.feature.MmTelFeature; -import android.util.Log; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * Base implementation for SMS over IMS. - * - * Any service wishing to provide SMS over IMS should extend this class and implement all methods - * that the service supports. - * @hide - */ -public class SmsImplBase { - private static final String LOG_TAG = "SmsImplBase"; - - /** @hide */ - @IntDef({ - SEND_STATUS_OK, - SEND_STATUS_ERROR, - SEND_STATUS_ERROR_RETRY, - SEND_STATUS_ERROR_FALLBACK - }) - @Retention(RetentionPolicy.SOURCE) - public @interface SendStatusResult {} - /** - * Message was sent successfully. - */ - public static final int SEND_STATUS_OK = 1; - - /** - * IMS provider failed to send the message and platform should not retry falling back to sending - * the message using the radio. - */ - public static final int SEND_STATUS_ERROR = 2; - - /** - * IMS provider failed to send the message and platform should retry again after setting TP-RD bit - * to high. - */ - public static final int SEND_STATUS_ERROR_RETRY = 3; - - /** - * IMS provider failed to send the message and platform should retry falling back to sending - * the message using the radio. - */ - public static final int SEND_STATUS_ERROR_FALLBACK = 4; - - /** @hide */ - @IntDef({ - DELIVER_STATUS_OK, - DELIVER_STATUS_ERROR - }) - @Retention(RetentionPolicy.SOURCE) - public @interface DeliverStatusResult {} - /** - * Message was delivered successfully. - */ - public static final int DELIVER_STATUS_OK = 1; - - /** - * Message was not delivered. - */ - public static final int DELIVER_STATUS_ERROR = 2; - - /** @hide */ - @IntDef({ - STATUS_REPORT_STATUS_OK, - STATUS_REPORT_STATUS_ERROR - }) - @Retention(RetentionPolicy.SOURCE) - public @interface StatusReportResult {} - - /** - * Status Report was set successfully. - */ - public static final int STATUS_REPORT_STATUS_OK = 1; - - /** - * Error while setting status report. - */ - public static final int STATUS_REPORT_STATUS_ERROR = 2; - - - // Lock for feature synchronization - private final Object mLock = new Object(); - private IImsSmsListener mListener; - - /** - * Registers a listener responsible for handling tasks like delivering messages. - * - * @param listener listener to register. - * - * @hide - */ - public final void registerSmsListener(IImsSmsListener listener) { - synchronized (mLock) { - mListener = listener; - } - } - - /** - * This method will be triggered by the platform when the user attempts to send an SMS. This - * method should be implemented by the IMS providers to provide implementation of sending an SMS - * over IMS. - * - * @param token unique token generated by the platform that should be used when triggering - * callbacks for this specific message. - * @param messageRef the message reference. - * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and - * {@link SmsMessage#FORMAT_3GPP2}. - * @param smsc the Short Message Service Center address. - * @param isRetry whether it is a retry of an already attempted message or not. - * @param pdu PDUs representing the contents of the message. - */ - public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, - byte[] pdu) { - // Base implementation returns error. Should be overridden. - try { - onSendSmsResult(token, messageRef, SEND_STATUS_ERROR, - SmsManager.RESULT_ERROR_GENERIC_FAILURE); - } catch (RemoteException e) { - Log.e(LOG_TAG, "Can not send sms: " + e.getMessage()); - } - } - - /** - * This method will be triggered by the platform after {@link #onSmsReceived(int, String, byte[])} - * has been called to deliver the result to the IMS provider. - * - * @param token token provided in {@link #onSmsReceived(int, String, byte[])} - * @param result result of delivering the message. Valid values are defined in - * {@link DeliverStatusResult} - * @param messageRef the message reference - */ - public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) { - Log.e(LOG_TAG, "acknowledgeSms() not implemented."); - } - - /** - * This method will be triggered by the platform after - * {@link #onSmsStatusReportReceived(int, int, String, byte[])} has been called to provide the - * result to the IMS provider. - * - * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} - * @param result result of delivering the message. Valid values are defined in - * {@link StatusReportResult} - * @param messageRef the message reference - */ - public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) { - Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented."); - } - - /** - * This method should be triggered by the IMS providers when there is an incoming message. The - * platform will deliver the message to the messages database and notify the IMS provider of the - * result by calling {@link #acknowledgeSms(int, int, int)}. - * - * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called. - * - * @param token unique token generated by IMS providers that the platform will use to trigger - * callbacks for this message. - * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and - * {@link SmsMessage#FORMAT_3GPP2}. - * @param pdu PDUs representing the contents of the message. - * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} - */ - public final void onSmsReceived(int token, String format, byte[] pdu) - throws IllegalStateException { - synchronized (mLock) { - if (mListener == null) { - throw new IllegalStateException("Feature not ready."); - } - try { - mListener.onSmsReceived(token, format, pdu); - } catch (RemoteException e) { - Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage()); - acknowledgeSms(token, 0, DELIVER_STATUS_ERROR); - } - } - } - - /** - * This method should be triggered by the IMS providers to pass the result of the sent message - * to the platform. - * - * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called. - * - * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} - * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040 - * @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult} - * @param reason reason in case status is failure. Valid values are: - * {@link SmsManager#RESULT_ERROR_NONE}, - * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE}, - * {@link SmsManager#RESULT_ERROR_RADIO_OFF}, - * {@link SmsManager#RESULT_ERROR_NULL_PDU}, - * {@link SmsManager#RESULT_ERROR_NO_SERVICE}, - * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED}, - * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED}, - * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED} - * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} - * @throws RemoteException if the connection to the framework is not available. If this happens - * attempting to send the SMS should be aborted. - */ - public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status, - int reason) throws IllegalStateException, RemoteException { - synchronized (mLock) { - if (mListener == null) { - throw new IllegalStateException("Feature not ready."); - } - mListener.onSendSmsResult(token, messageRef, status, reason); - } - } - - /** - * Sets the status report of the sent message. - * - * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} - * @param messageRef the message reference. - * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and - * {@link SmsMessage#FORMAT_3GPP2}. - * @param pdu PDUs representing the content of the status report. - * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} - */ - public final void onSmsStatusReportReceived(int token, int messageRef, String format, - byte[] pdu) { - synchronized (mLock) { - if (mListener == null) { - throw new IllegalStateException("Feature not ready."); - } - try { - mListener.onSmsStatusReportReceived(token, messageRef, format, pdu); - } catch (RemoteException e) { - Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage()); - acknowledgeSmsReport(token, messageRef, STATUS_REPORT_STATUS_ERROR); - } - } - } - - /** - * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS - * Provider. - * - * @return the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and - * {@link SmsMessage#FORMAT_3GPP2}. - */ - public String getSmsFormat() { - return SmsMessage.FORMAT_3GPP; - } - -} diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl index 785113f0ff6..e226adaac07 100644 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl +++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl @@ -18,7 +18,6 @@ package android.telephony.ims.internal.aidl; import android.os.Message; import android.telephony.ims.internal.aidl.IImsMmTelListener; -import android.telephony.ims.internal.aidl.IImsSmsListener; import android.telephony.ims.internal.aidl.IImsCapabilityCallback; import android.telephony.ims.internal.aidl.IImsCallSessionListener; import android.telephony.ims.internal.feature.CapabilityChangeRequest; @@ -50,11 +49,4 @@ interface IImsMmTelFeature { IImsCapabilityCallback c); oneway void queryCapabilityConfiguration(int capability, int radioTech, IImsCapabilityCallback c); - // SMS APIs - void setSmsListener(IImsSmsListener l); - oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry, - in byte[] pdu); - oneway void acknowledgeSms(int token, int messageRef, int result); - oneway void acknowledgeSmsReport(int token, int messageRef, int result); - String getSmsFormat(); } diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl deleted file mode 100644 index bf8d90b3241..00000000000 --- a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.telephony.ims.internal.aidl; - -/** - * See MMTelFeature for more information. - * {@hide} - */ -interface IImsSmsListener { - void onSendSmsResult(in int token, in int messageRef, in int status, in int reason); - void onSmsStatusReportReceived(in int token, in int messageRef, in String format, - in byte[] pdu); - void onSmsReceived(in int token, in String format, in byte[] pdu); -} \ No newline at end of file diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java index 8d888c2bcb2..9b576c72fa9 100644 --- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java @@ -21,14 +21,10 @@ import android.os.Message; import android.os.RemoteException; import android.telecom.TelecomManager; import android.telephony.ims.internal.ImsCallSessionListener; -import android.telephony.ims.internal.SmsImplBase; -import android.telephony.ims.internal.SmsImplBase.DeliverStatusResult; -import android.telephony.ims.internal.SmsImplBase.StatusReportResult; import android.telephony.ims.internal.aidl.IImsCallSessionListener; import android.telephony.ims.internal.aidl.IImsCapabilityCallback; import android.telephony.ims.internal.aidl.IImsMmTelFeature; import android.telephony.ims.internal.aidl.IImsMmTelListener; -import android.telephony.ims.internal.aidl.IImsSmsListener; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.telephony.ims.stub.ImsEcbmImplBase; import android.telephony.ims.stub.ImsMultiEndpointImplBase; @@ -67,11 +63,6 @@ public class MmTelFeature extends ImsFeature { } } - @Override - public void setSmsListener(IImsSmsListener l) throws RemoteException { - MmTelFeature.this.setSmsListener(l); - } - @Override public int getFeatureState() throws RemoteException { synchronized (mLock) { @@ -152,35 +143,6 @@ public class MmTelFeature extends ImsFeature { IImsCapabilityCallback c) { queryCapabilityConfigurationInternal(capability, radioTech, c); } - - @Override - public void sendSms(int token, int messageRef, String format, String smsc, boolean retry, - byte[] pdu) { - synchronized (mLock) { - MmTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu); - } - } - - @Override - public void acknowledgeSms(int token, int messageRef, int result) { - synchronized (mLock) { - MmTelFeature.this.acknowledgeSms(token, messageRef, result); - } - } - - @Override - public void acknowledgeSmsReport(int token, int messageRef, int result) { - synchronized (mLock) { - MmTelFeature.this.acknowledgeSmsReport(token, messageRef, result); - } - } - - @Override - public String getSmsFormat() { - synchronized (mLock) { - return MmTelFeature.this.getSmsFormat(); - } - } }; /** @@ -292,10 +254,6 @@ public class MmTelFeature extends ImsFeature { } } - private void setSmsListener(IImsSmsListener listener) { - getSmsImplementation().registerSmsListener(listener); - } - private void queryCapabilityConfigurationInternal(int capability, int radioTech, IImsCapabilityCallback c) { boolean enabled = queryCapabilityConfiguration(capability, radioTech); @@ -457,33 +415,6 @@ public class MmTelFeature extends ImsFeature { // Base Implementation - Should be overridden } - private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, - byte[] pdu) { - getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu); - } - - private void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) { - getSmsImplementation().acknowledgeSms(token, messageRef, result); - } - - private void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) { - getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); - } - - private String getSmsFormat() { - return getSmsImplementation().getSmsFormat(); - } - - /** - * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default - * non-functional implementation is returned. - * - * @return an instance of {@link SmsImplBase} which should be implemented by the IMS Provider. - */ - protected SmsImplBase getSmsImplementation() { - return new SmsImplBase(); - } - /**{@inheritDoc}*/ @Override public void onFeatureRemoved() { -- GitLab From 161ea3e6dc42dd84e4d2fe03cb00f6ed48781507 Mon Sep 17 00:00:00 2001 From: chaviw Date: Wed, 31 Jan 2018 12:01:12 -0800 Subject: [PATCH 191/416] Use WS pending transaction for MoveAnimation MoveAnimation updates the WS.surfaceControl position at the start of the animation. Use the WS pending transaction instead of display so there aren't two transactions updating the same property on the same SC. Fixes: 72646189 Test: Open IME from secondary app in split screen. Hit home and then open primary into full screen. Position for primary app should be correct. Test: go/wm-smoke-auto Change-Id: Ie034756b615550d0f351281764e93fa2863a559b --- .../core/java/com/android/server/wm/DisplayContent.java | 2 +- services/core/java/com/android/server/wm/WindowState.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 7674b5e9ed2..bd7535a046c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -662,7 +662,7 @@ class DisplayContent extends WindowContainer implements WindowManagerP * listeners and optionally animate it. Simply checking a change of position is not enough, * because being move due to dock divider is not a trigger for animation. */ - void handleWindowMovedIfNeeded(Transaction t) { + void handleWindowMovedIfNeeded() { if (!hasMoved()) { return; } @@ -1776,7 +1776,7 @@ class WindowState extends WindowContainer implements WindowManagerP && !isDragResizing() && !adjustedForMinimizedDockOrIme && getWindowConfiguration().hasMovementAnimations() && !mWinAnimator.mLastHidden) { - startMoveAnimation(t, left, top); + startMoveAnimation(left, top); } //TODO (multidisplay): Accessibility supported only for the default display. @@ -4360,7 +4360,7 @@ class WindowState extends WindowContainer implements WindowManagerP commitPendingTransaction(); } - private void startMoveAnimation(Transaction t, int left, int top) { + private void startMoveAnimation(int left, int top) { if (DEBUG_ANIM) Slog.v(TAG, "Setting move animation on " + this); final Point oldPosition = new Point(); final Point newPosition = new Point(); @@ -4369,7 +4369,7 @@ class WindowState extends WindowContainer implements WindowManagerP final AnimationAdapter adapter = new LocalAnimationAdapter( new MoveAnimationSpec(oldPosition.x, oldPosition.y, newPosition.x, newPosition.y), mService.mSurfaceAnimationRunner); - startAnimation(t, adapter); + startAnimation(getPendingTransaction(), adapter); } private void startAnimation(Transaction t, AnimationAdapter adapter) { -- GitLab From 472d8e34c9b5d68f444b72139d4afef500d73491 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Wed, 31 Jan 2018 12:37:15 -0800 Subject: [PATCH 192/416] Add tron counter for battery % too Bug: 72229630 Test: manual test with dumpsys power, etc Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java Change-Id: Iec9972a65f8a71bbcc85a1c177b268e91f90c434 --- .../com/android/server/EventLogTags.logtags | 2 +- .../batterysaver/BatterySavingStats.java | 72 +++++++++++++++---- .../batterysaver/BatterySavingStatsTest.java | 61 +++++++++------- 3 files changed, 94 insertions(+), 41 deletions(-) diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 219facd0b00..0502117cd73 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -34,7 +34,7 @@ option java_package com.android.server 2731 power_soft_sleep_requested (savedwaketimems|2) # Power save state has changed. See BatterySaverController.java for the details. 2739 battery_saver_mode (prevOffOrOn|1|5),(nowOffOrOn|1|5),(interactive|1|5),(features|3|5) -27390 battery_saving_stats (batterySaver|1|5),(interactive|1|5),(doze|1|5),(delta_duration|2|3),(delta_battery_drain|1|6),(total_duration|2|3),(total_battery_drain|1|6) +27390 battery_saving_stats (batterySaver|1|5),(interactive|1|5),(doze|1|5),(delta_duration|2|3),(delta_battery_drain|1|1),(delta_battery_drain_percent|1|6),(total_duration|2|3),(total_battery_drain|1|1),(total_battery_drain_percent|1|6) # # Leave IDs through 2740 for more power logs (2730 used by battery_discharge above) diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java index 946635043c1..b0b07ea767f 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySavingStats.java @@ -32,6 +32,8 @@ import java.io.PrintWriter; /** * This class keeps track of battery drain rate. * + * TODO: The use of the terms "percent" and "level" in this class is not standard. Fix it. + * * Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java */ @@ -96,8 +98,12 @@ public class BatterySavingStats { public int startBatteryLevel; public int endBatteryLevel; + public int startBatteryPercent; + public int endBatteryPercent; + public long totalTimeMillis; public int totalBatteryDrain; + public int totalBatteryDrainPercent; public long totalMinutes() { return totalTimeMillis / 60_000; @@ -110,13 +116,25 @@ public class BatterySavingStats { return (double) totalBatteryDrain / (totalTimeMillis / (60.0 * 60 * 1000)); } + public double drainPercentPerHour() { + if (totalTimeMillis == 0) { + return 0; + } + return (double) totalBatteryDrainPercent / (totalTimeMillis / (60.0 * 60 * 1000)); + } + @VisibleForTesting String toStringForTest() { return "{" + totalMinutes() + "m," + totalBatteryDrain + "," - + String.format("%.2f", drainPerHour()) + "}"; + + String.format("%.2f", drainPerHour()) + "uA/H," + + String.format("%.2f", drainPercentPerHour()) + "%" + + "}"; } } + @VisibleForTesting + static final String COUNTER_POWER_PERCENT_PREFIX = "battery_saver_stats_percent_"; + @VisibleForTesting static final String COUNTER_POWER_MILLIAMPS_PREFIX = "battery_saver_stats_milliamps_"; @@ -166,6 +184,9 @@ public class BatterySavingStats { private BatteryManagerInternal getBatteryManagerInternal() { if (mBatteryManagerInternal == null) { mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class); + if (mBatteryManagerInternal == null) { + Slog.wtf(TAG, "BatteryManagerInternal not initialized"); + } } return mBatteryManagerInternal; } @@ -229,12 +250,20 @@ public class BatterySavingStats { int injectBatteryLevel() { final BatteryManagerInternal bmi = getBatteryManagerInternal(); if (bmi == null) { - Slog.wtf(TAG, "BatteryManagerInternal not initialized"); return 0; } return bmi.getBatteryChargeCounter(); } + @VisibleForTesting + int injectBatteryPercent() { + final BatteryManagerInternal bmi = getBatteryManagerInternal(); + if (bmi == null) { + return 0; + } + return bmi.getBatteryLevel(); + } + /** * Called from the outside whenever any of the states changes, when the device is not plugged * in. @@ -262,33 +291,39 @@ public class BatterySavingStats { } final long now = injectCurrentTime(); final int batteryLevel = injectBatteryLevel(); + final int batteryPercent = injectBatteryPercent(); - endLastStateLocked(now, batteryLevel); - startNewStateLocked(newState, now, batteryLevel); - mMetricsLoggerHelper.transitionState(newState, now, batteryLevel); + endLastStateLocked(now, batteryLevel, batteryPercent); + startNewStateLocked(newState, now, batteryLevel, batteryPercent); + mMetricsLoggerHelper.transitionState(newState, now, batteryLevel, batteryPercent); } - private void endLastStateLocked(long now, int batteryLevel) { + private void endLastStateLocked(long now, int batteryLevel, int batteryPercent) { if (mCurrentState < 0) { return; } final Stat stat = getStat(mCurrentState); stat.endBatteryLevel = batteryLevel; + stat.endBatteryPercent = batteryPercent; stat.endTime = now; final long deltaTime = stat.endTime - stat.startTime; final int deltaDrain = stat.startBatteryLevel - stat.endBatteryLevel; + final int deltaPercent = stat.startBatteryPercent - stat.endBatteryPercent; stat.totalTimeMillis += deltaTime; stat.totalBatteryDrain += deltaDrain; + stat.totalBatteryDrainPercent += deltaPercent; if (DEBUG) { Slog.d(TAG, "State summary: " + stateToString(mCurrentState) + ": " + (deltaTime / 1_000) + "s " + "Start level: " + stat.startBatteryLevel + "uA " + "End level: " + stat.endBatteryLevel + "uA " - + deltaDrain + "uA"); + + "Start percent: " + stat.startBatteryPercent + "% " + + "End percent: " + stat.endBatteryPercent + "% " + + "Drain " + deltaDrain + "uA"); } EventLogTags.writeBatterySavingStats( BatterySaverState.fromIndex(mCurrentState), @@ -296,12 +331,14 @@ public class BatterySavingStats { DozeState.fromIndex(mCurrentState), deltaTime, deltaDrain, + deltaPercent, stat.totalTimeMillis, - stat.totalBatteryDrain); + stat.totalBatteryDrain, + stat.totalBatteryDrainPercent); } - private void startNewStateLocked(int newState, long now, int batteryLevel) { + private void startNewStateLocked(int newState, long now, int batteryLevel, int batteryPercent) { if (DEBUG) { Slog.d(TAG, "New state: " + stateToString(newState)); } @@ -313,6 +350,7 @@ public class BatterySavingStats { final Stat stat = getStat(mCurrentState); stat.startBatteryLevel = batteryLevel; + stat.startBatteryPercent = batteryPercent; stat.startTime = now; stat.endTime = 0; } @@ -325,7 +363,7 @@ public class BatterySavingStats { indent = indent + " "; pw.print(indent); - pw.println("Battery Saver: Off On"); + pw.println("Battery Saver: Off On"); dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr", DozeState.NOT_DOZING, "NonDoze"); dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, " Intr", @@ -357,12 +395,14 @@ public class BatterySavingStats { final Stat offStat = getStat(BatterySaverState.OFF, interactiveState, dozeState); final Stat onStat = getStat(BatterySaverState.ON, interactiveState, dozeState); - pw.println(String.format("%6dm %6dmA %8.1fmA/h %6dm %6dmA %8.1fmA/h", + pw.println(String.format("%6dm %6dmA (%3d%%) %8.1fmA/h %6dm %6dmA (%3d%%) %8.1fmA/h", offStat.totalMinutes(), offStat.totalBatteryDrain / 1000, + offStat.totalBatteryDrainPercent, offStat.drainPerHour() / 1000.0, onStat.totalMinutes(), onStat.totalBatteryDrain / 1000, + onStat.totalBatteryDrainPercent, onStat.drainPerHour() / 1000.0)); } @@ -371,12 +411,13 @@ public class BatterySavingStats { private int mLastState = STATE_NOT_INITIALIZED; private long mStartTime; private int mStartBatteryLevel; + private int mStartPercent; private static final int STATE_CHANGE_DETECT_MASK = (BatterySaverState.MASK << BatterySaverState.SHIFT) | (InteractiveState.MASK << InteractiveState.SHIFT); - public void transitionState(int newState, long now, int batteryLevel) { + public void transitionState(int newState, long now, int batteryLevel, int batteryPercent) { final boolean stateChanging = ((mLastState >= 0) ^ (newState >= 0)) || (((mLastState ^ newState) & STATE_CHANGE_DETECT_MASK) != 0); @@ -384,11 +425,13 @@ public class BatterySavingStats { if (mLastState >= 0) { final long deltaTime = now - mStartTime; final int deltaBattery = mStartBatteryLevel - batteryLevel; + final int deltaPercent = mStartPercent - batteryPercent; - report(mLastState, deltaTime, deltaBattery); + report(mLastState, deltaTime, deltaBattery, deltaPercent); } mStartTime = now; mStartBatteryLevel = batteryLevel; + mStartPercent = batteryPercent; } mLastState = newState; } @@ -405,9 +448,10 @@ public class BatterySavingStats { } } - void report(int state, long deltaTimeMs, int deltaBatteryUa) { + void report(int state, long deltaTimeMs, int deltaBatteryUa, int deltaPercent) { final String suffix = getCounterSuffix(state); mMetricsLogger.count(COUNTER_POWER_MILLIAMPS_PREFIX + suffix, deltaBatteryUa / 1000); + mMetricsLogger.count(COUNTER_POWER_PERCENT_PREFIX + suffix, deltaPercent); mMetricsLogger.count(COUNTER_TIME_SECONDS_PREFIX + suffix, (int) (deltaTimeMs / 1000)); } } diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java index c3714c85e89..f7516b2c369 100644 --- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java +++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java @@ -63,6 +63,11 @@ public class BatterySavingStatsTest { return mBatteryLevel; } + @Override + int injectBatteryPercent() { + return mBatteryLevel / 10; + } + void assertDumpable() { final ByteArrayOutputStream out = new ByteArrayOutputStream(); dump(new PrintWriter(out), ""); // Just make sure it won't crash. @@ -102,7 +107,7 @@ public class BatterySavingStatsTest { target.assertDumpable(); target.advanceClock(1); - target.drainBattery(2); + target.drainBattery(200); target.transitionState( BatterySaverState.OFF, @@ -110,7 +115,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(4); - target.drainBattery(1); + target.drainBattery(100); target.transitionState( BatterySaverState.OFF, @@ -118,7 +123,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(2); - target.drainBattery(5); + target.drainBattery(500); target.transitionState( BatterySaverState.OFF, @@ -126,7 +131,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(4); - target.drainBattery(1); + target.drainBattery(100); target.transitionState( BatterySaverState.OFF, @@ -134,7 +139,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(2); - target.drainBattery(5); + target.drainBattery(500); target.transitionState( BatterySaverState.OFF, @@ -142,7 +147,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(3); - target.drainBattery(1); + target.drainBattery(100); target.transitionState( BatterySaverState.OFF, @@ -150,7 +155,7 @@ public class BatterySavingStatsTest { DozeState.LIGHT); target.advanceClock(5); - target.drainBattery(1); + target.drainBattery(100); target.transitionState( BatterySaverState.OFF, @@ -158,7 +163,7 @@ public class BatterySavingStatsTest { DozeState.DEEP); target.advanceClock(1); - target.drainBattery(2); + target.drainBattery(200); target.transitionState( BatterySaverState.ON, @@ -166,7 +171,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(1); - target.drainBattery(3); + target.drainBattery(300); target.transitionState( BatterySaverState.OFF, @@ -174,7 +179,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(3); - target.drainBattery(5); + target.drainBattery(500); target.transitionState( BatterySaverState.ON, @@ -182,12 +187,12 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(3); - target.drainBattery(5); + target.drainBattery(500); target.startCharging(); target.advanceClock(5); - target.drainBattery(10); + target.drainBattery(1000); target.transitionState( BatterySaverState.ON, @@ -195,25 +200,25 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); target.advanceClock(5); - target.drainBattery(1); + target.drainBattery(100); target.startCharging(); target.assertDumpable(); assertEquals( - "BS=0,I=0,D=0:{4m,10,150.00}\n" + - "BS=1,I=0,D=0:{0m,0,0.00}\n" + - "BS=0,I=1,D=0:{14m,8,34.29}\n" + - "BS=1,I=1,D=0:{9m,9,60.00}\n" + - "BS=0,I=0,D=1:{5m,1,12.00}\n" + - "BS=1,I=0,D=1:{0m,0,0.00}\n" + - "BS=0,I=1,D=1:{0m,0,0.00}\n" + - "BS=1,I=1,D=1:{0m,0,0.00}\n" + - "BS=0,I=0,D=2:{1m,2,120.00}\n" + - "BS=1,I=0,D=2:{0m,0,0.00}\n" + - "BS=0,I=1,D=2:{0m,0,0.00}\n" + - "BS=1,I=1,D=2:{0m,0,0.00}", + "BS=0,I=0,D=0:{4m,1000,15000.00uA/H,1500.00%}\n" + + "BS=1,I=0,D=0:{0m,0,0.00uA/H,0.00%}\n" + + "BS=0,I=1,D=0:{14m,800,3428.57uA/H,342.86%}\n" + + "BS=1,I=1,D=0:{9m,900,6000.00uA/H,600.00%}\n" + + "BS=0,I=0,D=1:{5m,100,1200.00uA/H,120.00%}\n" + + "BS=1,I=0,D=1:{0m,0,0.00uA/H,0.00%}\n" + + "BS=0,I=1,D=1:{0m,0,0.00uA/H,0.00%}\n" + + "BS=1,I=1,D=1:{0m,0,0.00uA/H,0.00%}\n" + + "BS=0,I=0,D=2:{1m,200,12000.00uA/H,1200.00%}\n" + + "BS=1,I=0,D=2:{0m,0,0.00uA/H,0.00%}\n" + + "BS=0,I=1,D=2:{0m,0,0.00uA/H,0.00%}\n" + + "BS=1,I=1,D=2:{0m,0,0.00uA/H,0.00%}", target.toDebugString()); } @@ -245,6 +250,7 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); assertMetricsLog(BatterySavingStats.COUNTER_POWER_MILLIAMPS_PREFIX + "01", 2); + assertMetricsLog(BatterySavingStats.COUNTER_POWER_PERCENT_PREFIX + "01", 200); assertMetricsLog(BatterySavingStats.COUNTER_TIME_SECONDS_PREFIX + "01", 60); target.advanceClock(1); @@ -277,15 +283,17 @@ public class BatterySavingStatsTest { DozeState.NOT_DOZING); assertMetricsLog(BatterySavingStats.COUNTER_POWER_MILLIAMPS_PREFIX + "00", 2 * 3); + assertMetricsLog(BatterySavingStats.COUNTER_POWER_PERCENT_PREFIX + "00", 200 * 3); assertMetricsLog(BatterySavingStats.COUNTER_TIME_SECONDS_PREFIX + "00", 60 * 3); target.advanceClock(10); - target.drainBattery(10_000); + target.drainBattery(10000); reset(mMetricsLogger); target.startCharging(); assertMetricsLog(BatterySavingStats.COUNTER_POWER_MILLIAMPS_PREFIX + "11", 10); + assertMetricsLog(BatterySavingStats.COUNTER_POWER_PERCENT_PREFIX + "11", 1000); assertMetricsLog(BatterySavingStats.COUNTER_TIME_SECONDS_PREFIX + "11", 60 * 10); target.advanceClock(1); @@ -305,6 +313,7 @@ public class BatterySavingStatsTest { target.startCharging(); assertMetricsLog(BatterySavingStats.COUNTER_POWER_MILLIAMPS_PREFIX + "10", 2); + assertMetricsLog(BatterySavingStats.COUNTER_POWER_PERCENT_PREFIX + "10", 200); assertMetricsLog(BatterySavingStats.COUNTER_TIME_SECONDS_PREFIX + "10", 60); } } -- GitLab From f0586626e5955b183c11f46a9d04886f4b300209 Mon Sep 17 00:00:00 2001 From: Robert Carr Date: Mon, 29 Jan 2018 13:03:43 -0800 Subject: [PATCH 193/416] Nuke WSA#mClipRect/mHasClipRect Already dead code as mHasClipRect can never be set to true by trivial inspection. Test: Manual Change-Id: I003837a9f4975ccbfcc737743b1115e69bb608fc --- .../android/server/wm/WindowStateAnimator.java | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 1cfa95625a8..826d83088d0 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -135,8 +135,6 @@ class WindowStateAnimator { float mAlpha = 0; float mLastAlpha = 0; - boolean mHasClipRect; - Rect mClipRect = new Rect(); Rect mTmpClipRect = new Rect(); Rect mTmpFinalClipRect = new Rect(); Rect mLastClipRect = new Rect(); @@ -456,8 +454,6 @@ class WindowStateAnimator { // We may abort, so initialize to defaults. mLastSystemDecorRect.set(0, 0, 0, 0); - mHasClipRect = false; - mClipRect.set(0, 0, 0, 0); mLastClipRect.set(0, 0, 0, 0); // Set up surface control with initial size. @@ -649,14 +645,12 @@ class WindowStateAnimator { } void computeShownFrameLocked() { - final int displayId = mWin.getDisplayId(); final ScreenRotationAnimation screenRotationAnimation = mAnimator.getScreenRotationAnimationLocked(displayId); final boolean screenAnimation = screenRotationAnimation != null && screenRotationAnimation.isAnimating(); - mHasClipRect = false; if (screenAnimation) { // cache often used attributes locally final Rect frame = mWin.mFrame; @@ -796,9 +790,9 @@ class WindowStateAnimator { // We use the clip rect as provided by the tranformation for non-fullscreen windows to // avoid premature clipping with the system decor rect. - clipRect.set((mHasClipRect && !fullscreen) ? mClipRect : mSystemDecorRect); + clipRect.set(mSystemDecorRect); if (DEBUG_WINDOW_CROP) Slog.d(TAG, "win=" + w + " Initial clip rect: " + clipRect - + " mHasClipRect=" + mHasClipRect + " fullscreen=" + fullscreen); + + " fullscreen=" + fullscreen); if (isFreeformResizing && !w.isChildWindow()) { // For freeform resizing non child windows, we are using the big surface positioned @@ -808,12 +802,6 @@ class WindowStateAnimator { w.expandForSurfaceInsets(clipRect); - if (mHasClipRect && fullscreen) { - // We intersect the clip rect specified by the transformation with the expanded system - // decor rect to prevent artifacts from drawing during animation if the transformation - // clip rect extends outside the system decor rect. - clipRect.intersect(mClipRect); - } // The clip rect was generated assuming (0,0) as the window origin, // so we need to translate to match the actual surface coordinates. clipRect.offset(w.mAttrs.surfaceInsets.left, w.mAttrs.surfaceInsets.top); @@ -1379,7 +1367,6 @@ class WindowStateAnimator { pw.print(prefix); pw.print(" mLastHidden="); pw.println(mLastHidden); pw.print(prefix); pw.print("mSystemDecorRect="); mSystemDecorRect.printShortString(pw); pw.print(" last="); mLastSystemDecorRect.printShortString(pw); - pw.print(" mHasClipRect="); pw.print(mHasClipRect); pw.print(" mLastClipRect="); mLastClipRect.printShortString(pw); if (!mLastFinalClipRect.isEmpty()) { -- GitLab From 32bcb10e336741c8e43a35ee1048c7ce36257e39 Mon Sep 17 00:00:00 2001 From: Robert Carr Date: Mon, 29 Jan 2018 15:03:23 -0800 Subject: [PATCH 194/416] Restore pinned stack shadows. We use the approach of outsetting the stack bounds and then insetting windows which don't have surfaceInsets by said outsets. Test: Manual. go/wm-smoke Bug: 72657549 Change-Id: I9ac7e13ec696f88f02794175d0d44ac870f91d33 --- .../java/android/app/WindowConfiguration.java | 3 ++ .../android/internal/policy/DecorView.java | 3 +- .../java/com/android/server/wm/TaskStack.java | 32 +++++++++++++++++-- .../com/android/server/wm/WindowState.java | 11 +++++++ 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java index 085fc79f58e..46566e79d99 100644 --- a/core/java/android/app/WindowConfiguration.java +++ b/core/java/android/app/WindowConfiguration.java @@ -146,6 +146,9 @@ public class WindowConfiguration implements Parcelable, Comparable implements scheduleAnimation(); } + /** + * Calculate an amount by which to expand the stack bounds in each direction. + * Used to make room for shadows in the pinned windowing mode. + */ + int getStackOutset() { + if (inPinnedWindowingMode()) { + final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics(); + return mService.dipToPixel(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP, + displayMetrics); + } + return 0; + } + private void updateSurfaceSize(SurfaceControl.Transaction transaction) { if (mSurfaceControl == null) { return; } final Rect stackBounds = getBounds(); - final int width = stackBounds.width(); - final int height = stackBounds.height(); + int width = stackBounds.width(); + int height = stackBounds.height(); + + final int outset = getStackOutset(); + width += 2*outset; + height += 2*outset; + if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) { return; } @@ -1749,4 +1769,12 @@ public class TaskStack extends WindowContainer implements mDimmer.stopDim(getPendingTransaction()); scheduleAnimation(); } + + @Override + void getRelativePosition(Point outPos) { + super.getRelativePosition(outPos); + final int outset = getStackOutset(); + outPos.x -= outset; + outPos.y -= outset; + } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 53a8d82f551..36e3612bf10 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -4576,6 +4576,17 @@ class WindowState extends WindowContainer implements WindowManagerP outPoint.offset(-parentBounds.left, -parentBounds.top); } + TaskStack stack = getStack(); + + // If we have stack outsets, that means the top-left + // will be outset, and we need to inset ourselves + // to account for it. If we actually have shadows we will + // then un-inset ourselves by the surfaceInsets. + if (stack != null) { + final int outset = stack.getStackOutset(); + outPoint.offset(outset, outset); + } + // Expand for surface insets. See WindowState.expandForSurfaceInsets. outPoint.offset(-mAttrs.surfaceInsets.left, -mAttrs.surfaceInsets.top); } -- GitLab From 046a99ebbb90f9ecdead7b057ef99764a1d295b9 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 31 Jan 2018 14:59:29 -0500 Subject: [PATCH 195/416] Use ImageDecoder in ImageView.getDrawableFromUri Bug: 63909536 Test: Existing CTS tests ImageDecoder will bypass the InputStream if possible, allowing it to be more efficient. In addition, it handles density scaling differently; instead of using more RAM to scale the image up, it results in scaling at draw time. Change-Id: Ied7c0865a736f9ef0de367299264e18ccc3e0b92 --- core/java/android/widget/ImageView.java | 23 ++++++++----------- .../java/android/graphics/ImageDecoder.java | 23 ++++++++++++++++--- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 1dc5b44bed4..4b951fa1824 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -23,10 +23,12 @@ import android.annotation.TestApi; import android.content.ContentResolver; import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorFilter; +import android.graphics.ImageDecoder; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.PorterDuff; @@ -53,7 +55,6 @@ import android.widget.RemoteViews.RemoteView; import com.android.internal.R; import java.io.IOException; -import java.io.InputStream; /** * Displays image resources, for example {@link android.graphics.Bitmap} @@ -946,21 +947,15 @@ public class ImageView extends View { } } else if (ContentResolver.SCHEME_CONTENT.equals(scheme) || ContentResolver.SCHEME_FILE.equals(scheme)) { - InputStream stream = null; try { - stream = mContext.getContentResolver().openInputStream(uri); - return Drawable.createFromResourceStream(sCompatUseCorrectStreamDensity - ? getResources() : null, null, stream, null); - } catch (Exception e) { + Resources res = sCompatUseCorrectStreamDensity ? getResources() : null; + ImageDecoder.Source src = ImageDecoder.createSource(mContext.getContentResolver(), + uri, res); + return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> { + decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); + }); + } catch (IOException e) { Log.w(LOG_TAG, "Unable to open content: " + uri, e); - } finally { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - Log.w(LOG_TAG, "Unable to close content: " + uri, e); - } - } } } else { return Drawable.createFromPath(uri.toString()); diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index bbf21455945..ee7abc5bd25 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -74,7 +74,7 @@ public final class ImageDecoder implements AutoCloseable { int getDensity() { return Bitmap.DENSITY_NONE; } /* @hide */ - int computeDstDensity() { + final int computeDstDensity() { Resources res = getResources(); if (res == null) { return Bitmap.getDefaultDensity(); @@ -122,13 +122,19 @@ public final class ImageDecoder implements AutoCloseable { } private static class ContentResolverSource extends Source { - ContentResolverSource(@NonNull ContentResolver resolver, @NonNull Uri uri) { + ContentResolverSource(@NonNull ContentResolver resolver, @NonNull Uri uri, + @Nullable Resources res) { mResolver = resolver; mUri = uri; + mResources = res; } private final ContentResolver mResolver; private final Uri mUri; + private final Resources mResources; + + @Nullable + Resources getResources() { return mResources; } @Override public ImageDecoder createImageDecoder() throws IOException { @@ -511,7 +517,18 @@ public final class ImageDecoder implements AutoCloseable { @NonNull public static Source createSource(@NonNull ContentResolver cr, @NonNull Uri uri) { - return new ContentResolverSource(cr, uri); + return new ContentResolverSource(cr, uri, null); + } + + /** + * Provide Resources for density scaling. + * + * @hide + */ + @NonNull + public static Source createSource(@NonNull ContentResolver cr, + @NonNull Uri uri, @Nullable Resources res) { + return new ContentResolverSource(cr, uri, res); } /** -- GitLab From 02bafcbac4345e42b53fa15cee239da2a0a0fea4 Mon Sep 17 00:00:00 2001 From: Robert Carr Date: Wed, 31 Jan 2018 12:50:27 -0800 Subject: [PATCH 196/416] WSA: More dead code. Test: Boots Change-Id: I855f5315a6f6cb083d02db5c652f0e667bb9987c --- .../core/java/com/android/server/wm/WindowStateAnimator.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 826d83088d0..5c9cfbb7a5a 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -99,7 +99,6 @@ class WindowStateAnimator { // Unchanging local convenience fields. final WindowManagerService mService; final WindowState mWin; - private final WindowStateAnimator mParentWinAnimator; final WindowAnimator mAnimator; final Session mSession; final WindowManagerPolicy mPolicy; @@ -148,7 +147,6 @@ class WindowStateAnimator { * system decorations. */ private final Rect mSystemDecorRect = new Rect(); - private final Rect mLastSystemDecorRect = new Rect(); float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1; private float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1; @@ -222,7 +220,6 @@ class WindowStateAnimator { mContext = service.mContext; mWin = win; - mParentWinAnimator = !win.isChildWindow() ? null : win.getParentWindow().mWinAnimator; mSession = win.mSession; mAttrType = win.mAttrs.type; mIsWallpaper = win.mIsWallpaper; @@ -453,7 +450,6 @@ class WindowStateAnimator { } // We may abort, so initialize to defaults. - mLastSystemDecorRect.set(0, 0, 0, 0); mLastClipRect.set(0, 0, 0, 0); // Set up surface control with initial size. @@ -1366,7 +1362,6 @@ class WindowStateAnimator { pw.print(prefix); pw.print("mDrawState="); pw.print(drawStateToString()); pw.print(prefix); pw.print(" mLastHidden="); pw.println(mLastHidden); pw.print(prefix); pw.print("mSystemDecorRect="); mSystemDecorRect.printShortString(pw); - pw.print(" last="); mLastSystemDecorRect.printShortString(pw); pw.print(" mLastClipRect="); mLastClipRect.printShortString(pw); if (!mLastFinalClipRect.isEmpty()) { -- GitLab From 9d729c5304508b355345e5490d69f9509bb1cb4d Mon Sep 17 00:00:00 2001 From: Nathan Harold Date: Mon, 29 Jan 2018 19:05:29 -0800 Subject: [PATCH 197/416] CellSignalStrength cleanup Remove a few duplicated initialization methods in the CellSignalStrength classes. They were either not being used or were trivially refactored in to their respective constructors. Bug: 72742517 Test: compilation Change-Id: I7115eace62dc0b6d59ea25deedaf5a0d0f270496 --- .../telephony/CellSignalStrengthCdma.java | 51 +++----------- .../telephony/CellSignalStrengthGsm.java | 66 ++++--------------- .../telephony/CellSignalStrengthLte.java | 66 +++---------------- .../telephony/CellSignalStrengthWcdma.java | 48 +++----------- 4 files changed, 39 insertions(+), 192 deletions(-) diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java index dfaaab91801..ece1ee37817 100644 --- a/telephony/java/android/telephony/CellSignalStrengthCdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java @@ -36,48 +36,14 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements private int mEvdoEcio; // This value is the EVDO Ec/Io private int mEvdoSnr; // Valid values are 0-8. 8 is the highest signal to noise ratio - /** - * Empty constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthCdma() { setDefaultValues(); } - /** - * Constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthCdma(int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, int evdoSnr) { - initialize(cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr); - } - - /** - * Copy constructors - * - * @param s Source SignalStrength - * - * @hide - */ - public CellSignalStrengthCdma(CellSignalStrengthCdma s) { - copyFrom(s); - } - - /** - * Initialize all the values - * - * @param cdmaDbm - * @param cdmaEcio - * @param evdoDbm - * @param evdoEcio - * @param evdoSnr - * - * @hide - */ - public void initialize(int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, int evdoSnr) { mCdmaDbm = cdmaDbm; mCdmaEcio = cdmaEcio; mEvdoDbm = evdoDbm; @@ -85,9 +51,12 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements mEvdoSnr = evdoSnr; } - /** - * @hide - */ + /** @hide */ + public CellSignalStrengthCdma(CellSignalStrengthCdma s) { + copyFrom(s); + } + + /** @hide */ protected void copyFrom(CellSignalStrengthCdma s) { mCdmaDbm = s.mCdmaDbm; mCdmaEcio = s.mCdmaEcio; @@ -96,9 +65,7 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements mEvdoSnr = s.mEvdoSnr; } - /** - * @hide - */ + /** @hide */ @Override public CellSignalStrengthCdma copy() { return new CellSignalStrengthCdma(this); diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index f68d2cad122..8687cd1c454 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -34,80 +34,40 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P private static final int GSM_SIGNAL_STRENGTH_GOOD = 8; private static final int GSM_SIGNAL_STRENGTH_MODERATE = 5; - private int mSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5 + private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5 private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 - private int mTimingAdvance; + private int mTimingAdvance; // range from 0-219 or Integer.MAX_VALUE if unknown - /** - * Empty constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthGsm() { setDefaultValues(); } - /** - * Constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthGsm(int ss, int ber) { - initialize(ss, ber); - } - - /** - * Copy constructors - * - * @param s Source SignalStrength - * - * @hide - */ - public CellSignalStrengthGsm(CellSignalStrengthGsm s) { - copyFrom(s); + this(ss, ber, Integer.MAX_VALUE); } - /** - * Initialize all the values - * - * @param ss SignalStrength as ASU value - * @param ber is Bit Error Rate - * - * @hide - */ - public void initialize(int ss, int ber) { + /** @hide */ + public CellSignalStrengthGsm(int ss, int ber, int ta) { mSignalStrength = ss; mBitErrorRate = ber; - mTimingAdvance = Integer.MAX_VALUE; + mTimingAdvance = ta; } - /** - * Initialize all the values - * - * @param ss SignalStrength as ASU value - * @param ber is Bit Error Rate - * @param ta timing advance - * - * @hide - */ - public void initialize(int ss, int ber, int ta) { - mSignalStrength = ss; - mBitErrorRate = ber; - mTimingAdvance = ta; + /** @hide */ + public CellSignalStrengthGsm(CellSignalStrengthGsm s) { + copyFrom(s); } - /** - * @hide - */ + /** @hide */ protected void copyFrom(CellSignalStrengthGsm s) { mSignalStrength = s.mSignalStrength; mBitErrorRate = s.mBitErrorRate; mTimingAdvance = s.mTimingAdvance; } - /** - * @hide - */ + /** @hide */ @Override public CellSignalStrengthGsm copy() { return new CellSignalStrengthGsm(this); diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index 6ffc8b6e497..f009fb145fc 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -37,50 +37,15 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P private int mCqi; private int mTimingAdvance; - /** - * Empty constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthLte() { setDefaultValues(); } - /** - * Constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthLte(int signalStrength, int rsrp, int rsrq, int rssnr, int cqi, int timingAdvance) { - initialize(signalStrength, rsrp, rsrq, rssnr, cqi, timingAdvance); - } - - /** - * Copy constructors - * - * @param s Source SignalStrength - * - * @hide - */ - public CellSignalStrengthLte(CellSignalStrengthLte s) { - copyFrom(s); - } - - /** - * Initialize all the values - * - * @param lteSignalStrength - * @param rsrp - * @param rsrq - * @param rssnr - * @param cqi - * - * @hide - */ - public void initialize(int lteSignalStrength, int rsrp, int rsrq, int rssnr, int cqi, - int timingAdvance) { - mSignalStrength = lteSignalStrength; + mSignalStrength = signalStrength; mRsrp = rsrp; mRsrq = rsrq; mRssnr = rssnr; @@ -88,25 +53,12 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P mTimingAdvance = timingAdvance; } - /** - * Initialize from the SignalStrength structure. - * - * @param ss - * - * @hide - */ - public void initialize(SignalStrength ss, int timingAdvance) { - mSignalStrength = ss.getLteSignalStrength(); - mRsrp = ss.getLteRsrp(); - mRsrq = ss.getLteRsrq(); - mRssnr = ss.getLteRssnr(); - mCqi = ss.getLteCqi(); - mTimingAdvance = timingAdvance; + /** @hide */ + public CellSignalStrengthLte(CellSignalStrengthLte s) { + copyFrom(s); } - /** - * @hide - */ + /** @hide */ protected void copyFrom(CellSignalStrengthLte s) { mSignalStrength = s.mSignalStrength; mRsrp = s.mRsrp; @@ -116,9 +68,7 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P mTimingAdvance = s.mTimingAdvance; } - /** - * @hide - */ + /** @hide */ @Override public CellSignalStrengthLte copy() { return new CellSignalStrengthLte(this); diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java index 2cd56b8500a..dd32a960db9 100644 --- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java @@ -34,62 +34,32 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements private static final int WCDMA_SIGNAL_STRENGTH_GOOD = 8; private static final int WCDMA_SIGNAL_STRENGTH_MODERATE = 5; - private int mSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5 - private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 + private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5 + private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 - /** - * Empty constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthWcdma() { setDefaultValues(); } - /** - * Constructor - * - * @hide - */ + /** @hide */ public CellSignalStrengthWcdma(int ss, int ber) { - initialize(ss, ber); + mSignalStrength = ss; + mBitErrorRate = ber; } - /** - * Copy constructors - * - * @param s Source SignalStrength - * - * @hide - */ + /** @hide */ public CellSignalStrengthWcdma(CellSignalStrengthWcdma s) { copyFrom(s); } - /** - * Initialize all the values - * - * @param ss SignalStrength as ASU value - * @param ber is Bit Error Rate - * - * @hide - */ - public void initialize(int ss, int ber) { - mSignalStrength = ss; - mBitErrorRate = ber; - } - - /** - * @hide - */ + /** @hide */ protected void copyFrom(CellSignalStrengthWcdma s) { mSignalStrength = s.mSignalStrength; mBitErrorRate = s.mBitErrorRate; } - /** - * @hide - */ + /** @hide */ @Override public CellSignalStrengthWcdma copy() { return new CellSignalStrengthWcdma(this); -- GitLab From 45d4ab077ba93bdfcb8eefb425931bc0364372dd Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Wed, 31 Jan 2018 14:59:14 -0500 Subject: [PATCH 198/416] Improve volume touches - Change the ringer toggle into a tristate - Make the tap targets for the output chooser and ringer toggle larger - Prevent the slider from capturing extra touches - Add ripples to the row icon images Fixes: 72727455 Fixes: 72711039 Fixes: 72627046 Test: runtest systemui Change-Id: Ie658e9ee813be253dfface827fea86544ef80ed5 --- .../SystemUI/res/layout/volume_dialog.xml | 3 +- .../SystemUI/res/layout/volume_dialog_row.xml | 6 +- .../systemui/volume/VolumeDialogImpl.java | 44 ++----- .../systemui/volume/VolumeUiLayout.java | 6 + .../systemui/volume/VolumeDialogImplTest.java | 111 +++++++++++++++++- 5 files changed, 131 insertions(+), 39 deletions(-) diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index 117cd14f446..53dff05b74e 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -59,6 +59,7 @@ android:gravity="center" android:layout_gravity="end" android:translationZ="8dp" + android:clickable="true" android:orientation="vertical" > diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml index 3e80085225e..a9e5adfa531 100644 --- a/packages/SystemUI/res/layout/volume_dialog_row.xml +++ b/packages/SystemUI/res/layout/volume_dialog_row.xml @@ -63,6 +63,7 @@ android:background="?android:selectableItemBackgroundBorderless" android:contentDescription="@string/accessibility_output_chooser" style="@style/VolumeButtons" + android:clickable="false" android:layout_centerVertical="true" android:src="@drawable/ic_swap" android:soundEffectsEnabled="false" /> @@ -70,7 +71,7 @@ @@ -91,6 +90,7 @@ android:padding="10dp" android:layout_width="@dimen/volume_button_size" android:layout_height="@dimen/volume_button_size" + android:background="?android:selectableItemBackgroundBorderless" android:soundEffectsEnabled="false" /> \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 001a582297a..0c6e0f694b4 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -36,7 +36,6 @@ import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Color; -import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.media.AudioManager; import android.media.AudioSystem; @@ -58,7 +57,6 @@ import android.view.View; import android.view.View.AccessibilityDelegate; import android.view.View.OnAttachStateChangeListener; import android.view.View.OnClickListener; -import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; @@ -105,6 +103,7 @@ public class VolumeDialogImpl implements VolumeDialog { private CustomDialog mDialog; private ViewGroup mDialogView; private ViewGroup mDialogRowsView; + private ViewGroup mFooter; private ImageButton mRingerIcon; private TextView mRingerStatus; private final List mRows = new ArrayList<>(); @@ -202,8 +201,9 @@ public class VolumeDialogImpl implements VolumeDialog { hardwareLayout.setOutsideTouchListener(view -> dismiss(DISMISS_REASON_TOUCH_OUTSIDE)); mDialogRowsView = mDialog.findViewById(R.id.volume_dialog_rows); - mRingerIcon = mDialog.findViewById(R.id.ringer_icon); - mRingerStatus = mDialog.findViewById(R.id.ringer_status); + mFooter = mDialog.findViewById(R.id.footer); + mRingerIcon = mFooter.findViewById(R.id.ringer_icon); + mRingerStatus = mFooter.findViewById(R.id.ringer_status); if (mRows.isEmpty()) { addRow(AudioManager.STREAM_MUSIC, @@ -340,36 +340,8 @@ public class VolumeDialogImpl implements VolumeDialog { row.outputChooser = row.view.findViewById(R.id.output_chooser); row.outputChooser.setOnClickListener(mClickOutputChooser); - row.outputChooser.findViewById(R.id.output_chooser_button) - .setOnClickListener(mClickOutputChooser); row.connectedDevice = row.view.findViewById(R.id.volume_row_connected_device); - // forward events above the slider into the slider - row.view.findViewById(R.id.volume_row_slider_frame) - .setOnTouchListener(new OnTouchListener() { - private final Rect mSliderHitRect = new Rect(); - private boolean mDragging; - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouch(View v, MotionEvent event) { - row.slider.getHitRect(mSliderHitRect); - if (!mDragging && event.getActionMasked() == MotionEvent.ACTION_DOWN - && event.getY() < mSliderHitRect.top) { - mDragging = true; - } - if (mDragging) { - event.offsetLocation(-mSliderHitRect.left, -mSliderHitRect.top); - row.slider.dispatchTouchEvent(event); - if (event.getActionMasked() == MotionEvent.ACTION_UP - || event.getActionMasked() == MotionEvent.ACTION_CANCEL) { - mDragging = false; - } - return true; - } - return false; - } - }); row.icon = row.view.findViewById(R.id.volume_row_icon); row.icon.setImageResource(iconRes); if (row.stream != AudioSystem.STREAM_ACCESSIBILITY) { @@ -412,6 +384,8 @@ public class VolumeDialogImpl implements VolumeDialog { if (ss == null) { return; } + // normal -> vibrate -> silent -> normal (skip vibrate if device doesn't have + // a vibrator. final boolean hasVibrator = mController.hasVibrator(); if (mState.ringerModeInternal == AudioManager.RINGER_MODE_NORMAL) { if (hasVibrator) { @@ -419,7 +393,12 @@ public class VolumeDialogImpl implements VolumeDialog { } else { final boolean wasZero = ss.level == 0; mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0); + mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false); } + } else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) { + final boolean wasZero = ss.level == 0; + mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0); + mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false); } else { mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); if (ss.level == 0) { @@ -908,7 +887,6 @@ public class VolumeDialogImpl implements VolumeDialog { private final OnClickListener mClickOutputChooser = new OnClickListener() { @Override public void onClick(View v) { - // TODO: log dismissH(DISMISS_REASON_OUTPUT_CHOOSER); showOutputChooserH(); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java index 368194e57b9..f50a28766d7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUiLayout.java @@ -309,6 +309,12 @@ public class VolumeUiLayout extends FrameLayout { return super.getOutlineProvider(); } + @Override + public void setPressed(boolean pressed) + { + // Ignore presses because it activates the seekbar thumb unnecessarily. + } + public void setOutsideTouchListener(OnClickListener onClickListener) { mHasOutsideTouch = true; requestLayout(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 2d28c9f214f..4888fb284a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -16,12 +16,21 @@ package com.android.systemui.volume; +import static android.media.AudioManager.RINGER_MODE_NORMAL; +import static android.media.AudioManager.RINGER_MODE_SILENT; +import static android.media.AudioManager.RINGER_MODE_VIBRATE; +import static android.media.AudioManager.STREAM_RING; + import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS; import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import android.app.KeyguardManager; import android.media.AudioManager; import android.support.test.filters.SmallTest; @@ -32,8 +41,10 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.VolumeDialogController; +import com.android.systemui.plugins.VolumeDialogController.State; import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import org.junit.Before; @@ -70,13 +81,19 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDialog = new VolumeDialogImpl(getContext()); mDialog.init(0, null); - VolumeDialogController.State state = new VolumeDialogController.State(); + State state = createShellState(); + mDialog.onStateChangedH(state); + } + + private State createShellState() { + State state = new VolumeDialogController.State(); for (int i = AudioManager.STREAM_VOICE_CALL; i <= AudioManager.STREAM_ACCESSIBILITY; i++) { VolumeDialogController.StreamState ss = new VolumeDialogController.StreamState(); ss.name = STREAMS.get(i); + ss.level = 1; state.states.append(i, ss); } - mDialog.onStateChangedH(state); + return state; } private void navigateViews(View view, Predicate condition) { @@ -111,4 +128,94 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDialog.dismiss(DISMISS_REASON_UNKNOWN); } + @Test + public void testNoDuplicationOfParentState() { + mDialog.show(SHOW_REASON_UNKNOWN); + ViewGroup dialog = mDialog.getDialogView(); + + navigateViews(dialog, view -> !view.isDuplicateParentStateEnabled()); + + mDialog.dismiss(DISMISS_REASON_UNKNOWN); + } + + @Test + public void testNoClickableViewGroups() { + mDialog.show(SHOW_REASON_UNKNOWN); + ViewGroup dialog = mDialog.getDialogView(); + + navigateViews(dialog, view -> { + if (view instanceof ViewGroup) { + return !view.isClickable(); + } else { + return true; + } + }); + + mDialog.dismiss(DISMISS_REASON_UNKNOWN); + } + + @Test + public void testTristateToggle_withVibrator() { + when(mController.hasVibrator()).thenReturn(true); + + State state = createShellState(); + state.ringerModeInternal = RINGER_MODE_NORMAL; + mDialog.onStateChangedH(state); + + mDialog.show(SHOW_REASON_UNKNOWN); + ViewGroup dialog = mDialog.getDialogView(); + + // click once, verify updates to vibrate + dialog.findViewById(R.id.ringer_icon).performClick(); + verify(mController, times(1)).setRingerMode(RINGER_MODE_VIBRATE, false); + + // fake the update back to the dialog with the new ringer mode + state = createShellState(); + state.ringerModeInternal = RINGER_MODE_VIBRATE; + mDialog.onStateChangedH(state); + + // click once, verify updates to silent + dialog.findViewById(R.id.ringer_icon).performClick(); + verify(mController, times(1)).setRingerMode(RINGER_MODE_SILENT, false); + verify(mController, times(1)).setStreamVolume(STREAM_RING, 0); + + // fake the update back to the dialog with the new ringer mode + state = createShellState(); + state.states.get(STREAM_RING).level = 0; + state.ringerModeInternal = RINGER_MODE_SILENT; + mDialog.onStateChangedH(state); + + // click once, verify updates to normal + dialog.findViewById(R.id.ringer_icon).performClick(); + verify(mController, times(1)).setRingerMode(RINGER_MODE_NORMAL, false); + verify(mController, times(1)).setStreamVolume(STREAM_RING, 0); + } + + @Test + public void testTristateToggle_withoutVibrator() { + when(mController.hasVibrator()).thenReturn(false); + + State state = createShellState(); + state.ringerModeInternal = RINGER_MODE_NORMAL; + mDialog.onStateChangedH(state); + + mDialog.show(SHOW_REASON_UNKNOWN); + ViewGroup dialog = mDialog.getDialogView(); + + // click once, verify updates to silent + dialog.findViewById(R.id.ringer_icon).performClick(); + verify(mController, times(1)).setRingerMode(RINGER_MODE_SILENT, false); + verify(mController, times(1)).setStreamVolume(STREAM_RING, 0); + + // fake the update back to the dialog with the new ringer mode + state = createShellState(); + state.states.get(STREAM_RING).level = 0; + state.ringerModeInternal = RINGER_MODE_SILENT; + mDialog.onStateChangedH(state); + + // click once, verify updates to normal + dialog.findViewById(R.id.ringer_icon).performClick(); + verify(mController, times(1)).setRingerMode(RINGER_MODE_NORMAL, false); + verify(mController, times(1)).setStreamVolume(STREAM_RING, 0); + } } -- GitLab From 904c1eca5fe0dcf2c61e40d634b75bff3ea9157f Mon Sep 17 00:00:00 2001 From: Mohamed Abdalkader Date: Tue, 23 Jan 2018 09:56:31 -0800 Subject: [PATCH 199/416] Unhide SMS over IMS APIs While here add more constants for send sms result. Test: manual BUG:69846044 Merged-In: I66fdcff51dc5ded9f6199d09bb667c89f38b6d59 Change-Id: I66fdcff51dc5ded9f6199d09bb667c89f38b6d59 --- api/system-current.txt | 41 +++++++ .../java/android/telephony/SmsManager.java | 109 +++++++++++++++++- .../telephony/ims/feature/MMTelFeature.java | 8 +- .../ims/internal/stub/SmsImplBase.java | 91 ++++++++++----- 4 files changed, 212 insertions(+), 37 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index 041c21324fb..1d01bd11db0 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4044,6 +4044,24 @@ package android.telephony { public final class SmsManager { method public void sendMultipartTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.util.List, java.util.List, java.util.List); method public void sendTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent); + field public static final int RESULT_CANCELLED = 23; // 0x17 + field public static final int RESULT_ENCODING_ERROR = 18; // 0x12 + field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 6; // 0x6 + field public static final int RESULT_ERROR_NONE = 0; // 0x0 + field public static final int RESULT_INTERNAL_ERROR = 21; // 0x15 + field public static final int RESULT_INVALID_ARGUMENTS = 11; // 0xb + field public static final int RESULT_INVALID_SMSC_ADDRESS = 19; // 0x13 + field public static final int RESULT_INVALID_SMS_FORMAT = 14; // 0xe + field public static final int RESULT_INVALID_STATE = 12; // 0xc + field public static final int RESULT_MODEM_ERROR = 16; // 0x10 + field public static final int RESULT_NETWORK_ERROR = 17; // 0x11 + field public static final int RESULT_NETWORK_REJECT = 10; // 0xa + field public static final int RESULT_NO_MEMORY = 13; // 0xd + field public static final int RESULT_NO_RESOURCES = 22; // 0x16 + field public static final int RESULT_OPERATION_NOT_ALLOWED = 20; // 0x14 + field public static final int RESULT_RADIO_NOT_AVAILABLE = 9; // 0x9 + field public static final int RESULT_REQUEST_NOT_SUPPORTED = 24; // 0x18 + field public static final int RESULT_SYSTEM_ERROR = 15; // 0xf } public class SubscriptionManager { @@ -4287,6 +4305,29 @@ package android.telephony.ims { } +package android.telephony.ims.internal.stub { + + public class SmsImplBase { + ctor public SmsImplBase(); + method public void acknowledgeSms(int, int, int); + method public void acknowledgeSmsReport(int, int, int); + method public java.lang.String getSmsFormat(); + method public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException; + method public final void onSmsReceived(int, java.lang.String, byte[]) throws java.lang.RuntimeException; + method public final void onSmsStatusReportReceived(int, int, java.lang.String, byte[]) throws java.lang.RuntimeException; + method public void sendSms(int, int, java.lang.String, java.lang.String, boolean, byte[]); + field public static final int DELIVER_STATUS_ERROR = 2; // 0x2 + field public static final int DELIVER_STATUS_OK = 1; // 0x1 + field public static final int SEND_STATUS_ERROR = 2; // 0x2 + field public static final int SEND_STATUS_ERROR_FALLBACK = 4; // 0x4 + field public static final int SEND_STATUS_ERROR_RETRY = 3; // 0x3 + field public static final int SEND_STATUS_OK = 1; // 0x1 + field public static final int STATUS_REPORT_STATUS_ERROR = 2; // 0x2 + field public static final int STATUS_REPORT_STATUS_OK = 1; // 0x1 + } + +} + package android.telephony.mbms { public final class DownloadRequest implements android.os.Parcelable { diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 78a4c654ba1..9a45e7b9cc3 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -1127,7 +1127,11 @@ public final class SmsManager { // SMS send failure result codes - /** No error. {@hide}*/ + /** + * No error. + * @hide + */ + @SystemApi static public final int RESULT_ERROR_NONE = 0; /** Generic failure cause */ static public final int RESULT_ERROR_GENERIC_FAILURE = 1; @@ -1139,12 +1143,113 @@ public final class SmsManager { static public final int RESULT_ERROR_NO_SERVICE = 4; /** Failed because we reached the sending queue limit. */ static public final int RESULT_ERROR_LIMIT_EXCEEDED = 5; - /** Failed because FDN is enabled. {@hide} */ + /** + * Failed because FDN is enabled. + * @hide + */ + @SystemApi static public final int RESULT_ERROR_FDN_CHECK_FAILURE = 6; /** Failed because user denied the sending of this short code. */ static public final int RESULT_ERROR_SHORT_CODE_NOT_ALLOWED = 7; /** Failed because the user has denied this app ever send premium short codes. */ static public final int RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED = 8; + /** + * Failed because the radio was not available + * @hide + */ + @SystemApi + static public final int RESULT_RADIO_NOT_AVAILABLE = 9; + /** + * Failed because of network rejection + * @hide + */ + @SystemApi + static public final int RESULT_NETWORK_REJECT = 10; + /** + * Failed because of invalid arguments + * @hide + */ + @SystemApi + static public final int RESULT_INVALID_ARGUMENTS = 11; + /** + * Failed because of an invalid state + * @hide + */ + @SystemApi + static public final int RESULT_INVALID_STATE = 12; + /** + * Failed because there is no memory + * @hide + */ + @SystemApi + static public final int RESULT_NO_MEMORY = 13; + /** + * Failed because the sms format is not valid + * @hide + */ + @SystemApi + static public final int RESULT_INVALID_SMS_FORMAT = 14; + /** + * Failed because of a system error + * @hide + */ + @SystemApi + static public final int RESULT_SYSTEM_ERROR = 15; + /** + * Failed because of a modem error + * @hide + */ + @SystemApi + static public final int RESULT_MODEM_ERROR = 16; + /** + * Failed because of a network error + * @hide + */ + @SystemApi + static public final int RESULT_NETWORK_ERROR = 17; + /** + * Failed because of an encoding error + * @hide + */ + @SystemApi + static public final int RESULT_ENCODING_ERROR = 18; + /** + * Failed because of an invalid smsc address + * @hide + */ + @SystemApi + static public final int RESULT_INVALID_SMSC_ADDRESS = 19; + /** + * Failed because the operation is not allowed + * @hide + */ + @SystemApi + static public final int RESULT_OPERATION_NOT_ALLOWED = 20; + /** + * Failed because of an internal error + * @hide + */ + @SystemApi + static public final int RESULT_INTERNAL_ERROR = 21; + /** + * Failed because there are no resources + * @hide + */ + @SystemApi + static public final int RESULT_NO_RESOURCES = 22; + /** + * Failed because the operation was cancelled + * @hide + */ + @SystemApi + static public final int RESULT_CANCELLED = 23; + /** + * Failed because the request is not supported + * @hide + */ + @SystemApi + static public final int RESULT_REQUEST_NOT_SUPPORTED = 24; + static private final String PHONE_PACKAGE_NAME = "com.android.phone"; diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java index 51971072840..7a406ade9bf 100644 --- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java @@ -384,21 +384,21 @@ public class MMTelFeature extends ImsFeature { return null; } - public void setSmsListener(IImsSmsListener listener) { + private void setSmsListener(IImsSmsListener listener) { getSmsImplementation().registerSmsListener(listener); } - public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, + private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry, byte[] pdu) { getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu); } - public void acknowledgeSms(int token, int messageRef, + private void acknowledgeSms(int token, int messageRef, @SmsImplBase.DeliverStatusResult int result) { getSmsImplementation().acknowledgeSms(token, messageRef, result); } - public void acknowledgeSmsReport(int token, int messageRef, + private void acknowledgeSmsReport(int token, int messageRef, @SmsImplBase.StatusReportResult int result) { getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); } diff --git a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java index 113dad4696e..c9431fd0e49 100644 --- a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java +++ b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java @@ -17,10 +17,10 @@ package android.telephony.ims.internal.stub; import android.annotation.IntDef; +import android.annotation.SystemApi; import android.os.RemoteException; import android.telephony.SmsManager; import android.telephony.SmsMessage; -import android.telephony.ims.internal.feature.MmTelFeature; import android.util.Log; import com.android.ims.internal.IImsSmsListener; @@ -33,11 +33,14 @@ import java.lang.annotation.RetentionPolicy; * * Any service wishing to provide SMS over IMS should extend this class and implement all methods * that the service supports. + * * @hide */ +@SystemApi public class SmsImplBase { private static final String LOG_TAG = "SmsImplBase"; + /** @hide */ @IntDef({ SEND_STATUS_OK, SEND_STATUS_ERROR, @@ -58,8 +61,8 @@ public class SmsImplBase { public static final int SEND_STATUS_ERROR = 2; /** - * IMS provider failed to send the message and platform should retry again after setting TP-RD bit - * to high. + * IMS provider failed to send the message and platform should retry again after setting TP-RD + * bit to high. */ public static final int SEND_STATUS_ERROR_RETRY = 3; @@ -69,6 +72,7 @@ public class SmsImplBase { */ public static final int SEND_STATUS_ERROR_FALLBACK = 4; + /** @hide */ @IntDef({ DELIVER_STATUS_OK, DELIVER_STATUS_ERROR @@ -85,6 +89,7 @@ public class SmsImplBase { */ public static final int DELIVER_STATUS_ERROR = 2; + /** @hide */ @IntDef({ STATUS_REPORT_STATUS_OK, STATUS_REPORT_STATUS_ERROR @@ -140,21 +145,23 @@ public class SmsImplBase { try { onSendSmsResult(token, messageRef, SEND_STATUS_ERROR, SmsManager.RESULT_ERROR_GENERIC_FAILURE); - } catch (RemoteException e) { + } catch (RuntimeException e) { Log.e(LOG_TAG, "Can not send sms: " + e.getMessage()); } } /** - * This method will be triggered by the platform after {@link #onSmsReceived(int, String, byte[])} - * has been called to deliver the result to the IMS provider. + * This method will be triggered by the platform after + * {@link #onSmsReceived(int, String, byte[])} has been called to deliver the result to the IMS + * provider. * * @param token token provided in {@link #onSmsReceived(int, String, byte[])} - * @param result result of delivering the message. Valid values are defined in - * {@link DeliverStatusResult} + * @param result result of delivering the message. Valid values are: + * {@link #DELIVER_STATUS_OK}, + * {@link #DELIVER_STATUS_OK} * @param messageRef the message reference */ - public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) { + public void acknowledgeSms(int token, @DeliverStatusResult int messageRef, int result) { Log.e(LOG_TAG, "acknowledgeSms() not implemented."); } @@ -164,8 +171,9 @@ public class SmsImplBase { * result to the IMS provider. * * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} - * @param result result of delivering the message. Valid values are defined in - * {@link StatusReportResult} + * @param result result of delivering the message. Valid values are: + * {@link #STATUS_REPORT_STATUS_OK}, + * {@link #STATUS_REPORT_STATUS_ERROR} * @param messageRef the message reference */ public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) { @@ -177,20 +185,17 @@ public class SmsImplBase { * platform will deliver the message to the messages database and notify the IMS provider of the * result by calling {@link #acknowledgeSms(int, int, int)}. * - * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called. - * * @param token unique token generated by IMS providers that the platform will use to trigger * callbacks for this message. * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and * {@link SmsMessage#FORMAT_3GPP2}. * @param pdu PDUs representing the contents of the message. - * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} + * @throws RuntimeException if called before {@link #onReady()} is triggered. */ - public final void onSmsReceived(int token, String format, byte[] pdu) - throws IllegalStateException { + public final void onSmsReceived(int token, String format, byte[] pdu) throws RuntimeException { synchronized (mLock) { if (mListener == null) { - throw new IllegalStateException("Feature not ready."); + throw new RuntimeException("Feature not ready."); } try { mListener.onSmsReceived(token, format, pdu); @@ -205,11 +210,13 @@ public class SmsImplBase { * This method should be triggered by the IMS providers to pass the result of the sent message * to the platform. * - * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called. - * * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040 - * @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult} + * @param status result of sending the SMS. Valid values are: + * {@link #SEND_STATUS_OK}, + * {@link #SEND_STATUS_ERROR}, + * {@link #SEND_STATUS_ERROR_RETRY}, + * {@link #SEND_STATUS_ERROR_FALLBACK}, * @param reason reason in case status is failure. Valid values are: * {@link SmsManager#RESULT_ERROR_NONE}, * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE}, @@ -217,19 +224,41 @@ public class SmsImplBase { * {@link SmsManager#RESULT_ERROR_NULL_PDU}, * {@link SmsManager#RESULT_ERROR_NO_SERVICE}, * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED}, + * {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE}, * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED}, - * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED} - * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} - * @throws RemoteException if the connection to the framework is not available. If this happens - * attempting to send the SMS should be aborted. + * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED}, + * {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE}, + * {@link SmsManager#RESULT_NETWORK_REJECT}, + * {@link SmsManager#RESULT_INVALID_ARGUMENTS}, + * {@link SmsManager#RESULT_INVALID_STATE}, + * {@link SmsManager#RESULT_NO_MEMORY}, + * {@link SmsManager#RESULT_INVALID_SMS_FORMAT}, + * {@link SmsManager#RESULT_SYSTEM_ERROR}, + * {@link SmsManager#RESULT_MODEM_ERROR}, + * {@link SmsManager#RESULT_NETWORK_ERROR}, + * {@link SmsManager#RESULT_ENCODING_ERROR}, + * {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS}, + * {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED}, + * {@link SmsManager#RESULT_INTERNAL_ERROR}, + * {@link SmsManager#RESULT_NO_RESOURCES}, + * {@link SmsManager#RESULT_CANCELLED}, + * {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED} + * + * @throws RuntimeException if called before {@link #onReady()} is triggered or if the + * connection to the framework is not available. If this happens attempting to send the SMS + * should be aborted. */ - public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status, - int reason) throws IllegalStateException, RemoteException { + public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status, + int reason) throws RuntimeException { synchronized (mLock) { if (mListener == null) { - throw new IllegalStateException("Feature not ready."); + throw new RuntimeException("Feature not ready."); + } + try { + mListener.onSendSmsResult(token, messageRef, status, reason); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); } - mListener.onSendSmsResult(token, messageRef, status, reason); } } @@ -241,13 +270,13 @@ public class SmsImplBase { * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and * {@link SmsMessage#FORMAT_3GPP2}. * @param pdu PDUs representing the content of the status report. - * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()} + * @throws RuntimeException if called before {@link #onReady()} is triggered */ public final void onSmsStatusReportReceived(int token, int messageRef, String format, - byte[] pdu) { + byte[] pdu) throws RuntimeException{ synchronized (mLock) { if (mListener == null) { - throw new IllegalStateException("Feature not ready."); + throw new RuntimeException("Feature not ready."); } try { mListener.onSmsStatusReportReceived(token, messageRef, format, pdu); -- GitLab From f29223e3d92b2e07d2f0701116c1e6e2d10a815c Mon Sep 17 00:00:00 2001 From: chaviw Date: Wed, 31 Jan 2018 13:23:51 -0800 Subject: [PATCH 200/416] Re-added Dimmer for IME window container The dimmer was only set for AboveAppWindowContainers so IME couldn't add a dim layer. Moved Dimmer so all NonAppWindowContainers can add a dim layer, which includes AboveAppWindowContainers and IME container. This also fixes the flicker issue from the bug. Change-Id: I747eb3db4982f820f8a09b7a184d0467d240b335 Fixes: 72645607 Test: Dim layer appears when using search from IME. Test: go/wm-smoke-auto --- .../com/android/server/wm/DisplayContent.java | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 7674b5e9ed2..239e73e347d 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3599,8 +3599,6 @@ class DisplayContent extends WindowContainer Date: Thu, 14 Sep 2017 17:10:00 -0700 Subject: [PATCH 201/416] Update diskstats to break out code & data. This updates both the text-based diskstats and the proto-based diskstats to both have both an aggregated and line-itemed view of app sizes. Formerly, the code and data sizes were rolled up into the same category and now they are separated. Bug: 63908720 Test: FrameworksServicesTest passes Merged-In: I1434327ffde6ad1f31243218c5201a80f9725a63 (cherry picked from commit b6cc838142d2390eaec99670bb6caf6bee0ec96f) --- core/proto/android/service/diskstats.proto | 8 ++++++-- .../com/android/server/DiskStatsService.java | 16 +++++++++++++--- .../com/android/server/storage/AppCollector.java | 2 +- .../server/storage/DiskStatsFileLogger.java | 14 ++++++++++++-- .../storage/DiskStatsLoggingServiceTest.java | 4 +++- 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/core/proto/android/service/diskstats.proto b/core/proto/android/service/diskstats.proto index 4d865264d19..4057e45430f 100644 --- a/core/proto/android/service/diskstats.proto +++ b/core/proto/android/service/diskstats.proto @@ -47,7 +47,7 @@ message DiskStatsServiceDumpProto { } message DiskStatsCachedValuesProto { - // Total app data size, in kilobytes + // Total app code size, in kilobytes int64 agg_apps_size = 1; // Total app cache size, in kilobytes int64 agg_apps_cache_size = 2; @@ -65,15 +65,19 @@ message DiskStatsCachedValuesProto { int64 other_size = 8; // Sizes of individual packages repeated DiskStatsAppSizesProto app_sizes = 9; + // Total app data size, in kilobytes + int64 agg_apps_data_size = 10; } message DiskStatsAppSizesProto { // Name of the package string package_name = 1; - // App's data size in kilobytes + // App's code size in kilobytes int64 app_size = 2; // App's cache size in kilobytes int64 cache_size = 3; + // App's data size in kilobytes + int64 app_data_size = 4; } message DiskStatsFreeSpaceProto { diff --git a/services/core/java/com/android/server/DiskStatsService.java b/services/core/java/com/android/server/DiskStatsService.java index 800081e51c9..2d2c6b0bc8b 100644 --- a/services/core/java/com/android/server/DiskStatsService.java +++ b/services/core/java/com/android/server/DiskStatsService.java @@ -202,6 +202,8 @@ public class DiskStatsService extends Binder { JSONObject json = new JSONObject(jsonString); pw.print("App Size: "); pw.println(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)); + pw.print("App Data Size: "); + pw.println(json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY)); pw.print("App Cache Size: "); pw.println(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)); pw.print("Photos Size: "); @@ -220,6 +222,8 @@ public class DiskStatsService extends Binder { pw.println(json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY)); pw.print("App Sizes: "); pw.println(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY)); + pw.print("App Data Sizes: "); + pw.println(json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY)); pw.print("Cache Sizes: "); pw.println(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY)); } catch (IOException | JSONException e) { @@ -235,6 +239,8 @@ public class DiskStatsService extends Binder { proto.write(DiskStatsCachedValuesProto.AGG_APPS_SIZE, json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)); + proto.write(DiskStatsCachedValuesProto.AGG_APPS_DATA_SIZE, + json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY)); proto.write(DiskStatsCachedValuesProto.AGG_APPS_CACHE_SIZE, json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)); proto.write(DiskStatsCachedValuesProto.PHOTOS_SIZE, @@ -252,22 +258,26 @@ public class DiskStatsService extends Binder { JSONArray packageNamesArray = json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY); JSONArray appSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY); + JSONArray appDataSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY); JSONArray cacheSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY); final int len = packageNamesArray.length(); - if (len == appSizesArray.length() && len == cacheSizesArray.length()) { + if (len == appSizesArray.length() + && len == appDataSizesArray.length() + && len == cacheSizesArray.length()) { for (int i = 0; i < len; i++) { long packageToken = proto.start(DiskStatsCachedValuesProto.APP_SIZES); proto.write(DiskStatsAppSizesProto.PACKAGE_NAME, packageNamesArray.getString(i)); proto.write(DiskStatsAppSizesProto.APP_SIZE, appSizesArray.getLong(i)); + proto.write(DiskStatsAppSizesProto.APP_DATA_SIZE, appDataSizesArray.getLong(i)); proto.write(DiskStatsAppSizesProto.CACHE_SIZE, cacheSizesArray.getLong(i)); proto.end(packageToken); } } else { - Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray and cacheSizesArray " - + "are not the same"); + Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray, appDataSizesArray " + + " and cacheSizesArray are not the same"); } proto.end(cachedValuesToken); diff --git a/services/core/java/com/android/server/storage/AppCollector.java b/services/core/java/com/android/server/storage/AppCollector.java index 03b754f777b..0b51f9cc7b7 100644 --- a/services/core/java/com/android/server/storage/AppCollector.java +++ b/services/core/java/com/android/server/storage/AppCollector.java @@ -135,7 +135,7 @@ public class AppCollector { PackageStats packageStats = new PackageStats(app.packageName, user.id); packageStats.cacheSize = storageStats.getCacheBytes(); - packageStats.codeSize = storageStats.getCodeBytes(); + packageStats.codeSize = storageStats.getAppBytes(); packageStats.dataSize = storageStats.getDataBytes(); stats.add(packageStats); } catch (NameNotFoundException | IOException e) { diff --git a/services/core/java/com/android/server/storage/DiskStatsFileLogger.java b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java index 0094ab5559d..1db3ec4ca50 100644 --- a/services/core/java/com/android/server/storage/DiskStatsFileLogger.java +++ b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java @@ -56,10 +56,12 @@ public class DiskStatsFileLogger { public static final String SYSTEM_KEY = "systemSize"; public static final String MISC_KEY = "otherSize"; public static final String APP_SIZE_AGG_KEY = "appSize"; + public static final String APP_DATA_SIZE_AGG_KEY = "appDataSize"; public static final String APP_CACHE_AGG_KEY = "cacheSize"; public static final String PACKAGE_NAMES_KEY = "packageNames"; public static final String APP_SIZES_KEY = "appSizes"; public static final String APP_CACHES_KEY = "cacheSizes"; + public static final String APP_DATA_KEY = "appDataSizes"; public static final String LAST_QUERY_TIMESTAMP_KEY = "queryTime"; private MeasurementResult mResult; @@ -114,31 +116,39 @@ public class DiskStatsFileLogger { private void addAppsToJson(JSONObject json) throws JSONException { JSONArray names = new JSONArray(); JSONArray appSizeList = new JSONArray(); + JSONArray appDataSizeList = new JSONArray(); JSONArray cacheSizeList = new JSONArray(); long appSizeSum = 0L; + long appDataSizeSum = 0L; long cacheSizeSum = 0L; boolean isExternal = Environment.isExternalStorageEmulated(); for (Map.Entry entry : filterOnlyPrimaryUser().entrySet()) { PackageStats stat = entry.getValue(); - long appSize = stat.codeSize + stat.dataSize; + long appSize = stat.codeSize; + long appDataSize = stat.dataSize; long cacheSize = stat.cacheSize; if (isExternal) { - appSize += stat.externalCodeSize + stat.externalDataSize; + appSize += stat.externalCodeSize; + appDataSize += stat.externalDataSize; cacheSize += stat.externalCacheSize; } appSizeSum += appSize; + appDataSizeSum += appDataSize; cacheSizeSum += cacheSize; names.put(stat.packageName); appSizeList.put(appSize); + appDataSizeList.put(appDataSize); cacheSizeList.put(cacheSize); } json.put(PACKAGE_NAMES_KEY, names); json.put(APP_SIZES_KEY, appSizeList); json.put(APP_CACHES_KEY, cacheSizeList); + json.put(APP_DATA_KEY, appDataSizeList); json.put(APP_SIZE_AGG_KEY, appSizeSum); json.put(APP_CACHE_AGG_KEY, cacheSizeSum); + json.put(APP_DATA_SIZE_AGG_KEY, appDataSizeSum); } /** diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java index 375edf3cb5a..b647b99df89 100644 --- a/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java @@ -149,11 +149,13 @@ public class DiskStatsLoggingServiceTest extends AndroidTestCase { assertThat(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(3L); assertThat(json.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(10L); assertThat(json.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(7L); - assertThat(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(15L); + assertThat(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(10L); + assertThat(json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY)).isEqualTo(5L); assertThat(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(55L); assertThat( json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(1L); assertThat(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(1L); + assertThat(json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY).length()).isEqualTo(1L); assertThat(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(1L); } -- GitLab From b407b89c9dbaf06e8725f436e83ca2a38335c3c7 Mon Sep 17 00:00:00 2001 From: Doris Ling Date: Wed, 31 Jan 2018 13:09:54 -0800 Subject: [PATCH 202/416] Add string config_headlineFontFamilyMedium to symbols.xml Bug: 72710227 Test: rebuild Change-Id: I24b94a85eeab6712f3c9d142a0dd2607141ac2c3 --- core/res/res/values/symbols.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a62d49ee7bf..d7ab0a4347f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3151,6 +3151,7 @@ + -- GitLab From 43c97a0e9057e2f7ff34b90cb50692cf56937da2 Mon Sep 17 00:00:00 2001 From: Patrick Baumann Date: Wed, 31 Jan 2018 20:09:03 +0000 Subject: [PATCH 203/416] Revert "Revert "Removes EphemrealResolverService and related"" This reverts commit 1e0c91968e802d49c26e2e8d6ca6e8d31f451894. Reason for revert: Original issue fixed Fixes: 38137176 Fixes: 38121489 Test: manual; builds and instant apps launch Change-Id: If320503381b21580ac1b127c49f754b39ffcc93b --- api/current.txt | 18 +- api/system-removed.txt | 50 ---- .../android/app/EphemeralResolverService.java | 104 -------- .../app/InstantAppResolverService.java | 10 +- core/java/android/content/Intent.java | 30 --- .../content/pm/EphemeralIntentFilter.java | 85 ------- .../content/pm/EphemeralResolveInfo.java | 224 ------------------ .../android/server/pm/InstantAppResolver.java | 28 +-- ...java => InstantAppResolverConnection.java} | 43 ++-- .../server/pm/PackageManagerService.java | 71 ++---- 10 files changed, 72 insertions(+), 591 deletions(-) delete mode 100644 core/java/android/app/EphemeralResolverService.java delete mode 100644 core/java/android/content/pm/EphemeralIntentFilter.java delete mode 100644 core/java/android/content/pm/EphemeralResolveInfo.java rename services/core/java/com/android/server/pm/{EphemeralResolverConnection.java => InstantAppResolverConnection.java} (91%) diff --git a/api/current.txt b/api/current.txt index 085fa33ebe3..7eafc9cc14b 100644 --- a/api/current.txt +++ b/api/current.txt @@ -11613,15 +11613,15 @@ package android.content.res { public final class AssetManager implements java.lang.AutoCloseable { method public void close(); - method public java.lang.String[] getLocales(); - method public java.lang.String[] list(java.lang.String) throws java.io.IOException; - method public java.io.InputStream open(java.lang.String) throws java.io.IOException; - method public java.io.InputStream open(java.lang.String, int) throws java.io.IOException; - method public android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException; - method public android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException; - method public android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException; - method public android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException; - method public android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException; + method public final java.lang.String[] getLocales(); + method public final java.lang.String[] list(java.lang.String) throws java.io.IOException; + method public final java.io.InputStream open(java.lang.String) throws java.io.IOException; + method public final java.io.InputStream open(java.lang.String, int) throws java.io.IOException; + method public final android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException; + method public final android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException; + method public final android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException; + method public final android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException; + method public final android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException; field public static final int ACCESS_BUFFER = 3; // 0x3 field public static final int ACCESS_RANDOM = 1; // 0x1 field public static final int ACCESS_STREAMING = 2; // 0x2 diff --git a/api/system-removed.txt b/api/system-removed.txt index b63703dc003..48f43e0880d 100644 --- a/api/system-removed.txt +++ b/api/system-removed.txt @@ -1,13 +1,5 @@ package android.app { - public abstract deprecated class EphemeralResolverService extends android.app.InstantAppResolverService { - ctor public EphemeralResolverService(); - method public android.os.Looper getLooper(); - method public abstract deprecated java.util.List onEphemeralResolveInfoList(int[], int); - method public android.content.pm.EphemeralResolveInfo onGetEphemeralIntentFilter(java.lang.String); - method public java.util.List onGetEphemeralResolveInfo(int[]); - } - public class Notification implements android.os.Parcelable { method public static java.lang.Class getNotificationStyleClass(java.lang.String); } @@ -31,10 +23,7 @@ package android.content { public class Intent implements java.lang.Cloneable android.os.Parcelable { field public static final deprecated java.lang.String ACTION_DEVICE_INITIALIZATION_WIZARD = "android.intent.action.DEVICE_INITIALIZATION_WIZARD"; - field public static final deprecated java.lang.String ACTION_EPHEMERAL_RESOLVER_SETTINGS = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS"; - field public static final deprecated java.lang.String ACTION_INSTALL_EPHEMERAL_PACKAGE = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE"; field public static final deprecated java.lang.String ACTION_MASTER_CLEAR = "android.intent.action.MASTER_CLEAR"; - field public static final deprecated java.lang.String ACTION_RESOLVE_EPHEMERAL_PACKAGE = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE"; field public static final deprecated java.lang.String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE"; field public static final deprecated java.lang.String EXTRA_CDMA_DEFAULT_ROAMING_INDICATOR = "cdmaDefaultRoamingIndicator"; field public static final deprecated java.lang.String EXTRA_CDMA_ROAMING_INDICATOR = "cdmaRoamingIndicator"; @@ -62,45 +51,6 @@ package android.content { } -package android.content.pm { - - public final deprecated class EphemeralIntentFilter implements android.os.Parcelable { - ctor public EphemeralIntentFilter(java.lang.String, java.util.List); - method public int describeContents(); - method public java.util.List getFilters(); - method public java.lang.String getSplitName(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - - public final deprecated class EphemeralResolveInfo implements android.os.Parcelable { - ctor public deprecated EphemeralResolveInfo(android.net.Uri, java.lang.String, java.util.List); - ctor public deprecated EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List); - ctor public EphemeralResolveInfo(android.content.pm.EphemeralResolveInfo.EphemeralDigest, java.lang.String, java.util.List, int); - ctor public EphemeralResolveInfo(java.lang.String, java.lang.String, java.util.List); - method public int describeContents(); - method public byte[] getDigestBytes(); - method public int getDigestPrefix(); - method public deprecated java.util.List getFilters(); - method public java.util.List getIntentFilters(); - method public java.lang.String getPackageName(); - method public int getVersionCode(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - field public static final java.lang.String SHA_ALGORITHM = "SHA-256"; - } - - public static final class EphemeralResolveInfo.EphemeralDigest implements android.os.Parcelable { - ctor public EphemeralResolveInfo.EphemeralDigest(java.lang.String); - method public int describeContents(); - method public byte[][] getDigestBytes(); - method public int[] getDigestPrefix(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; - } - -} - package android.media.tv { public final class TvInputManager { diff --git a/core/java/android/app/EphemeralResolverService.java b/core/java/android/app/EphemeralResolverService.java deleted file mode 100644 index d1c244136ec..00000000000 --- a/core/java/android/app/EphemeralResolverService.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.app; - -import android.annotation.SystemApi; -import android.content.pm.EphemeralResolveInfo; -import android.content.pm.InstantAppResolveInfo; -import android.os.Build; -import android.os.Looper; -import android.util.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Base class for implementing the resolver service. - * @hide - * @removed - * @deprecated use InstantAppResolverService instead - */ -@Deprecated -@SystemApi -public abstract class EphemeralResolverService extends InstantAppResolverService { - private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; - private static final String TAG = "PackageManager"; - - /** - * Called to retrieve resolve info for ephemeral applications. - * - * @param digestPrefix The hash prefix of the ephemeral's domain. - * @param prefixMask A mask that was applied to each digest prefix. This should - * be used when comparing against the digest prefixes as all bits might - * not be set. - * @deprecated use {@link #onGetEphemeralResolveInfo(int[])} instead - */ - @Deprecated - public abstract List onEphemeralResolveInfoList( - int digestPrefix[], int prefix); - - /** - * Called to retrieve resolve info for ephemeral applications. - * - * @param digestPrefix The hash prefix of the ephemeral's domain. - */ - public List onGetEphemeralResolveInfo(int digestPrefix[]) { - return onEphemeralResolveInfoList(digestPrefix, 0xFFFFF000); - } - - /** - * Called to retrieve intent filters for ephemeral applications. - * - * @param hostName The name of the host to get intent filters for. - */ - public EphemeralResolveInfo onGetEphemeralIntentFilter(String hostName) { - throw new IllegalStateException("Must define"); - } - - @Override - public Looper getLooper() { - return super.getLooper(); - } - - void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, - InstantAppResolutionCallback callback) { - if (DEBUG_EPHEMERAL) { - Log.d(TAG, "Legacy resolver; getInstantAppResolveInfo;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - final List response = onGetEphemeralResolveInfo(digestPrefix); - final int responseSize = response == null ? 0 : response.size(); - final List resultList = new ArrayList<>(responseSize); - for (int i = 0; i < responseSize; i++) { - resultList.add(response.get(i).getInstantAppResolveInfo()); - } - callback.onInstantAppResolveInfo(resultList); - } - - void _onGetInstantAppIntentFilter(int[] digestPrefix, String token, - String hostName, InstantAppResolutionCallback callback) { - if (DEBUG_EPHEMERAL) { - Log.d(TAG, "Legacy resolver; getInstantAppIntentFilter;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - final EphemeralResolveInfo response = onGetEphemeralIntentFilter(hostName); - final List resultList = new ArrayList<>(1); - resultList.add(response.getInstantAppResolveInfo()); - callback.onInstantAppResolveInfo(resultList); - } -} diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java index 89aff36f883..76a36820ed8 100644 --- a/core/java/android/app/InstantAppResolverService.java +++ b/core/java/android/app/InstantAppResolverService.java @@ -43,7 +43,7 @@ import java.util.List; */ @SystemApi public abstract class InstantAppResolverService extends Service { - private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; private static final String TAG = "PackageManager"; /** @hide */ @@ -133,7 +133,7 @@ public abstract class InstantAppResolverService extends Service { @Override public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix, String token, int sequence, IRemoteCallback callback) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "[" + token + "] Phase1 called; posting"); } final SomeArgs args = SomeArgs.obtain(); @@ -148,7 +148,7 @@ public abstract class InstantAppResolverService extends Service { @Override public void getInstantAppIntentFilterList(Intent sanitizedIntent, int[] digestPrefix, String token, IRemoteCallback callback) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "[" + token + "] Phase2 called; posting"); } final SomeArgs args = SomeArgs.obtain(); @@ -203,7 +203,7 @@ public abstract class InstantAppResolverService extends Service { final String token = (String) args.arg3; final Intent intent = (Intent) args.arg4; final int sequence = message.arg1; - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "[" + token + "] Phase1 request;" + " prefix: " + Arrays.toString(digestPrefix)); } @@ -217,7 +217,7 @@ public abstract class InstantAppResolverService extends Service { final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; final Intent intent = (Intent) args.arg4; - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "[" + token + "] Phase2 request;" + " prefix: " + Arrays.toString(digestPrefix)); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index b3c8737a283..908465efa0c 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1553,16 +1553,6 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_INSTALL_FAILURE = "android.intent.action.INSTALL_FAILURE"; - /** - * @hide - * @removed - * @deprecated Do not use. This will go away. - * Replace with {@link #ACTION_INSTALL_INSTANT_APP_PACKAGE}. - */ - @SystemApi - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_INSTALL_EPHEMERAL_PACKAGE - = "android.intent.action.INSTALL_EPHEMERAL_PACKAGE"; /** * Activity Action: Launch instant application installer. *

    @@ -1576,16 +1566,6 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_INSTALL_INSTANT_APP_PACKAGE = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE"; - /** - * @hide - * @removed - * @deprecated Do not use. This will go away. - * Replace with {@link #ACTION_RESOLVE_INSTANT_APP_PACKAGE}. - */ - @SystemApi - @SdkConstant(SdkConstantType.SERVICE_ACTION) - public static final String ACTION_RESOLVE_EPHEMERAL_PACKAGE - = "android.intent.action.RESOLVE_EPHEMERAL_PACKAGE"; /** * Service Action: Resolve instant application. *

    @@ -1600,16 +1580,6 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_RESOLVE_INSTANT_APP_PACKAGE = "android.intent.action.RESOLVE_INSTANT_APP_PACKAGE"; - /** - * @hide - * @removed - * @deprecated Do not use. This will go away. - * Replace with {@link #ACTION_INSTANT_APP_RESOLVER_SETTINGS}. - */ - @SystemApi - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_EPHEMERAL_RESOLVER_SETTINGS - = "android.intent.action.EPHEMERAL_RESOLVER_SETTINGS"; /** * Activity Action: Launch instant app settings. * diff --git a/core/java/android/content/pm/EphemeralIntentFilter.java b/core/java/android/content/pm/EphemeralIntentFilter.java deleted file mode 100644 index 1dbbf816ed9..00000000000 --- a/core/java/android/content/pm/EphemeralIntentFilter.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.pm; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.content.IntentFilter; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; - -/** - * Information about an ephemeral application intent filter. - * @hide - * @removed - */ -@Deprecated -@SystemApi -public final class EphemeralIntentFilter implements Parcelable { - private final InstantAppIntentFilter mInstantAppIntentFilter; - - public EphemeralIntentFilter(@Nullable String splitName, @NonNull List filters) { - mInstantAppIntentFilter = new InstantAppIntentFilter(splitName, filters); - } - - EphemeralIntentFilter(@NonNull InstantAppIntentFilter intentFilter) { - mInstantAppIntentFilter = intentFilter; - } - - EphemeralIntentFilter(Parcel in) { - mInstantAppIntentFilter = in.readParcelable(null /*loader*/); - } - - public String getSplitName() { - return mInstantAppIntentFilter.getSplitName(); - } - - public List getFilters() { - return mInstantAppIntentFilter.getFilters(); - } - - /** @hide */ - InstantAppIntentFilter getInstantAppIntentFilter() { - return mInstantAppIntentFilter; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mInstantAppIntentFilter, flags); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public EphemeralIntentFilter createFromParcel(Parcel in) { - return new EphemeralIntentFilter(in); - } - @Override - public EphemeralIntentFilter[] newArray(int size) { - return new EphemeralIntentFilter[size]; - } - }; -} diff --git a/core/java/android/content/pm/EphemeralResolveInfo.java b/core/java/android/content/pm/EphemeralResolveInfo.java deleted file mode 100644 index 12131a3ebc9..00000000000 --- a/core/java/android/content/pm/EphemeralResolveInfo.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.pm; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemApi; -import android.content.IntentFilter; -import android.content.pm.InstantAppResolveInfo.InstantAppDigest; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -/** - * Information about an ephemeral application. - * @hide - * @removed - */ -@Deprecated -@SystemApi -public final class EphemeralResolveInfo implements Parcelable { - /** Algorithm that will be used to generate the domain digest */ - public static final String SHA_ALGORITHM = "SHA-256"; - - private final InstantAppResolveInfo mInstantAppResolveInfo; - @Deprecated - private final List mLegacyFilters; - - @Deprecated - public EphemeralResolveInfo(@NonNull Uri uri, @NonNull String packageName, - @NonNull List filters) { - if (uri == null || packageName == null || filters == null || filters.isEmpty()) { - throw new IllegalArgumentException(); - } - final List ephemeralFilters = new ArrayList<>(1); - ephemeralFilters.add(new EphemeralIntentFilter(packageName, filters)); - mInstantAppResolveInfo = new InstantAppResolveInfo(uri.getHost(), packageName, - createInstantAppIntentFilterList(ephemeralFilters)); - mLegacyFilters = new ArrayList(filters.size()); - mLegacyFilters.addAll(filters); - } - - @Deprecated - public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName, - @Nullable List filters) { - this(digest, packageName, filters, -1 /*versionCode*/); - } - - public EphemeralResolveInfo(@NonNull EphemeralDigest digest, @Nullable String packageName, - @Nullable List filters, int versionCode) { - mInstantAppResolveInfo = new InstantAppResolveInfo( - digest.getInstantAppDigest(), packageName, - createInstantAppIntentFilterList(filters), versionCode); - mLegacyFilters = null; - } - - public EphemeralResolveInfo(@NonNull String hostName, @Nullable String packageName, - @Nullable List filters) { - this(new EphemeralDigest(hostName), packageName, filters); - } - - EphemeralResolveInfo(Parcel in) { - mInstantAppResolveInfo = in.readParcelable(null /*loader*/); - mLegacyFilters = new ArrayList(); - in.readList(mLegacyFilters, null /*loader*/); - } - - /** @hide */ - public InstantAppResolveInfo getInstantAppResolveInfo() { - return mInstantAppResolveInfo; - } - - private static List createInstantAppIntentFilterList( - List filters) { - if (filters == null) { - return null; - } - final int filterCount = filters.size(); - final List returnList = new ArrayList<>(filterCount); - for (int i = 0; i < filterCount; i++) { - returnList.add(filters.get(i).getInstantAppIntentFilter()); - } - return returnList; - } - - public byte[] getDigestBytes() { - return mInstantAppResolveInfo.getDigestBytes(); - } - - public int getDigestPrefix() { - return mInstantAppResolveInfo.getDigestPrefix(); - } - - public String getPackageName() { - return mInstantAppResolveInfo.getPackageName(); - } - - public List getIntentFilters() { - final List filters = mInstantAppResolveInfo.getIntentFilters(); - final int filterCount = filters.size(); - final List returnList = new ArrayList<>(filterCount); - for (int i = 0; i < filterCount; i++) { - returnList.add(new EphemeralIntentFilter(filters.get(i))); - } - return returnList; - } - - public int getVersionCode() { - return mInstantAppResolveInfo.getVersionCode(); - } - - @Deprecated - public List getFilters() { - return mLegacyFilters; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mInstantAppResolveInfo, flags); - out.writeList(mLegacyFilters); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public EphemeralResolveInfo createFromParcel(Parcel in) { - return new EphemeralResolveInfo(in); - } - @Override - public EphemeralResolveInfo[] newArray(int size) { - return new EphemeralResolveInfo[size]; - } - }; - - /** - * Helper class to generate and store each of the digests and prefixes - * sent to the Ephemeral Resolver. - *

    - * Since intent filters may want to handle multiple hosts within a - * domain [eg “*.google.com”], the resolver is presented with multiple - * hash prefixes. For example, "a.b.c.d.e" generates digests for - * "d.e", "c.d.e", "b.c.d.e" and "a.b.c.d.e". - * - * @hide - */ - @SystemApi - public static final class EphemeralDigest implements Parcelable { - private final InstantAppDigest mInstantAppDigest; - - public EphemeralDigest(@NonNull String hostName) { - this(hostName, -1 /*maxDigests*/); - } - - /** @hide */ - public EphemeralDigest(@NonNull String hostName, int maxDigests) { - mInstantAppDigest = new InstantAppDigest(hostName, maxDigests); - } - - EphemeralDigest(Parcel in) { - mInstantAppDigest = in.readParcelable(null /*loader*/); - } - - /** @hide */ - InstantAppDigest getInstantAppDigest() { - return mInstantAppDigest; - } - - public byte[][] getDigestBytes() { - return mInstantAppDigest.getDigestBytes(); - } - - public int[] getDigestPrefix() { - return mInstantAppDigest.getDigestPrefix(); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeParcelable(mInstantAppDigest, flags); - } - - @SuppressWarnings("hiding") - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - @Override - public EphemeralDigest createFromParcel(Parcel in) { - return new EphemeralDigest(in); - } - @Override - public EphemeralDigest[] newArray(int size) { - return new EphemeralDigest[size]; - } - }; - } -} diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index 55212cc6b3d..00fdb9d687d 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -51,8 +51,8 @@ import android.util.Slog; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; -import com.android.server.pm.EphemeralResolverConnection.ConnectionException; -import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback; +import com.android.server.pm.InstantAppResolverConnection.ConnectionException; +import com.android.server.pm.InstantAppResolverConnection.PhaseTwoCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -66,7 +66,7 @@ import java.util.UUID; /** @hide */ public abstract class InstantAppResolver { - private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; private static final String TAG = "PackageManager"; private static final int RESOLUTION_SUCCESS = 0; @@ -117,10 +117,10 @@ public abstract class InstantAppResolver { } public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne( - EphemeralResolverConnection connection, InstantAppRequest requestObj) { + InstantAppResolverConnection connection, InstantAppRequest requestObj) { final long startTime = System.currentTimeMillis(); final String token = UUID.randomUUID().toString(); - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Log.d(TAG, "[" + token + "] Phase1; resolving"); } final Intent origIntent = requestObj.origIntent; @@ -152,7 +152,7 @@ public abstract class InstantAppResolver { logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE, startTime, token, resolutionStatus); } - if (DEBUG_EPHEMERAL && resolveInfo == null) { + if (DEBUG_INSTANT && resolveInfo == null) { if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) { Log.d(TAG, "[" + token + "] Phase1; bind timed out"); } else if (resolutionStatus == RESOLUTION_CALL_TIMEOUT) { @@ -173,11 +173,11 @@ public abstract class InstantAppResolver { } public static void doInstantAppResolutionPhaseTwo(Context context, - EphemeralResolverConnection connection, InstantAppRequest requestObj, + InstantAppResolverConnection connection, InstantAppRequest requestObj, ActivityInfo instantAppInstaller, Handler callbackHandler) { final long startTime = System.currentTimeMillis(); final String token = requestObj.responseObj.token; - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Log.d(TAG, "[" + token + "] Phase2; resolving"); } final Intent origIntent = requestObj.origIntent; @@ -234,7 +234,7 @@ public abstract class InstantAppResolver { } logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token, resolutionStatus); - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { if (resolutionStatus == RESOLUTION_BIND_TIMEOUT) { Log.d(TAG, "[" + token + "] Phase2; bind timed out"); } else { @@ -444,13 +444,13 @@ public abstract class InstantAppResolver { return null; } // No filters; we need to start phase two - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Log.d(TAG, "No app filters; go to phase 2"); } return Collections.emptyList(); } - final PackageManagerService.EphemeralIntentResolver instantAppResolver = - new PackageManagerService.EphemeralIntentResolver(); + final PackageManagerService.InstantAppIntentResolver instantAppResolver = + new PackageManagerService.InstantAppIntentResolver(); for (int j = instantAppFilters.size() - 1; j >= 0; --j) { final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); final List splitFilters = instantAppFilter.getFilters(); @@ -481,11 +481,11 @@ public abstract class InstantAppResolver { instantAppResolver.queryIntent( origIntent, resolvedType, false /*defaultOnly*/, userId); if (!matchedResolveInfoList.isEmpty()) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList); } return matchedResolveInfoList; - } else if (DEBUG_EPHEMERAL) { + } else if (DEBUG_INSTANT) { Log.d(TAG, "[" + token + "] No matches found" + " package: " + instantAppInfo.getPackageName() + ", versionCode: " + instantAppInfo.getVersionCode()); diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java similarity index 91% rename from services/core/java/com/android/server/pm/EphemeralResolverConnection.java rename to services/core/java/com/android/server/pm/InstantAppResolverConnection.java index 6d6c960eed7..a9ee41162ae 100644 --- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java +++ b/services/core/java/com/android/server/pm/InstantAppResolverConnection.java @@ -37,34 +37,29 @@ import android.util.Slog; import android.util.TimedRemoteCaller; import com.android.internal.annotations.GuardedBy; -import com.android.internal.os.TransferPipe; -import java.io.FileDescriptor; -import java.io.IOException; -import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.TimeoutException; /** - * Represents a remote ephemeral resolver. It is responsible for binding to the remote + * Represents a remote instant app resolver. It is responsible for binding to the remote * service and handling all interactions in a timely manner. * @hide */ -final class EphemeralResolverConnection implements DeathRecipient { +final class InstantAppResolverConnection implements DeathRecipient { private static final String TAG = "PackageManager"; // This is running in a critical section and the timeout must be sufficiently low private static final long BIND_SERVICE_TIMEOUT_MS = Build.IS_ENG ? 500 : 300; private static final long CALL_SERVICE_TIMEOUT_MS = Build.IS_ENG ? 200 : 100; - private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; private final Object mLock = new Object(); - private final GetEphemeralResolveInfoCaller mGetEphemeralResolveInfoCaller = - new GetEphemeralResolveInfoCaller(); + private final GetInstantAppResolveInfoCaller mGetInstantAppResolveInfoCaller = + new GetInstantAppResolveInfoCaller(); private final ServiceConnection mServiceConnection = new MyServiceConnection(); private final Context mContext; /** Intent used to bind to the service */ @@ -79,7 +74,7 @@ final class EphemeralResolverConnection implements DeathRecipient { @GuardedBy("mLock") private IInstantAppResolver mRemoteInstance; - public EphemeralResolverConnection( + public InstantAppResolverConnection( Context context, ComponentName componentName, String action) { mContext = context; mIntent = new Intent(action).setComponent(componentName); @@ -98,8 +93,8 @@ final class EphemeralResolverConnection implements DeathRecipient { throw new ConnectionException(ConnectionException.FAILURE_INTERRUPTED); } try { - return mGetEphemeralResolveInfoCaller - .getEphemeralResolveInfoList(target, sanitizedIntent, hashPrefix, token); + return mGetInstantAppResolveInfoCaller + .getInstantAppResolveInfoList(target, sanitizedIntent, hashPrefix, token); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_CALL); } catch (RemoteException ignore) { @@ -171,7 +166,7 @@ final class EphemeralResolverConnection implements DeathRecipient { if (mBindState == STATE_PENDING) { // there is a pending bind, let's see if we can use it. - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.i(TAG, "[" + token + "] Previous bind timed out; waiting for connection"); } try { @@ -188,7 +183,7 @@ final class EphemeralResolverConnection implements DeathRecipient { if (mBindState == STATE_BINDING) { // someone was binding when we called bind(), or they raced ahead while we were // waiting in the PENDING case; wait for their result instead. Last chance! - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.i(TAG, "[" + token + "] Another thread is binding; waiting for connection"); } waitForBindLocked(token); @@ -206,12 +201,12 @@ final class EphemeralResolverConnection implements DeathRecipient { IInstantAppResolver instance = null; try { if (doUnbind) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.i(TAG, "[" + token + "] Previous connection never established; rebinding"); } mContext.unbindService(mServiceConnection); } - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "[" + token + "] Binding to instant app resolver"); } final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; @@ -247,7 +242,7 @@ final class EphemeralResolverConnection implements DeathRecipient { @Override public void binderDied() { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Binder to instant app resolver died"); } synchronized (mLock) { @@ -286,7 +281,7 @@ final class EphemeralResolverConnection implements DeathRecipient { private final class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Connected to instant app resolver"); } synchronized (mLock) { @@ -295,7 +290,7 @@ final class EphemeralResolverConnection implements DeathRecipient { mBindState = STATE_IDLE; } try { - service.linkToDeath(EphemeralResolverConnection.this, 0 /*flags*/); + service.linkToDeath(InstantAppResolverConnection.this, 0 /*flags*/); } catch (RemoteException e) { handleBinderDiedLocked(); } @@ -305,7 +300,7 @@ final class EphemeralResolverConnection implements DeathRecipient { @Override public void onServiceDisconnected(ComponentName name) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Disconnected from instant app resolver"); } synchronized (mLock) { @@ -314,11 +309,11 @@ final class EphemeralResolverConnection implements DeathRecipient { } } - private static final class GetEphemeralResolveInfoCaller + private static final class GetInstantAppResolveInfoCaller extends TimedRemoteCaller> { private final IRemoteCallback mCallback; - public GetEphemeralResolveInfoCaller() { + public GetInstantAppResolveInfoCaller() { super(CALL_SERVICE_TIMEOUT_MS); mCallback = new IRemoteCallback.Stub() { @Override @@ -333,7 +328,7 @@ final class EphemeralResolverConnection implements DeathRecipient { }; } - public List getEphemeralResolveInfoList( + public List getInstantAppResolveInfoList( IInstantAppResolver target, Intent sanitizedIntent, int hashPrefix[], String token) throws RemoteException, TimeoutException { final int sequence = onBeforeRemoteCall(); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index be988f2eb6f..73223151420 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -423,7 +423,7 @@ public class PackageManagerService extends IPackageManager.Stub public static final boolean DEBUG_DEXOPT = false; private static final boolean DEBUG_ABI_SELECTION = false; - private static final boolean DEBUG_EPHEMERAL = Build.IS_DEBUGGABLE; + private static final boolean DEBUG_INSTANT = Build.IS_DEBUGGABLE; private static final boolean DEBUG_TRIAGED_MISSING = false; private static final boolean DEBUG_APP_DATA = false; @@ -981,7 +981,7 @@ public class PackageManagerService extends IPackageManager.Stub private int mIntentFilterVerificationToken = 0; /** The service connection to the ephemeral resolver */ - final EphemeralResolverConnection mInstantAppResolverConnection; + final InstantAppResolverConnection mInstantAppResolverConnection; /** Component used to show resolver settings for Instant Apps */ final ComponentName mInstantAppResolverSettingsComponent; @@ -3149,10 +3149,10 @@ Slog.e("TODD", final Pair instantAppResolverComponent = getInstantAppResolverLPr(); if (instantAppResolverComponent != null) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Set ephemeral resolver: " + instantAppResolverComponent); } - mInstantAppResolverConnection = new EphemeralResolverConnection( + mInstantAppResolverConnection = new InstantAppResolverConnection( mContext, instantAppResolverComponent.first, instantAppResolverComponent.second); mInstantAppResolverSettingsComponent = @@ -3532,7 +3532,7 @@ Slog.e("TODD", final String[] packageArray = mContext.getResources().getStringArray(R.array.config_ephemeralResolverPackage); if (packageArray.length == 0 && !Build.IS_DEBUGGABLE) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Ephemeral resolver NOT found; empty package list"); } return null; @@ -3547,19 +3547,9 @@ Slog.e("TODD", final Intent resolverIntent = new Intent(actionName); List resolvers = queryIntentServicesInternal(resolverIntent, null, resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/); - // temporarily look for the old action - if (resolvers.size() == 0) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "Ephemeral resolver not found with new action; try old one"); - } - actionName = Intent.ACTION_RESOLVE_EPHEMERAL_PACKAGE; - resolverIntent.setAction(actionName); - resolvers = queryIntentServicesInternal(resolverIntent, null, - resolveFlags, UserHandle.USER_SYSTEM, callingUid, false /*includeInstantApps*/); - } final int N = resolvers.size(); if (N == 0) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Ephemeral resolver NOT found; no matching intent filters"); } return null; @@ -3575,20 +3565,20 @@ Slog.e("TODD", final String packageName = info.serviceInfo.packageName; if (!possiblePackages.contains(packageName) && !Build.IS_DEBUGGABLE) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Ephemeral resolver not in allowed package list;" + " pkg: " + packageName + ", info:" + info); } continue; } - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Ephemeral resolver found;" + " pkg: " + packageName + ", info:" + info); } return new Pair<>(new ComponentName(packageName, info.serviceInfo.name), actionName); } - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Ephemeral resolver NOT found"); } return null; @@ -3598,11 +3588,9 @@ Slog.e("TODD", String[] orderedActions = Build.IS_ENG ? new String[]{ Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE + "_TEST", - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, - Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE} + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE} : new String[]{ - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, - Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE}; + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE}; final int resolveFlags = MATCH_DIRECT_BOOT_AWARE @@ -3618,7 +3606,7 @@ Slog.e("TODD", matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, resolveFlags, UserHandle.USER_SYSTEM); if (matches.isEmpty()) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Instant App installer not found with " + action); } } else { @@ -3656,15 +3644,6 @@ Slog.e("TODD", final int resolveFlags = MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE; List matches = queryIntentActivitiesInternal(intent, null, resolveFlags, UserHandle.USER_SYSTEM); - // temporarily look for the old action - if (matches.isEmpty()) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "Ephemeral resolver settings not found with new action; try old one"); - } - intent.setAction(Intent.ACTION_EPHEMERAL_RESOLVER_SETTINGS); - matches = queryIntentActivitiesInternal(intent, null, resolveFlags, - UserHandle.USER_SYSTEM); - } if (matches.isEmpty()) { return null; } @@ -6007,7 +5986,7 @@ Slog.e("TODD", final int status = (int) (packedStatus >> 32); if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS || status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName + ", status: " + status); } @@ -6015,7 +5994,7 @@ Slog.e("TODD", } } if (ps.getInstantApp(userId)) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "DENY instant app installed;" + " pkg: " + packageName); } @@ -6651,7 +6630,7 @@ Slog.e("TODD", // there's a local instant application installed, but, the user has // chosen to never use it; skip resolution and don't acknowledge // an instant application is even available - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Instant app marked to never run; pkg: " + packageName); } blockResolution = true; @@ -6659,7 +6638,7 @@ Slog.e("TODD", } else { // we have a locally installed instant application; skip resolution // but acknowledge there's an instant application available - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Found installed instant app; pkg: " + packageName); } localInstantApp = info; @@ -6717,7 +6696,7 @@ Slog.e("TODD", // make sure this resolver is the default ephemeralInstaller.isDefault = true; ephemeralInstaller.auxiliaryInfo = auxiliaryResponse; - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } @@ -7604,7 +7583,7 @@ Slog.e("TODD", info.serviceInfo.splitName)) { // requested service is defined in a split that hasn't been installed yet. // add the installer to the resolve list - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } final ResolveInfo installerInfo = new ResolveInfo( @@ -7726,7 +7705,7 @@ Slog.e("TODD", info.providerInfo.splitName)) { // requested provider is defined in a split that hasn't been installed yet. // add the installer to the resolve list - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } final ResolveInfo installerInfo = new ResolveInfo( @@ -11791,14 +11770,14 @@ Slog.e("TODD", private void setUpInstantAppInstallerActivityLP(ActivityInfo installerActivity) { if (installerActivity == null) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Clear ephemeral installer activity"); } mInstantAppInstallerActivity = null; return; } - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.d(TAG, "Set ephemeral installer activity: " + installerActivity.getComponentName()); } @@ -13258,7 +13237,7 @@ Slog.e("TODD", private int mFlags; } - static final class EphemeralIntentResolver + static final class InstantAppIntentResolver extends IntentResolver { /** @@ -13628,7 +13607,7 @@ Slog.e("TODD", IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, String installerPackageName, int installerUid, UserHandle user, PackageParser.SigningDetails signingDetails) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { if ((sessionParams.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0) { Slog.d(TAG, "Ephemeral install of " + packageName); } @@ -15112,7 +15091,7 @@ Slog.e("TODD", pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags, packageAbiOverride); - if (DEBUG_EPHEMERAL && ephemeral) { + if (DEBUG_INSTANT && ephemeral) { Slog.v(TAG, "pkgLite for install: " + pkgLite); } @@ -15179,7 +15158,7 @@ Slog.e("TODD", installFlags |= PackageManager.INSTALL_EXTERNAL; installFlags &= ~PackageManager.INSTALL_INTERNAL; } else if (loc == PackageHelper.RECOMMEND_INSTALL_EPHEMERAL) { - if (DEBUG_EPHEMERAL) { + if (DEBUG_INSTANT) { Slog.v(TAG, "...setting INSTALL_EPHEMERAL install flag"); } installFlags |= PackageManager.INSTALL_INSTANT_APP; -- GitLab From 577d402d0d938c14d415054289a5ecbc613d0046 Mon Sep 17 00:00:00 2001 From: Patrick Baumann Date: Wed, 31 Jan 2018 16:55:10 +0000 Subject: [PATCH 204/416] Revert "Revert "Adds generic intent Instant App resolution"" This reverts commit 860b8ba71938e9860a31881c1d1431877f9d01a2. The original change that was reverted contained a bug that allowed an http view/browsable intent used to query for browsers to be considered as a candidate for instant apps. This was resulting in an attempt to bind to the instant app resolver while holding a lock on mPackages. This change ensures that PMS doesn't bind while checking for the browser status of a package in both the instant app filtering code and by adding the FLAG_IGNORE_EPHEMERAL to the canonical browser intent. Reason for revert: Applying fix Change-Id: I4896b3a15416a11fdc3f6c191e552c4ce8963623 Fixes: 63117034 Fixes: 71916178 Test: Manual using test app at google_experimental/users/patb/InstantAppsInP Test: atest android.appsecurity.cts.EphemeralTest passes after modification --- api/current.txt | 1 + api/system-current.txt | 11 +- .../android/app/EphemeralResolverService.java | 12 - .../java/android/app/IInstantAppResolver.aidl | 8 +- .../app/InstantAppResolverService.java | 107 +++--- core/java/android/content/Intent.java | 50 ++- .../content/pm/AuxiliaryResolveInfo.java | 99 ++++-- .../content/pm/InstantAppResolveInfo.java | 96 +++++- .../internal/app/ResolverListController.java | 13 +- .../server/am/ActivityStackSupervisor.java | 10 +- .../android/server/am/ActivityStarter.java | 30 +- .../pm/EphemeralResolverConnection.java | 28 +- .../android/server/pm/InstantAppResolver.java | 322 ++++++++++++------ .../server/pm/PackageManagerService.java | 183 +++++----- 14 files changed, 646 insertions(+), 324 deletions(-) diff --git a/api/current.txt b/api/current.txt index 44f2f083604..085fa33ebe3 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10050,6 +10050,7 @@ package android.content { field public static final int FLAG_ACTIVITY_FORWARD_RESULT = 33554432; // 0x2000000 field public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 1048576; // 0x100000 field public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 4096; // 0x1000 + field public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 2048; // 0x800 field public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; // 0x8000000 field public static final int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; // 0x80000 field public static final int FLAG_ACTIVITY_NEW_TASK = 268435456; // 0x10000000 diff --git a/api/system-current.txt b/api/system-current.txt index 034ee3090fc..08ac295c936 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -311,8 +311,10 @@ package android.app { ctor public InstantAppResolverService(); method public final void attachBaseContext(android.content.Context); method public final android.os.IBinder onBind(android.content.Intent); - method public void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); - method public void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); + method public deprecated void onGetInstantAppIntentFilter(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); + method public void onGetInstantAppIntentFilter(android.content.Intent, int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); + method public deprecated void onGetInstantAppResolveInfo(int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); + method public void onGetInstantAppResolveInfo(android.content.Intent, int[], java.lang.String, android.app.InstantAppResolverService.InstantAppResolutionCallback); } public static final class InstantAppResolverService.InstantAppResolutionCallback { @@ -821,12 +823,14 @@ package android.content { field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST"; field public static final java.lang.String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS"; field public static final java.lang.String EXTRA_FORCE_FACTORY_RESET = "android.intent.extra.FORCE_FACTORY_RESET"; + field public static final java.lang.String EXTRA_INSTANT_APP_BUNDLES = "android.intent.extra.INSTANT_APP_BUNDLES"; field public static final java.lang.String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID"; field public static final java.lang.String EXTRA_PACKAGES = "android.intent.extra.PACKAGES"; field public static final java.lang.String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; field public static final java.lang.String EXTRA_REASON = "android.intent.extra.REASON"; field public static final java.lang.String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK"; field public static final java.lang.String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED"; + field public static final java.lang.String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP"; } public class IntentFilter implements android.os.Parcelable { @@ -869,6 +873,7 @@ package android.content.pm { ctor public InstantAppResolveInfo(android.content.pm.InstantAppResolveInfo.InstantAppDigest, java.lang.String, java.util.List, int); ctor public InstantAppResolveInfo(android.content.pm.InstantAppResolveInfo.InstantAppDigest, java.lang.String, java.util.List, long, android.os.Bundle); ctor public InstantAppResolveInfo(java.lang.String, java.lang.String, java.util.List); + ctor public InstantAppResolveInfo(android.os.Bundle); method public int describeContents(); method public byte[] getDigestBytes(); method public int getDigestPrefix(); @@ -877,6 +882,7 @@ package android.content.pm { method public long getLongVersionCode(); method public java.lang.String getPackageName(); method public deprecated int getVersionCode(); + method public boolean shouldLetInstallerDecide(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -888,6 +894,7 @@ package android.content.pm { method public int[] getDigestPrefix(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; + field public static final android.content.pm.InstantAppResolveInfo.InstantAppDigest UNDEFINED; } public final class IntentFilterVerificationInfo implements android.os.Parcelable { diff --git a/core/java/android/app/EphemeralResolverService.java b/core/java/android/app/EphemeralResolverService.java index 427a0386e87..d1c244136ec 100644 --- a/core/java/android/app/EphemeralResolverService.java +++ b/core/java/android/app/EphemeralResolverService.java @@ -17,20 +17,10 @@ package android.app; import android.annotation.SystemApi; -import android.app.Service; -import android.app.InstantAppResolverService.InstantAppResolutionCallback; -import android.content.Context; -import android.content.Intent; import android.content.pm.EphemeralResolveInfo; import android.content.pm.InstantAppResolveInfo; import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.IRemoteCallback; import android.os.Looper; -import android.os.Message; -import android.os.RemoteException; import android.util.Log; import java.util.ArrayList; @@ -85,7 +75,6 @@ public abstract class EphemeralResolverService extends InstantAppResolverService return super.getLooper(); } - @Override void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, InstantAppResolutionCallback callback) { if (DEBUG_EPHEMERAL) { @@ -101,7 +90,6 @@ public abstract class EphemeralResolverService extends InstantAppResolverService callback.onInstantAppResolveInfo(resultList); } - @Override void _onGetInstantAppIntentFilter(int[] digestPrefix, String token, String hostName, InstantAppResolutionCallback callback) { if (DEBUG_EPHEMERAL) { diff --git a/core/java/android/app/IInstantAppResolver.aidl b/core/java/android/app/IInstantAppResolver.aidl index 805d8c057d2..ae200578d82 100644 --- a/core/java/android/app/IInstantAppResolver.aidl +++ b/core/java/android/app/IInstantAppResolver.aidl @@ -16,13 +16,15 @@ package android.app; +import android.content.Intent; import android.os.IRemoteCallback; /** @hide */ oneway interface IInstantAppResolver { - void getInstantAppResolveInfoList(in int[] digestPrefix, + void getInstantAppResolveInfoList(in Intent sanitizedIntent, in int[] hostDigestPrefix, String token, int sequence, IRemoteCallback callback); - void getInstantAppIntentFilterList(in int[] digestPrefix, - String token, String hostName, IRemoteCallback callback); + void getInstantAppIntentFilterList(in Intent sanitizedIntent, in int[] hostDigestPrefix, + String token, IRemoteCallback callback); + } diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java index c5dc86c79ef..89aff36f883 100644 --- a/core/java/android/app/InstantAppResolverService.java +++ b/core/java/android/app/InstantAppResolverService.java @@ -17,7 +17,6 @@ package android.app; import android.annotation.SystemApi; -import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.InstantAppResolveInfo; @@ -35,6 +34,7 @@ import android.util.Slog; import com.android.internal.os.SomeArgs; import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -53,23 +53,65 @@ public abstract class InstantAppResolverService extends Service { Handler mHandler; /** - * Called to retrieve resolve info for instant applications. + * Called to retrieve resolve info for instant applications immediately. * * @param digestPrefix The hash prefix of the instant app's domain. + * @deprecated should implement {@link #onGetInstantAppResolveInfo(Intent, int[], String, + * InstantAppResolutionCallback)} */ + @Deprecated public void onGetInstantAppResolveInfo( int digestPrefix[], String token, InstantAppResolutionCallback callback) { throw new IllegalStateException("Must define"); } /** - * Called to retrieve intent filters for instant applications. + * Called to retrieve intent filters for instant applications from potentially expensive + * sources. * * @param digestPrefix The hash prefix of the instant app's domain. + * @deprecated should implement {@link #onGetInstantAppIntentFilter(Intent, int[], String, + * InstantAppResolutionCallback)} */ + @Deprecated public void onGetInstantAppIntentFilter( int digestPrefix[], String token, InstantAppResolutionCallback callback) { - throw new IllegalStateException("Must define"); + throw new IllegalStateException("Must define onGetInstantAppIntentFilter"); + } + + /** + * Called to retrieve resolve info for instant applications immediately. + * + * @param sanitizedIntent The sanitized {@link Intent} used for resolution. + * @param hostDigestPrefix The hash prefix of the instant app's domain. + */ + public void onGetInstantAppResolveInfo(Intent sanitizedIntent, int[] hostDigestPrefix, + String token, InstantAppResolutionCallback callback) { + // if not overridden, forward to old methods and filter out non-web intents + if (sanitizedIntent.isBrowsableWebIntent()) { + onGetInstantAppResolveInfo(hostDigestPrefix, token, callback); + } else { + callback.onInstantAppResolveInfo(Collections.emptyList()); + } + } + + /** + * Called to retrieve intent filters for instant applications from potentially expensive + * sources. + * + * @param sanitizedIntent The sanitized {@link Intent} used for resolution. + * @param hostDigestPrefix The hash prefix of the instant app's domain or null if no host is + * defined. + */ + public void onGetInstantAppIntentFilter(Intent sanitizedIntent, int[] hostDigestPrefix, + String token, InstantAppResolutionCallback callback) { + Log.e(TAG, "New onGetInstantAppIntentFilter is not overridden"); + // if not overridden, forward to old methods and filter out non-web intents + if (sanitizedIntent.isBrowsableWebIntent()) { + onGetInstantAppIntentFilter(hostDigestPrefix, token, callback); + } else { + callback.onInstantAppResolveInfo(Collections.emptyList()); + } } /** @@ -89,8 +131,8 @@ public abstract class InstantAppResolverService extends Service { public final IBinder onBind(Intent intent) { return new IInstantAppResolver.Stub() { @Override - public void getInstantAppResolveInfoList( - int digestPrefix[], String token, int sequence, IRemoteCallback callback) { + public void getInstantAppResolveInfoList(Intent sanitizedIntent, int[] digestPrefix, + String token, int sequence, IRemoteCallback callback) { if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Phase1 called; posting"); } @@ -98,14 +140,14 @@ public abstract class InstantAppResolverService extends Service { args.arg1 = callback; args.arg2 = digestPrefix; args.arg3 = token; - mHandler.obtainMessage( - ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, sequence, 0, args) - .sendToTarget(); + args.arg4 = sanitizedIntent; + mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_RESOLVE_INFO, + sequence, 0, args).sendToTarget(); } @Override - public void getInstantAppIntentFilterList( - int digestPrefix[], String token, String hostName, IRemoteCallback callback) { + public void getInstantAppIntentFilterList(Intent sanitizedIntent, + int[] digestPrefix, String token, IRemoteCallback callback) { if (DEBUG_EPHEMERAL) { Slog.v(TAG, "[" + token + "] Phase2 called; posting"); } @@ -113,9 +155,9 @@ public abstract class InstantAppResolverService extends Service { args.arg1 = callback; args.arg2 = digestPrefix; args.arg3 = token; - args.arg4 = hostName; - mHandler.obtainMessage( - ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, callback).sendToTarget(); + args.arg4 = sanitizedIntent; + mHandler.obtainMessage(ServiceHandler.MSG_GET_INSTANT_APP_INTENT_FILTER, + callback).sendToTarget(); } }; } @@ -142,29 +184,9 @@ public abstract class InstantAppResolverService extends Service { } } - @Deprecated - void _onGetInstantAppResolveInfo(int[] digestPrefix, String token, - InstantAppResolutionCallback callback) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "[" + token + "] Phase1 request;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - onGetInstantAppResolveInfo(digestPrefix, token, callback); - } - @Deprecated - void _onGetInstantAppIntentFilter(int digestPrefix[], String token, String hostName, - InstantAppResolutionCallback callback) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "[" + token + "] Phase2 request;" - + " prefix: " + Arrays.toString(digestPrefix)); - } - onGetInstantAppIntentFilter(digestPrefix, token, callback); - } - private final class ServiceHandler extends Handler { public static final int MSG_GET_INSTANT_APP_RESOLVE_INFO = 1; public static final int MSG_GET_INSTANT_APP_INTENT_FILTER = 2; - public ServiceHandler(Looper looper) { super(looper, null /*callback*/, true /*async*/); } @@ -179,9 +201,13 @@ public abstract class InstantAppResolverService extends Service { final IRemoteCallback callback = (IRemoteCallback) args.arg1; final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; + final Intent intent = (Intent) args.arg4; final int sequence = message.arg1; - _onGetInstantAppResolveInfo( - digestPrefix, token, + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "[" + token + "] Phase1 request;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + onGetInstantAppResolveInfo(intent, digestPrefix, token, new InstantAppResolutionCallback(sequence, callback)); } break; @@ -190,9 +216,12 @@ public abstract class InstantAppResolverService extends Service { final IRemoteCallback callback = (IRemoteCallback) args.arg1; final int[] digestPrefix = (int[]) args.arg2; final String token = (String) args.arg3; - final String hostName = (String) args.arg4; - _onGetInstantAppIntentFilter( - digestPrefix, token, hostName, + final Intent intent = (Intent) args.arg4; + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "[" + token + "] Phase2 request;" + + " prefix: " + Arrays.toString(digestPrefix)); + } + onGetInstantAppIntentFilter(intent, digestPrefix, token, new InstantAppResolutionCallback(-1 /*sequence*/, callback)); } break; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index e02a2949429..b3c8737a283 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -50,6 +50,7 @@ import android.provider.DocumentsContract; import android.provider.DocumentsProvider; import android.provider.MediaStore; import android.provider.OpenableColumns; +import android.text.TextUtils; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; @@ -4472,7 +4473,15 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION"; /** - * A {@link Bundle} of metadata that describes the instanta application that needs to be + * An array of {@link Bundle}s containing details about resolved instant apps.. + * @hide + */ + @SystemApi + public static final String EXTRA_INSTANT_APP_BUNDLES = + "android.intent.extra.INSTANT_APP_BUNDLES"; + + /** + * A {@link Bundle} of metadata that describes the instant application that needs to be * installed. This data is populated from the response to * {@link android.content.pm.InstantAppResolveInfo#getExtras()} as provided by the registered * instant application resolver. @@ -4481,6 +4490,16 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_INSTANT_APP_EXTRAS = "android.intent.extra.INSTANT_APP_EXTRAS"; + /** + * A boolean value indicating that the instant app resolver was unable to state with certainty + * that it did or did not have an app for the sanitized {@link Intent} defined at + * {@link #EXTRA_INTENT}. + * @hide + */ + @SystemApi + public static final String EXTRA_UNKNOWN_INSTANT_APP = + "android.intent.extra.UNKNOWN_INSTANT_APP"; + /** * The version code of the app to install components from. * @deprecated Use {@link #EXTRA_LONG_VERSION_CODE). @@ -5029,6 +5048,7 @@ public class Intent implements Parcelable, Cloneable { FLAG_GRANT_PREFIX_URI_PERMISSION, FLAG_DEBUG_TRIAGED_MISSING, FLAG_IGNORE_EPHEMERAL, + FLAG_ACTIVITY_MATCH_EXTERNAL, FLAG_ACTIVITY_NO_HISTORY, FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_NEW_TASK, @@ -5072,6 +5092,7 @@ public class Intent implements Parcelable, Cloneable { FLAG_INCLUDE_STOPPED_PACKAGES, FLAG_DEBUG_TRIAGED_MISSING, FLAG_IGNORE_EPHEMERAL, + FLAG_ACTIVITY_MATCH_EXTERNAL, FLAG_ACTIVITY_NO_HISTORY, FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_NEW_TASK, @@ -5475,6 +5496,14 @@ public class Intent implements Parcelable, Cloneable { */ public static final int FLAG_ACTIVITY_LAUNCH_ADJACENT = 0x00001000; + + /** + * If set, resolution of this intent may take place via an instant app not + * yet on the device if there does not yet exist an app on device to + * resolve it. + */ + public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 0x00000800; + /** * If set, when sending a broadcast only registered receivers will be * called -- no BroadcastReceiver components will be launched. @@ -10028,6 +10057,25 @@ public class Intent implements Parcelable, Cloneable { } } + /** @hide */ + public boolean hasWebURI() { + if (getData() == null) { + return false; + } + final String scheme = getScheme(); + if (TextUtils.isEmpty(scheme)) { + return false; + } + return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS); + } + + /** @hide */ + public boolean isBrowsableWebIntent() { + return ACTION_VIEW.equals(mAction) + && hasCategory(CATEGORY_BROWSABLE) + && hasWebURI(); + } + /** * @hide */ diff --git a/core/java/android/content/pm/AuxiliaryResolveInfo.java b/core/java/android/content/pm/AuxiliaryResolveInfo.java index 6bdcefbe974..202df50dda6 100644 --- a/core/java/android/content/pm/AuxiliaryResolveInfo.java +++ b/core/java/android/content/pm/AuxiliaryResolveInfo.java @@ -21,6 +21,10 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; +import android.os.Bundle; + +import java.util.Collections; +import java.util.List; /** * Auxiliary application resolution response. @@ -31,56 +35,95 @@ import android.content.IntentFilter; * hasn't been installed. * @hide */ -public final class AuxiliaryResolveInfo extends IntentFilter { - /** Resolved information returned from the external instant resolver */ - public final InstantAppResolveInfo resolveInfo; - /** The resolved package. Copied from {@link #resolveInfo}. */ - public final String packageName; +public final class AuxiliaryResolveInfo { /** The activity to launch if there's an installation failure. */ public final ComponentName installFailureActivity; - /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */ - public final String splitName; /** Whether or not instant resolution needs the second phase */ public final boolean needsPhaseTwo; /** Opaque token to track the instant application resolution */ public final String token; - /** The version code of the package */ - public final long versionCode; /** An intent to start upon failure to install */ public final Intent failureIntent; + /** The matching filters for this resolve info. */ + public final List filters; /** Create a response for installing an instant application. */ - public AuxiliaryResolveInfo(@NonNull InstantAppResolveInfo resolveInfo, - @NonNull IntentFilter orig, - @Nullable String splitName, - @NonNull String token, + public AuxiliaryResolveInfo(@NonNull String token, boolean needsPhase2, - @Nullable Intent failureIntent) { - super(orig); - this.resolveInfo = resolveInfo; - this.packageName = resolveInfo.getPackageName(); - this.splitName = splitName; + @Nullable Intent failureIntent, + @Nullable List filters) { this.token = token; this.needsPhaseTwo = needsPhase2; - this.versionCode = resolveInfo.getVersionCode(); this.failureIntent = failureIntent; + this.filters = filters; this.installFailureActivity = null; } /** Create a response for installing a split on demand. */ - public AuxiliaryResolveInfo(@NonNull String packageName, - @Nullable String splitName, - @Nullable ComponentName failureActivity, - long versionCode, - @Nullable Intent failureIntent) { + public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity, + @Nullable Intent failureIntent, + @Nullable List filters) { super(); - this.packageName = packageName; this.installFailureActivity = failureActivity; - this.splitName = splitName; - this.versionCode = versionCode; - this.resolveInfo = null; + this.filters = filters; this.token = null; this.needsPhaseTwo = false; this.failureIntent = failureIntent; } + + /** Create a response for installing a split on demand. */ + public AuxiliaryResolveInfo(@Nullable ComponentName failureActivity, + String packageName, long versionCode, String splitName) { + this(failureActivity, null, Collections.singletonList( + new AuxiliaryResolveInfo.AuxiliaryFilter(packageName, versionCode, splitName))); + } + + /** @hide */ + public static final class AuxiliaryFilter extends IntentFilter { + /** Resolved information returned from the external instant resolver */ + public final InstantAppResolveInfo resolveInfo; + /** The resolved package. Copied from {@link #resolveInfo}. */ + public final String packageName; + /** The version code of the package */ + public final long versionCode; + /** The resolve split. Copied from the matched filter in {@link #resolveInfo}. */ + public final String splitName; + /** The extras to pass on to the installer for this filter. */ + public final Bundle extras; + + public AuxiliaryFilter(IntentFilter orig, InstantAppResolveInfo resolveInfo, + String splitName, Bundle extras) { + super(orig); + this.resolveInfo = resolveInfo; + this.packageName = resolveInfo.getPackageName(); + this.versionCode = resolveInfo.getLongVersionCode(); + this.splitName = splitName; + this.extras = extras; + } + + public AuxiliaryFilter(InstantAppResolveInfo resolveInfo, + String splitName, Bundle extras) { + this.resolveInfo = resolveInfo; + this.packageName = resolveInfo.getPackageName(); + this.versionCode = resolveInfo.getLongVersionCode(); + this.splitName = splitName; + this.extras = extras; + } + + public AuxiliaryFilter(String packageName, long versionCode, String splitName) { + this.resolveInfo = null; + this.packageName = packageName; + this.versionCode = versionCode; + this.splitName = splitName; + this.extras = null; + } + + @Override + public String toString() { + return "AuxiliaryFilter{" + + "packageName='" + packageName + '\'' + + ", versionCode=" + versionCode + + ", splitName='" + splitName + '\'' + '}'; + } + } } \ No newline at end of file diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java index 19cb9323ba9..112c5dae673 100644 --- a/core/java/android/content/pm/InstantAppResolveInfo.java +++ b/core/java/android/content/pm/InstantAppResolveInfo.java @@ -19,6 +19,7 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.content.Intent; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -26,11 +27,35 @@ import android.os.Parcelable; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; /** - * Information about an instant application. + * Describes an externally resolvable instant application. There are three states that this class + * can represent:

    + *

      + *
    • + * The first, usable only for non http/s intents, implies that the resolver cannot + * immediately resolve this intent and would prefer that resolution be deferred to the + * instant app installer. Represent this state with {@link #InstantAppResolveInfo(Bundle)}. + * If the {@link android.content.Intent} has the scheme set to http/s and a set of digest + * prefixes were passed into one of the resolve methods in + * {@link android.app.InstantAppResolverService}, this state cannot be used. + *
    • + *
    • + * The second represents a partial match and is constructed with any of the other + * constructors. By setting one or more of the {@link Nullable}arguments to null, you + * communicate to the resolver in response to + * {@link android.app.InstantAppResolverService#onGetInstantAppResolveInfo(Intent, int[], + * String, InstantAppResolverService.InstantAppResolutionCallback)} + * that you need a 2nd round of resolution to complete the request. + *
    • + *
    • + * The third represents a complete match and is constructed with all @Nullable parameters + * populated. + *
    • + *
    * @hide */ @SystemApi @@ -38,6 +63,8 @@ public final class InstantAppResolveInfo implements Parcelable { /** Algorithm that will be used to generate the domain digest */ private static final String SHA_ALGORITHM = "SHA-256"; + private static final byte[] EMPTY_DIGEST = new byte[0]; + private final InstantAppDigest mDigest; private final String mPackageName; /** The filters used to match domain */ @@ -46,15 +73,30 @@ public final class InstantAppResolveInfo implements Parcelable { private final long mVersionCode; /** Data about the app that should be passed along to the Instant App installer on resolve */ private final Bundle mExtras; + /** + * A flag that indicates that the resolver is aware that an app may match, but would prefer + * that the installer get the sanitized intent to decide. This should not be used for + * resolutions that include a host and will be ignored in such cases. + */ + private final boolean mShouldLetInstallerDecide; + /** Constructor for intent-based InstantApp resolution results. */ public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, @Nullable List filters, int versionCode) { this(digest, packageName, filters, (long) versionCode, null /* extras */); } + /** Constructor for intent-based InstantApp resolution results with extras. */ public InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, @Nullable List filters, long versionCode, @Nullable Bundle extras) { + this(digest, packageName, filters, versionCode, extras, false); + } + + /** Constructor for intent-based InstantApp resolution results with extras. */ + private InstantAppResolveInfo(@NonNull InstantAppDigest digest, @Nullable String packageName, + @Nullable List filters, long versionCode, + @Nullable Bundle extras, boolean shouldLetInstallerDecide) { // validate arguments if ((packageName == null && (filters != null && filters.size() != 0)) || (packageName != null && (filters == null || filters.size() == 0))) { @@ -62,7 +104,7 @@ public final class InstantAppResolveInfo implements Parcelable { } mDigest = digest; if (filters != null) { - mFilters = new ArrayList(filters.size()); + mFilters = new ArrayList<>(filters.size()); mFilters.addAll(filters); } else { mFilters = null; @@ -70,25 +112,48 @@ public final class InstantAppResolveInfo implements Parcelable { mPackageName = packageName; mVersionCode = versionCode; mExtras = extras; + mShouldLetInstallerDecide = shouldLetInstallerDecide; } + /** Constructor for intent-based InstantApp resolution results by hostname. */ public InstantAppResolveInfo(@NonNull String hostName, @Nullable String packageName, @Nullable List filters) { this(new InstantAppDigest(hostName), packageName, filters, -1 /*versionCode*/, null /* extras */); } + /** + * Constructor that creates a "let the installer decide" response with optional included + * extras. + */ + public InstantAppResolveInfo(@Nullable Bundle extras) { + this(InstantAppDigest.UNDEFINED, null, null, -1, extras, true); + } + InstantAppResolveInfo(Parcel in) { - mDigest = in.readParcelable(null /*loader*/); - mPackageName = in.readString(); - mFilters = new ArrayList(); - in.readList(mFilters, null /*loader*/); - mVersionCode = in.readLong(); + mShouldLetInstallerDecide = in.readBoolean(); mExtras = in.readBundle(); + if (mShouldLetInstallerDecide) { + mDigest = InstantAppDigest.UNDEFINED; + mPackageName = null; + mFilters = Collections.emptyList(); + mVersionCode = -1; + } else { + mDigest = in.readParcelable(null /*loader*/); + mPackageName = in.readString(); + mFilters = new ArrayList<>(); + in.readList(mFilters, null /*loader*/); + mVersionCode = in.readLong(); + } + } + + /** Returns true if the installer should be notified that it should query for packages. */ + public boolean shouldLetInstallerDecide() { + return mShouldLetInstallerDecide; } public byte[] getDigestBytes() { - return mDigest.getDigestBytes()[0]; + return mDigest.mDigestBytes.length > 0 ? mDigest.getDigestBytes()[0] : EMPTY_DIGEST; } public int getDigestPrefix() { @@ -127,11 +192,15 @@ public final class InstantAppResolveInfo implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { + out.writeBoolean(mShouldLetInstallerDecide); + out.writeBundle(mExtras); + if (mShouldLetInstallerDecide) { + return; + } out.writeParcelable(mDigest, flags); out.writeString(mPackageName); out.writeList(mFilters); out.writeLong(mVersionCode); - out.writeBundle(mExtras); } public static final Parcelable.Creator CREATOR @@ -159,7 +228,9 @@ public final class InstantAppResolveInfo implements Parcelable { @SystemApi public static final class InstantAppDigest implements Parcelable { private static final int DIGEST_MASK = 0xfffff000; - private static final int DIGEST_PREFIX_COUNT = 5; + + public static final InstantAppDigest UNDEFINED = + new InstantAppDigest(new byte[][]{}, new int[]{}); /** Full digest of the domain hashes */ private final byte[][] mDigestBytes; /** The first 4 bytes of the domain hashes */ @@ -186,6 +257,11 @@ public final class InstantAppResolveInfo implements Parcelable { } } + private InstantAppDigest(byte[][] digestBytes, int[] prefix) { + this.mDigestPrefix = prefix; + this.mDigestBytes = digestBytes; + } + private static byte[][] generateDigest(String hostName, int maxDigests) { ArrayList digests = new ArrayList<>(); try { diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 2ab2d20ed20..1dfff5efcab 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -103,11 +103,14 @@ public class ResolverListController { List resolvedComponents = null; for (int i = 0, N = intents.size(); i < N; i++) { final Intent intent = intents.get(i); - final List infos = mpm.queryIntentActivities(intent, - PackageManager.MATCH_DEFAULT_ONLY - | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) - | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0) - | PackageManager.MATCH_INSTANT); + int flags = PackageManager.MATCH_DEFAULT_ONLY + | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) + | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0); + if (intent.isBrowsableWebIntent() + || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { + flags |= PackageManager.MATCH_INSTANT; + } + final List infos = mpm.queryIntentActivities(intent, flags); // Remove any activities that are not exported. int totalSize = infos.size(); for (int j = totalSize - 1; j >= 0 ; j--) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 6beafcb94ea..bf388258769 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1251,10 +1251,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D synchronized (mService) { try { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent"); + int modifiedFlags = flags + | PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS; + if (intent.isBrowsableWebIntent() + || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { + modifiedFlags |= PackageManager.MATCH_INSTANT; + } return mService.getPackageManagerInternalLocked().resolveIntent( - intent, resolvedType, PackageManager.MATCH_INSTANT - | PackageManager.MATCH_DEFAULT_ONLY | flags - | ActivityManagerService.STOCK_PM_FLAGS, userId, true); + intent, resolvedType, modifiedFlags, userId, true); } finally { Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 8fd754af1a0..eab88aa45e4 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -31,6 +31,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; @@ -789,7 +790,7 @@ class ActivityStarter { // Instead, launch the ephemeral installer. Once the installer is finished, it // starts either the intent we resolved here [on install error] or the ephemeral // app [on install success]. - if (rInfo != null && rInfo.auxiliaryInfo != null) { + if (rInfo != null && rInfo.isInstantAppAvailable) { intent = createLaunchIntent(rInfo.auxiliaryInfo, ephemeralIntent, callingPackage, verificationBundle, resolvedType, userId); resolvedType = null; @@ -849,22 +850,27 @@ class ActivityStarter { /** * Creates a launch intent for the given auxiliary resolution data. */ - private @NonNull Intent createLaunchIntent(@NonNull AuxiliaryResolveInfo auxiliaryResponse, + private @NonNull Intent createLaunchIntent(@Nullable AuxiliaryResolveInfo auxiliaryResponse, Intent originalIntent, String callingPackage, Bundle verificationBundle, String resolvedType, int userId) { - if (auxiliaryResponse.needsPhaseTwo) { + if (auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo) { // request phase two resolution mService.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo( auxiliaryResponse, originalIntent, resolvedType, callingPackage, verificationBundle, userId); } return InstantAppResolver.buildEphemeralInstallerIntent( - Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, originalIntent, - auxiliaryResponse.failureIntent, callingPackage, verificationBundle, - resolvedType, userId, auxiliaryResponse.packageName, auxiliaryResponse.splitName, - auxiliaryResponse.installFailureActivity, auxiliaryResponse.versionCode, - auxiliaryResponse.token, auxiliaryResponse.resolveInfo.getExtras(), - auxiliaryResponse.needsPhaseTwo); + originalIntent, + InstantAppResolver.sanitizeIntent(originalIntent), + auxiliaryResponse == null ? null : auxiliaryResponse.failureIntent, + callingPackage, + verificationBundle, + resolvedType, + userId, + auxiliaryResponse == null ? null : auxiliaryResponse.installFailureActivity, + auxiliaryResponse == null ? null : auxiliaryResponse.token, + auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo, + auxiliaryResponse == null ? null : auxiliaryResponse.filters); } void postStartActivityProcessing(ActivityRecord r, int result, ActivityStack targetStack) { @@ -924,12 +930,12 @@ class ActivityStarter { // Don't modify the client's object! intent = new Intent(intent); if (componentSpecified - && intent.getData() != null - && Intent.ACTION_VIEW.equals(intent.getAction()) + && !Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE.equals(intent.getAction()) + && !Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE.equals(intent.getAction()) && mService.getPackageManagerInternalLocked() .isInstantAppInstallerComponent(intent.getComponent())) { // intercept intents targeted directly to the ephemeral installer the - // ephemeral installer should never be started with a raw URL; instead + // ephemeral installer should never be started with a raw Intent; instead // adjust the intent so it looks like a "normal" instant app launch intent.setComponent(null /*component*/); componentSpecified = false; diff --git a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java index b5ddf8c511f..6d6c960eed7 100644 --- a/services/core/java/com/android/server/pm/EphemeralResolverConnection.java +++ b/services/core/java/com/android/server/pm/EphemeralResolverConnection.java @@ -43,6 +43,7 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.TimeoutException; @@ -84,8 +85,8 @@ final class EphemeralResolverConnection implements DeathRecipient { mIntent = new Intent(action).setComponent(componentName); } - public final List getInstantAppResolveInfoList(int hashPrefix[], - String token) throws ConnectionException { + public final List getInstantAppResolveInfoList(Intent sanitizedIntent, + int hashPrefix[], String token) throws ConnectionException { throwIfCalledOnMainThread(); IInstantAppResolver target = null; try { @@ -98,7 +99,7 @@ final class EphemeralResolverConnection implements DeathRecipient { } try { return mGetEphemeralResolveInfoCaller - .getEphemeralResolveInfoList(target, hashPrefix, token); + .getEphemeralResolveInfoList(target, sanitizedIntent, hashPrefix, token); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_CALL); } catch (RemoteException ignore) { @@ -111,26 +112,22 @@ final class EphemeralResolverConnection implements DeathRecipient { return null; } - public final void getInstantAppIntentFilterList(int hashPrefix[], String token, - String hostName, PhaseTwoCallback callback, Handler callbackHandler, - final long startTime) throws ConnectionException { + public final void getInstantAppIntentFilterList(Intent sanitizedIntent, int hashPrefix[], + String token, PhaseTwoCallback callback, Handler callbackHandler, final long startTime) + throws ConnectionException { final IRemoteCallback remoteCallback = new IRemoteCallback.Stub() { @Override public void sendResult(Bundle data) throws RemoteException { final ArrayList resolveList = data.getParcelableArrayList( InstantAppResolverService.EXTRA_RESOLVE_INFO); - callbackHandler.post(new Runnable() { - @Override - public void run() { - callback.onPhaseTwoResolved(resolveList, startTime); - } - }); + callbackHandler.post(() -> callback.onPhaseTwoResolved(resolveList, startTime)); } }; try { getRemoteInstanceLazy(token) - .getInstantAppIntentFilterList(hashPrefix, token, hostName, remoteCallback); + .getInstantAppIntentFilterList(sanitizedIntent, hashPrefix, token, + remoteCallback); } catch (TimeoutException e) { throw new ConnectionException(ConnectionException.FAILURE_BIND); } catch (InterruptedException e) { @@ -337,10 +334,11 @@ final class EphemeralResolverConnection implements DeathRecipient { } public List getEphemeralResolveInfoList( - IInstantAppResolver target, int hashPrefix[], String token) + IInstantAppResolver target, Intent sanitizedIntent, int hashPrefix[], String token) throws RemoteException, TimeoutException { final int sequence = onBeforeRemoteCall(); - target.getInstantAppResolveInfoList(hashPrefix, token, sequence, mCallback); + target.getInstantAppResolveInfoList(sanitizedIntent, hashPrefix, token, sequence, + mCallback); return getResultTimed(sequence); } } diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index 30072d45cca..55212cc6b3d 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -40,11 +40,14 @@ import android.content.pm.InstantAppIntentFilter; import android.content.pm.InstantAppResolveInfo; import android.content.pm.InstantAppResolveInfo.InstantAppDigest; import android.metrics.LogMaker; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; +import android.text.TextUtils; import android.util.Log; +import android.util.Slog; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; @@ -53,8 +56,12 @@ import com.android.server.pm.EphemeralResolverConnection.PhaseTwoCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.UUID; /** @hide */ @@ -79,6 +86,7 @@ public abstract class InstantAppResolver { public @interface ResolutionStatus {} private static MetricsLogger sMetricsLogger; + private static MetricsLogger getLogger() { if (sMetricsLogger == null) { sMetricsLogger = new MetricsLogger(); @@ -86,26 +94,49 @@ public abstract class InstantAppResolver { return sMetricsLogger; } - public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne(Context context, + /** + * Returns an intent with potential PII removed from the original intent. Fields removed + * include extras and the host + path of the data, if defined. + */ + public static Intent sanitizeIntent(Intent origIntent) { + final Intent sanitizedIntent; + sanitizedIntent = new Intent(origIntent.getAction()); + Set categories = origIntent.getCategories(); + if (categories != null) { + for (String category : categories) { + sanitizedIntent.addCategory(category); + } + } + Uri sanitizedUri = origIntent.getData() == null + ? null + : Uri.fromParts(origIntent.getScheme(), "", ""); + sanitizedIntent.setDataAndType(sanitizedUri, origIntent.getType()); + sanitizedIntent.addFlags(origIntent.getFlags()); + sanitizedIntent.setPackage(origIntent.getPackage()); + return sanitizedIntent; + } + + public static AuxiliaryResolveInfo doInstantAppResolutionPhaseOne( EphemeralResolverConnection connection, InstantAppRequest requestObj) { final long startTime = System.currentTimeMillis(); final String token = UUID.randomUUID().toString(); if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] Phase1; resolving"); } - final Intent intent = requestObj.origIntent; - final InstantAppDigest digest = - new InstantAppDigest(intent.getData().getHost(), 5 /*maxDigests*/); + final Intent origIntent = requestObj.origIntent; + final Intent sanitizedIntent = sanitizeIntent(origIntent); + + final InstantAppDigest digest = getInstantAppDigest(origIntent); final int[] shaPrefix = digest.getDigestPrefix(); AuxiliaryResolveInfo resolveInfo = null; @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS; try { final List instantAppResolveInfoList = - connection.getInstantAppResolveInfoList(shaPrefix, token); + connection.getInstantAppResolveInfoList(sanitizedIntent, shaPrefix, token); if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) { resolveInfo = InstantAppResolver.filterInstantAppIntent( - instantAppResolveInfoList, intent, requestObj.resolvedType, - requestObj.userId, intent.getPackage(), digest, token); + instantAppResolveInfoList, origIntent, requestObj.resolvedType, + requestObj.userId, origIntent.getPackage(), digest, token); } } catch (ConnectionException e) { if (e.failure == ConnectionException.FAILURE_BIND) { @@ -135,6 +166,12 @@ public abstract class InstantAppResolver { return resolveInfo; } + private static InstantAppDigest getInstantAppDigest(Intent origIntent) { + return origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost()) + ? new InstantAppDigest(origIntent.getData().getHost(), 5 /*maxDigests*/) + : InstantAppDigest.UNDEFINED; + } + public static void doInstantAppResolutionPhaseTwo(Context context, EphemeralResolverConnection connection, InstantAppRequest requestObj, ActivityInfo instantAppInstaller, Handler callbackHandler) { @@ -143,73 +180,53 @@ public abstract class InstantAppResolver { if (DEBUG_EPHEMERAL) { Log.d(TAG, "[" + token + "] Phase2; resolving"); } - final Intent intent = requestObj.origIntent; - final String hostName = intent.getData().getHost(); - final InstantAppDigest digest = new InstantAppDigest(hostName, 5 /*maxDigests*/); + final Intent origIntent = requestObj.origIntent; + final Intent sanitizedIntent = sanitizeIntent(origIntent); + final InstantAppDigest digest = getInstantAppDigest(origIntent); final int[] shaPrefix = digest.getDigestPrefix(); final PhaseTwoCallback callback = new PhaseTwoCallback() { @Override void onPhaseTwoResolved(List instantAppResolveInfoList, long startTime) { - final String packageName; - final String splitName; - final long versionCode; final Intent failureIntent; - final Bundle extras; if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) { final AuxiliaryResolveInfo instantAppIntentInfo = InstantAppResolver.filterInstantAppIntent( - instantAppResolveInfoList, intent, null /*resolvedType*/, - 0 /*userId*/, intent.getPackage(), digest, token); - if (instantAppIntentInfo != null - && instantAppIntentInfo.resolveInfo != null) { - packageName = instantAppIntentInfo.resolveInfo.getPackageName(); - splitName = instantAppIntentInfo.splitName; - versionCode = instantAppIntentInfo.resolveInfo.getVersionCode(); + instantAppResolveInfoList, origIntent, null /*resolvedType*/, + 0 /*userId*/, origIntent.getPackage(), digest, token); + if (instantAppIntentInfo != null) { failureIntent = instantAppIntentInfo.failureIntent; - extras = instantAppIntentInfo.resolveInfo.getExtras(); } else { - packageName = null; - splitName = null; - versionCode = -1; failureIntent = null; - extras = null; } } else { - packageName = null; - splitName = null; - versionCode = -1; failureIntent = null; - extras = null; } final Intent installerIntent = buildEphemeralInstallerIntent( - Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE, requestObj.origIntent, + sanitizedIntent, failureIntent, requestObj.callingPackage, requestObj.verificationBundle, requestObj.resolvedType, requestObj.userId, - packageName, - splitName, requestObj.responseObj.installFailureActivity, - versionCode, token, - extras, - false /*needsPhaseTwo*/); + false /*needsPhaseTwo*/, + requestObj.responseObj.filters); installerIntent.setComponent(new ComponentName( instantAppInstaller.packageName, instantAppInstaller.name)); logMetrics(ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO, startTime, token, - packageName != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE); + requestObj.responseObj.filters != null ? RESOLUTION_SUCCESS : RESOLUTION_FAILURE); context.startActivity(installerIntent); } }; try { - connection.getInstantAppIntentFilterList( - shaPrefix, token, hostName, callback, callbackHandler, startTime); + connection.getInstantAppIntentFilterList(sanitizedIntent, shaPrefix, token, callback, + callbackHandler, startTime); } catch (ConnectionException e) { @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE; if (e.failure == ConnectionException.FAILURE_BIND) { @@ -231,23 +248,20 @@ public abstract class InstantAppResolver { * Builds and returns an intent to launch the instant installer. */ public static Intent buildEphemeralInstallerIntent( - @NonNull String action, @NonNull Intent origIntent, - @NonNull Intent failureIntent, + @NonNull Intent sanitizedIntent, + @Nullable Intent failureIntent, @NonNull String callingPackage, @Nullable Bundle verificationBundle, @NonNull String resolvedType, int userId, - @NonNull String instantAppPackageName, - @Nullable String instantAppSplitName, @Nullable ComponentName installFailureActivity, - long versionCode, @Nullable String token, - @Nullable Bundle extras, - boolean needsPhaseTwo) { + boolean needsPhaseTwo, + List filters) { // Construct the intent that launches the instant installer int flags = origIntent.getFlags(); - final Intent intent = new Intent(action); + final Intent intent = new Intent(); intent.setFlags(flags | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK @@ -260,20 +274,23 @@ public abstract class InstantAppResolver { intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost()); } intent.putExtra(Intent.EXTRA_INSTANT_APP_ACTION, origIntent.getAction()); - if (extras != null) { - intent.putExtra(Intent.EXTRA_INSTANT_APP_EXTRAS, extras); - } + intent.putExtra(Intent.EXTRA_INTENT, sanitizedIntent); - // We have all of the data we need; just start the installer without a second phase - if (!needsPhaseTwo) { - // Intent that is launched if the package couldn't be installed for any reason. + if (needsPhaseTwo) { + intent.setAction(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE); + } else { + // We have all of the data we need; just start the installer without a second phase if (failureIntent != null || installFailureActivity != null) { + // Intent that is launched if the package couldn't be installed for any reason. try { final Intent onFailureIntent; if (installFailureActivity != null) { onFailureIntent = new Intent(); onFailureIntent.setComponent(installFailureActivity); - onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName); + if (filters != null && filters.size() == 1) { + onFailureIntent.putExtra(Intent.EXTRA_SPLIT_NAME, + filters.get(0).splitName); + } onFailureIntent.putExtra(Intent.EXTRA_INTENT, origIntent); } else { onFailureIntent = failureIntent; @@ -309,17 +326,35 @@ public abstract class InstantAppResolver { intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, new IntentSender(successIntentTarget)); } catch (RemoteException ignore) { /* ignore; same process */ } - - intent.putExtra(Intent.EXTRA_PACKAGE_NAME, instantAppPackageName); - intent.putExtra(Intent.EXTRA_SPLIT_NAME, instantAppSplitName); - intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) (versionCode & 0x7fffffff)); - intent.putExtra(Intent.EXTRA_LONG_VERSION_CODE, versionCode); - intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage); if (verificationBundle != null) { intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle); } - } + intent.putExtra(Intent.EXTRA_CALLING_PACKAGE, callingPackage); + if (filters != null) { + Bundle resolvableFilters[] = new Bundle[filters.size()]; + for (int i = 0, max = filters.size(); i < max; i++) { + Bundle resolvableFilter = new Bundle(); + AuxiliaryResolveInfo.AuxiliaryFilter filter = filters.get(i); + resolvableFilter.putBoolean(Intent.EXTRA_UNKNOWN_INSTANT_APP, + filter.resolveInfo != null + && filter.resolveInfo.shouldLetInstallerDecide()); + resolvableFilter.putString(Intent.EXTRA_PACKAGE_NAME, filter.packageName); + resolvableFilter.putString(Intent.EXTRA_SPLIT_NAME, filter.splitName); + resolvableFilter.putLong(Intent.EXTRA_LONG_VERSION_CODE, filter.versionCode); + resolvableFilter.putBundle(Intent.EXTRA_INSTANT_APP_EXTRAS, filter.extras); + resolvableFilters[i] = resolvableFilter; + if (i == 0) { + // for backwards compat, always set the first result on the intent and add + // the int version code + intent.putExtras(resolvableFilter); + intent.putExtra(Intent.EXTRA_VERSION_CODE, (int) filter.versionCode); + } + } + intent.putExtra(Intent.EXTRA_INSTANT_APP_BUNDLES, resolvableFilters); + } + intent.setAction(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE); + } return intent; } @@ -330,69 +365,134 @@ public abstract class InstantAppResolver { final int[] shaPrefix = digest.getDigestPrefix(); final byte[][] digestBytes = digest.getDigestBytes(); final Intent failureIntent = new Intent(origIntent); + boolean requiresSecondPhase = false; failureIntent.setFlags(failureIntent.getFlags() | Intent.FLAG_IGNORE_EPHEMERAL); failureIntent.setLaunchToken(token); - // Go in reverse order so we match the narrowest scope first. - for (int i = shaPrefix.length - 1; i >= 0 ; --i) { - for (InstantAppResolveInfo instantAppInfo : instantAppResolveInfoList) { - if (!Arrays.equals(digestBytes[i], instantAppInfo.getDigestBytes())) { - continue; + ArrayList filters = null; + boolean isWebIntent = origIntent.isBrowsableWebIntent(); + for (InstantAppResolveInfo instantAppResolveInfo : instantAppResolveInfoList) { + if (shaPrefix.length > 0 && instantAppResolveInfo.shouldLetInstallerDecide()) { + Slog.e(TAG, "InstantAppResolveInfo with mShouldLetInstallerDecide=true when digest" + + " provided; ignoring"); + continue; + } + byte[] filterDigestBytes = instantAppResolveInfo.getDigestBytes(); + // Only include matching digests if we have a prefix and we're either dealing with a + // web intent or the resolveInfo specifies digest details. + if (shaPrefix.length > 0 && (isWebIntent || filterDigestBytes.length > 0)) { + boolean matchFound = false; + // Go in reverse order so we match the narrowest scope first. + for (int i = shaPrefix.length - 1; i >= 0; --i) { + if (Arrays.equals(digestBytes[i], filterDigestBytes)) { + matchFound = true; + break; + } } - if (packageName != null - && !packageName.equals(instantAppInfo.getPackageName())) { + if (!matchFound) { continue; } - final List instantAppFilters = - instantAppInfo.getIntentFilters(); - // No filters; we need to start phase two - if (instantAppFilters == null || instantAppFilters.isEmpty()) { - if (DEBUG_EPHEMERAL) { - Log.d(TAG, "No app filters; go to phase 2"); - } - return new AuxiliaryResolveInfo(instantAppInfo, - new IntentFilter(Intent.ACTION_VIEW) /*intentFilter*/, - null /*splitName*/, token, true /*needsPhase2*/, - null /*failureIntent*/); - } - // We have a domain match; resolve the filters to see if anything matches. - final PackageManagerService.EphemeralIntentResolver instantAppResolver = - new PackageManagerService.EphemeralIntentResolver(); - for (int j = instantAppFilters.size() - 1; j >= 0; --j) { - final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); - final List splitFilters = instantAppFilter.getFilters(); - if (splitFilters == null || splitFilters.isEmpty()) { - continue; - } - for (int k = splitFilters.size() - 1; k >= 0; --k) { - final AuxiliaryResolveInfo intentInfo = - new AuxiliaryResolveInfo(instantAppInfo, - splitFilters.get(k), instantAppFilter.getSplitName(), - token, false /*needsPhase2*/, failureIntent); - instantAppResolver.addFilter(intentInfo); - } + } + // We matched a resolve info; resolve the filters to see if anything matches completely. + List matchFilters = computeResolveFilters( + origIntent, resolvedType, userId, packageName, token, instantAppResolveInfo); + if (matchFilters != null) { + if (matchFilters.isEmpty()) { + requiresSecondPhase = true; } - List matchedResolveInfoList = instantAppResolver.queryIntent( - origIntent, resolvedType, false /*defaultOnly*/, userId); - if (!matchedResolveInfoList.isEmpty()) { - if (DEBUG_EPHEMERAL) { - final AuxiliaryResolveInfo info = matchedResolveInfoList.get(0); - Log.d(TAG, "[" + token + "] Found match;" - + " package: " + info.packageName - + ", split: " + info.splitName - + ", versionCode: " + info.versionCode); - } - return matchedResolveInfoList.get(0); - } else if (DEBUG_EPHEMERAL) { - Log.d(TAG, "[" + token + "] No matches found" - + " package: " + instantAppInfo.getPackageName() - + ", versionCode: " + instantAppInfo.getVersionCode()); + if (filters == null) { + filters = new ArrayList<>(matchFilters); + } else { + filters.addAll(matchFilters); } } } + if (filters != null && !filters.isEmpty()) { + return new AuxiliaryResolveInfo(token, requiresSecondPhase, failureIntent, filters); + } // Hash or filter mis-match; no instant apps for this domain. return null; } + /** + * Returns one of three states:

    + *

      + *
    • {@code null} if there are no matches will not be; resolution is unnecessary.
    • + *
    • An empty list signifying that a 2nd phase of resolution is required.
    • + *
    • A populated list meaning that matches were found and should be sent directly to the + * installer
    • + *
    + * + */ + private static List computeResolveFilters( + Intent origIntent, String resolvedType, int userId, String packageName, String token, + InstantAppResolveInfo instantAppInfo) { + if (instantAppInfo.shouldLetInstallerDecide()) { + return Collections.singletonList( + new AuxiliaryResolveInfo.AuxiliaryFilter( + instantAppInfo, null /* splitName */, + instantAppInfo.getExtras())); + } + if (packageName != null + && !packageName.equals(instantAppInfo.getPackageName())) { + return null; + } + final List instantAppFilters = + instantAppInfo.getIntentFilters(); + if (instantAppFilters == null || instantAppFilters.isEmpty()) { + // No filters on web intent; no matches, 2nd phase unnecessary. + if (origIntent.isBrowsableWebIntent()) { + return null; + } + // No filters; we need to start phase two + if (DEBUG_EPHEMERAL) { + Log.d(TAG, "No app filters; go to phase 2"); + } + return Collections.emptyList(); + } + final PackageManagerService.EphemeralIntentResolver instantAppResolver = + new PackageManagerService.EphemeralIntentResolver(); + for (int j = instantAppFilters.size() - 1; j >= 0; --j) { + final InstantAppIntentFilter instantAppFilter = instantAppFilters.get(j); + final List splitFilters = instantAppFilter.getFilters(); + if (splitFilters == null || splitFilters.isEmpty()) { + continue; + } + for (int k = splitFilters.size() - 1; k >= 0; --k) { + IntentFilter filter = splitFilters.get(k); + Iterator authorities = + filter.authoritiesIterator(); + // ignore http/s-only filters. + if ((authorities == null || !authorities.hasNext()) + && (filter.hasDataScheme("http") || filter.hasDataScheme("https")) + && filter.hasAction(Intent.ACTION_VIEW) + && filter.hasCategory(Intent.CATEGORY_BROWSABLE)) { + continue; + } + instantAppResolver.addFilter( + new AuxiliaryResolveInfo.AuxiliaryFilter( + filter, + instantAppInfo, + instantAppFilter.getSplitName(), + instantAppInfo.getExtras() + )); + } + } + List matchedResolveInfoList = + instantAppResolver.queryIntent( + origIntent, resolvedType, false /*defaultOnly*/, userId); + if (!matchedResolveInfoList.isEmpty()) { + if (DEBUG_EPHEMERAL) { + Log.d(TAG, "[" + token + "] Found match(es); " + matchedResolveInfoList); + } + return matchedResolveInfoList; + } else if (DEBUG_EPHEMERAL) { + Log.d(TAG, "[" + token + "] No matches found" + + " package: " + instantAppInfo.getPackageName() + + ", versionCode: " + instantAppInfo.getVersionCode()); + } + return null; + } + private static void logMetrics(int action, long startTime, String token, @ResolutionStatus int status) { final LogMaker logMaker = new LogMaker(action) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 01c44af586e..be988f2eb6f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -582,6 +582,7 @@ public class PackageManagerService extends IPackageManager.Stub sBrowserIntent.setAction(Intent.ACTION_VIEW); sBrowserIntent.addCategory(Intent.CATEGORY_BROWSABLE); sBrowserIntent.setData(Uri.parse("http:")); + sBrowserIntent.addFlags(Intent.FLAG_IGNORE_EPHEMERAL); } /** @@ -3594,24 +3595,35 @@ Slog.e("TODD", } private @Nullable ActivityInfo getInstantAppInstallerLPr() { - final Intent intent = new Intent(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE); + String[] orderedActions = Build.IS_ENG + ? new String[]{ + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE + "_TEST", + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, + Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE} + : new String[]{ + Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE, + Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE}; final int resolveFlags = MATCH_DIRECT_BOOT_AWARE - | MATCH_DIRECT_BOOT_UNAWARE - | (!Build.IS_DEBUGGABLE ? MATCH_SYSTEM_ONLY : 0); - List matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, - resolveFlags, UserHandle.USER_SYSTEM); - // temporarily look for the old action - if (matches.isEmpty()) { - if (DEBUG_EPHEMERAL) { - Slog.d(TAG, "Ephemeral installer not found with new action; try old one"); - } - intent.setAction(Intent.ACTION_INSTALL_EPHEMERAL_PACKAGE); + | MATCH_DIRECT_BOOT_UNAWARE + | Intent.FLAG_IGNORE_EPHEMERAL + | (!Build.IS_ENG ? MATCH_SYSTEM_ONLY : 0); + final Intent intent = new Intent(); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setDataAndType(Uri.fromFile(new File("foo.apk")), PACKAGE_MIME_TYPE); + List matches = null; + for (String action : orderedActions) { + intent.setAction(action); matches = queryIntentActivitiesInternal(intent, PACKAGE_MIME_TYPE, resolveFlags, UserHandle.USER_SYSTEM); + if (matches.isEmpty()) { + if (DEBUG_EPHEMERAL) { + Slog.d(TAG, "Instant App installer not found with " + action); + } + } else { + break; + } } Iterator iter = matches.iterator(); while (iter.hasNext()) { @@ -3619,7 +3631,8 @@ Slog.e("TODD", final PackageSetting ps = mSettings.mPackages.get(rInfo.activityInfo.packageName); if (ps != null) { final PermissionsState permissionsState = ps.getPermissionsState(); - if (permissionsState.hasPermission(Manifest.permission.INSTALL_PACKAGES, 0)) { + if (permissionsState.hasPermission(Manifest.permission.INSTALL_PACKAGES, 0) + || Build.IS_ENG) { continue; } } @@ -4778,10 +4791,7 @@ Slog.e("TODD", flags |= PackageManager.MATCH_INSTANT; } else { final boolean wantMatchInstant = (flags & PackageManager.MATCH_INSTANT) != 0; - final boolean allowMatchInstant = - (wantInstantApps - && Intent.ACTION_VIEW.equals(intent.getAction()) - && hasWebURI(intent)) + final boolean allowMatchInstant = wantInstantApps || (wantMatchInstant && canViewInstantApps(callingUid, userId)); flags &= ~(PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY | PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY); @@ -5971,8 +5981,14 @@ Slog.e("TODD", if (!skipPackageCheck && intent.getPackage() != null) { return false; } - final boolean isWebUri = hasWebURI(intent); - if (!isWebUri || intent.getData().getHost() == null) { + if (!intent.isBrowsableWebIntent()) { + // for non web intents, we should not resolve externally if an app already exists to + // handle it or if the caller didn't explicitly request it. + if ((resolvedActivities != null && resolvedActivities.size() != 0) + || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) == 0) { + return false; + } + } else if (intent.getData() == null || TextUtils.isEmpty(intent.getData().getHost())) { return false; } // Deny ephemeral apps if the user chose _ALWAYS or _ALWAYS_ASK for intent resolution. @@ -6370,7 +6386,7 @@ Slog.e("TODD", if (matches.get(i).getTargetUserId() == targetUserId) return true; } } - if (hasWebURI(intent)) { + if (intent.hasWebURI()) { // cross-profile app linking works only towards the parent. final int callingUid = Binder.getCallingUid(); final UserInfo parent = getProfileParent(sourceUserId); @@ -6545,7 +6561,7 @@ Slog.e("TODD", sortResult = true; } } - if (hasWebURI(intent)) { + if (intent.hasWebURI()) { CrossProfileDomainInfo xpDomainInfo = null; final UserInfo parent = getProfileParent(userId); if (parent != null) { @@ -6631,7 +6647,6 @@ Slog.e("TODD", if (ps.getInstantApp(userId)) { final long packedStatus = getDomainVerificationStatusLPr(ps, userId); final int status = (int)(packedStatus >> 32); - final int linkGeneration = (int)(packedStatus & 0xFFFFFFFF); if (status == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) { // there's a local instant application installed, but, the user has // chosen to never use it; skip resolution and don't acknowledge @@ -6663,9 +6678,8 @@ Slog.e("TODD", null /*responseObj*/, intent /*origIntent*/, resolvedType, null /*callingPackage*/, userId, null /*verificationBundle*/, resolveForStart); - auxiliaryResponse = - InstantAppResolver.doInstantAppResolutionPhaseOne( - mContext, mInstantAppResolverConnection, requestObject); + auxiliaryResponse = InstantAppResolver.doInstantAppResolutionPhaseOne( + mInstantAppResolverConnection, requestObject); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } else { // we have an instant application locally, but, we can't admit that since @@ -6674,35 +6688,40 @@ Slog.e("TODD", // instant application available externally. when it comes time to start // the instant application, we'll do the right thing. final ApplicationInfo ai = localInstantApp.activityInfo.applicationInfo; - auxiliaryResponse = new AuxiliaryResolveInfo( - ai.packageName, null /*splitName*/, null /*failureActivity*/, - ai.versionCode, null /*failureIntent*/); + auxiliaryResponse = new AuxiliaryResolveInfo(null /* failureActivity */, + ai.packageName, ai.versionCode, null /* splitName */); } } - if (auxiliaryResponse != null) { - if (DEBUG_EPHEMERAL) { - Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); - } - final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo); - final PackageSetting ps = - mSettings.mPackages.get(mInstantAppInstallerActivity.packageName); - if (ps != null) { - ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo( - mInstantAppInstallerActivity, 0, ps.readUserState(userId), userId); - ephemeralInstaller.activityInfo.launchToken = auxiliaryResponse.token; - ephemeralInstaller.auxiliaryInfo = auxiliaryResponse; - // make sure this resolver is the default - ephemeralInstaller.isDefault = true; - ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART - | IntentFilter.MATCH_ADJUSTMENT_NORMAL; - // add a non-generic filter - ephemeralInstaller.filter = new IntentFilter(intent.getAction()); - ephemeralInstaller.filter.addDataPath( - intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL); - ephemeralInstaller.isInstantAppAvailable = true; - result.add(ephemeralInstaller); - } + if (intent.isBrowsableWebIntent() && auxiliaryResponse == null) { + return result; + } + final PackageSetting ps = mSettings.mPackages.get(mInstantAppInstallerActivity.packageName); + if (ps == null) { + return result; + } + final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo); + ephemeralInstaller.activityInfo = PackageParser.generateActivityInfo( + mInstantAppInstallerActivity, 0, ps.readUserState(userId), userId); + ephemeralInstaller.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART + | IntentFilter.MATCH_ADJUSTMENT_NORMAL; + // add a non-generic filter + ephemeralInstaller.filter = new IntentFilter(); + if (intent.getAction() != null) { + ephemeralInstaller.filter.addAction(intent.getAction()); + } + if (intent.getData() != null && intent.getData().getPath() != null) { + ephemeralInstaller.filter.addDataPath( + intent.getData().getPath(), PatternMatcher.PATTERN_LITERAL); + } + ephemeralInstaller.isInstantAppAvailable = true; + // make sure this resolver is the default + ephemeralInstaller.isDefault = true; + ephemeralInstaller.auxiliaryInfo = auxiliaryResponse; + if (DEBUG_EPHEMERAL) { + Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } + + result.add(ephemeralInstaller); return result; } @@ -6817,10 +6836,11 @@ Slog.e("TODD", final ResolveInfo info = resolveInfos.get(i); // allow activities that are defined in the provided package if (allowDynamicSplits + && info.activityInfo != null && info.activityInfo.splitName != null && !ArrayUtils.contains(info.activityInfo.applicationInfo.splitNames, info.activityInfo.splitName)) { - if (mInstantAppInstallerInfo == null) { + if (mInstantAppInstallerActivity == null) { if (DEBUG_INSTALL) { Slog.v(TAG, "No installer - not adding it to the ResolveInfo list"); } @@ -6832,14 +6852,15 @@ Slog.e("TODD", if (DEBUG_INSTALL) { Slog.v(TAG, "Adding installer to the ResolveInfo list"); } - final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo); + final ResolveInfo installerInfo = new ResolveInfo( + mInstantAppInstallerInfo); final ComponentName installFailureActivity = findInstallFailureActivity( info.activityInfo.packageName, filterCallingUid, userId); installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( - info.activityInfo.packageName, info.activityInfo.splitName, installFailureActivity, + info.activityInfo.packageName, info.activityInfo.applicationInfo.versionCode, - null /*failureIntent*/); + info.activityInfo.splitName); installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART | IntentFilter.MATCH_ADJUSTMENT_NORMAL; // add a non-generic filter @@ -6856,6 +6877,7 @@ Slog.e("TODD", installerInfo.priority = info.priority; installerInfo.preferredOrder = info.preferredOrder; installerInfo.isDefault = info.isDefault; + installerInfo.isInstantAppAvailable = true; resolveInfos.set(i, installerInfo); continue; } @@ -6913,17 +6935,6 @@ Slog.e("TODD", return resolveInfos.size() > 0 && resolveInfos.get(0).priority >= 0; } - private static boolean hasWebURI(Intent intent) { - if (intent.getData() == null) { - return false; - } - final String scheme = intent.getScheme(); - if (TextUtils.isEmpty(scheme)) { - return false; - } - return scheme.equals(IntentFilter.SCHEME_HTTP) || scheme.equals(IntentFilter.SCHEME_HTTPS); - } - private List filterCandidatesWithDomainPreferredActivitiesLPr(Intent intent, int matchFlags, List candidates, CrossProfileDomainInfo xpDomainInfo, int userId) { @@ -7596,11 +7607,13 @@ Slog.e("TODD", if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } - final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo); + final ResolveInfo installerInfo = new ResolveInfo( + mInstantAppInstallerInfo); installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( - info.serviceInfo.packageName, info.serviceInfo.splitName, - null /*failureActivity*/, info.serviceInfo.applicationInfo.versionCode, - null /*failureIntent*/); + null /* installFailureActivity */, + info.serviceInfo.packageName, + info.serviceInfo.applicationInfo.versionCode, + info.serviceInfo.splitName); // make sure this resolver is the default installerInfo.isDefault = true; installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART @@ -7716,11 +7729,13 @@ Slog.e("TODD", if (DEBUG_EPHEMERAL) { Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list"); } - final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo); + final ResolveInfo installerInfo = new ResolveInfo( + mInstantAppInstallerInfo); installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo( - info.providerInfo.packageName, info.providerInfo.splitName, - null /*failureActivity*/, info.providerInfo.applicationInfo.versionCode, - null /*failureIntent*/); + null /*failureActivity*/, + info.providerInfo.packageName, + info.providerInfo.applicationInfo.versionCode, + info.providerInfo.splitName); // make sure this resolver is the default installerInfo.isDefault = true; installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART @@ -11794,7 +11809,7 @@ Slog.e("TODD", mInstantAppInstallerActivity.exported = true; mInstantAppInstallerActivity.enabled = true; mInstantAppInstallerInfo.activityInfo = mInstantAppInstallerActivity; - mInstantAppInstallerInfo.priority = 0; + mInstantAppInstallerInfo.priority = 1; mInstantAppInstallerInfo.preferredOrder = 1; mInstantAppInstallerInfo.isDefault = true; mInstantAppInstallerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART @@ -13244,7 +13259,8 @@ Slog.e("TODD", } static final class EphemeralIntentResolver - extends IntentResolver { + extends IntentResolver { /** * The result that has the highest defined order. Ordering applies on a * per-package basis. Mapping is from package name to Pair of order and @@ -13259,18 +13275,19 @@ Slog.e("TODD", final ArrayMap> mOrderResult = new ArrayMap<>(); @Override - protected AuxiliaryResolveInfo[] newArray(int size) { - return new AuxiliaryResolveInfo[size]; + protected AuxiliaryResolveInfo.AuxiliaryFilter[] newArray(int size) { + return new AuxiliaryResolveInfo.AuxiliaryFilter[size]; } @Override - protected boolean isPackageForFilter(String packageName, AuxiliaryResolveInfo responseObj) { + protected boolean isPackageForFilter(String packageName, + AuxiliaryResolveInfo.AuxiliaryFilter responseObj) { return true; } @Override - protected AuxiliaryResolveInfo newResult(AuxiliaryResolveInfo responseObj, int match, - int userId) { + protected AuxiliaryResolveInfo.AuxiliaryFilter newResult( + AuxiliaryResolveInfo.AuxiliaryFilter responseObj, int match, int userId) { if (!sUserManager.exists(userId)) { return null; } @@ -13291,7 +13308,7 @@ Slog.e("TODD", } @Override - protected void filterResults(List results) { + protected void filterResults(List results) { // only do work if ordering is enabled [most of the time it won't be] if (mOrderResult.size() == 0) { return; -- GitLab From 159cd024d996047ac8caf1ae941d5be80047dedf Mon Sep 17 00:00:00 2001 From: Patrick Baumann Date: Thu, 11 Jan 2018 13:25:05 -0800 Subject: [PATCH 205/416] Marking used instant apps fields as System API Change-Id: I4a907600b9fa75b1789843a9c2e7d2c33aaaff6b Fixes: 71852699 Bug: 72450666 Bug: 72700831 Test: builds and AIA still functional --- api/system-current.txt | 11 ++++ core/java/android/content/Intent.java | 51 ++++++++++++++++++- .../android/content/pm/ApplicationInfo.java | 7 ++- .../android/server/pm/InstantAppResolver.java | 16 ++++-- 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index 08ac295c936..88ee1c0adab 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -822,8 +822,16 @@ package android.content { field public static final java.lang.String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED"; field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST"; field public static final java.lang.String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS"; + field public static final java.lang.String EXTRA_CALLING_PACKAGE = "android.intent.extra.CALLING_PACKAGE"; field public static final java.lang.String EXTRA_FORCE_FACTORY_RESET = "android.intent.extra.FORCE_FACTORY_RESET"; + field public static final java.lang.String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION"; field public static final java.lang.String EXTRA_INSTANT_APP_BUNDLES = "android.intent.extra.INSTANT_APP_BUNDLES"; + field public static final java.lang.String EXTRA_INSTANT_APP_EXTRAS = "android.intent.extra.INSTANT_APP_EXTRAS"; + field public static final java.lang.String EXTRA_INSTANT_APP_FAILURE = "android.intent.extra.INSTANT_APP_FAILURE"; + field public static final java.lang.String EXTRA_INSTANT_APP_HOSTNAME = "android.intent.extra.INSTANT_APP_HOSTNAME"; + field public static final java.lang.String EXTRA_INSTANT_APP_SUCCESS = "android.intent.extra.INSTANT_APP_SUCCESS"; + field public static final java.lang.String EXTRA_INSTANT_APP_TOKEN = "android.intent.extra.INSTANT_APP_TOKEN"; + field public static final java.lang.String EXTRA_LONG_VERSION_CODE = "android.intent.extra.LONG_VERSION_CODE"; field public static final java.lang.String EXTRA_ORIGINATING_UID = "android.intent.extra.ORIGINATING_UID"; field public static final java.lang.String EXTRA_PACKAGES = "android.intent.extra.PACKAGES"; field public static final java.lang.String EXTRA_PERMISSION_NAME = "android.intent.extra.PERMISSION_NAME"; @@ -831,6 +839,7 @@ package android.content { field public static final java.lang.String EXTRA_REMOTE_CALLBACK = "android.intent.extra.REMOTE_CALLBACK"; field public static final java.lang.String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED"; field public static final java.lang.String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP"; + field public static final java.lang.String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE"; } public class IntentFilter implements android.os.Parcelable { @@ -843,7 +852,9 @@ package android.content { package android.content.pm { public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { + method public boolean isInstantApp(); field public java.lang.String credentialProtectedDataDir; + field public int targetSandboxVersion; } public final class InstantAppInfo implements android.os.Parcelable { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 908465efa0c..9b62f192ae6 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4414,32 +4414,77 @@ public class Intent implements Parcelable, Cloneable { /** * A {@link IntentSender} to start after ephemeral installation success. + * @deprecated Use {@link #EXTRA_INSTANT_APP_SUCCESS). + * @removed * @hide */ + @Deprecated public static final String EXTRA_EPHEMERAL_SUCCESS = "android.intent.extra.EPHEMERAL_SUCCESS"; + /** + * A {@link IntentSender} to start after instant app installation success. + * @hide + */ + @SystemApi + public static final String EXTRA_INSTANT_APP_SUCCESS = + "android.intent.extra.INSTANT_APP_SUCCESS"; + /** * A {@link IntentSender} to start after ephemeral installation failure. + * @deprecated Use {@link #EXTRA_INSTANT_APP_FAILURE). + * @removed * @hide */ + @Deprecated public static final String EXTRA_EPHEMERAL_FAILURE = "android.intent.extra.EPHEMERAL_FAILURE"; + /** + * A {@link IntentSender} to start after instant app installation failure. + * @hide + */ + @SystemApi + public static final String EXTRA_INSTANT_APP_FAILURE = + "android.intent.extra.INSTANT_APP_FAILURE"; + /** * The host name that triggered an ephemeral resolution. + * @deprecated Use {@link #EXTRA_INSTANT_APP_HOSTNAME). + * @removed * @hide */ + @Deprecated public static final String EXTRA_EPHEMERAL_HOSTNAME = "android.intent.extra.EPHEMERAL_HOSTNAME"; + /** + * The host name that triggered an instant app resolution. + * @hide + */ + @SystemApi + public static final String EXTRA_INSTANT_APP_HOSTNAME = + "android.intent.extra.INSTANT_APP_HOSTNAME"; + /** * An opaque token to track ephemeral resolution. + * @deprecated Use {@link #EXTRA_INSTANT_APP_TOKEN). + * @removed * @hide */ + @Deprecated public static final String EXTRA_EPHEMERAL_TOKEN = "android.intent.extra.EPHEMERAL_TOKEN"; + /** + * An opaque token to track instant app resolution. + * @hide + */ + @SystemApi + public static final String EXTRA_INSTANT_APP_TOKEN = + "android.intent.extra.INSTANT_APP_TOKEN"; + /** * The action that triggered an instant application resolution. * @hide */ + @SystemApi public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION"; /** @@ -4457,6 +4502,7 @@ public class Intent implements Parcelable, Cloneable { * instant application resolver. * @hide */ + @SystemApi public static final String EXTRA_INSTANT_APP_EXTRAS = "android.intent.extra.INSTANT_APP_EXTRAS"; @@ -4482,12 +4528,14 @@ public class Intent implements Parcelable, Cloneable { * The version code of the app to install components from. * @hide */ + @SystemApi public static final String EXTRA_LONG_VERSION_CODE = "android.intent.extra.LONG_VERSION_CODE"; /** - * The app that triggered the ephemeral installation. + * The app that triggered the instant app installation. * @hide */ + @SystemApi public static final String EXTRA_CALLING_PACKAGE = "android.intent.extra.CALLING_PACKAGE"; @@ -4496,6 +4544,7 @@ public class Intent implements Parcelable, Cloneable { * installer may use. * @hide */ + @SystemApi public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE"; diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index f6697e8148a..db1630b67bd 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -958,6 +958,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * Version of the sandbox the application wants to run in. * @hide */ + @SystemApi public int targetSandboxVersion; /** @@ -1655,7 +1656,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_FORWARD_LOCK) != 0; } - /** @hide */ + /** + * True if the application is installed as an instant app. + * @hide + */ + @SystemApi public boolean isInstantApp() { return (privateFlags & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0; } diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java index 00fdb9d687d..af446ba0291 100644 --- a/services/core/java/com/android/server/pm/InstantAppResolver.java +++ b/services/core/java/com/android/server/pm/InstantAppResolver.java @@ -268,10 +268,14 @@ public abstract class InstantAppResolver { | Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); if (token != null) { + // TODO(b/72700831): remove populating old extra intent.putExtra(Intent.EXTRA_EPHEMERAL_TOKEN, token); + intent.putExtra(Intent.EXTRA_INSTANT_APP_TOKEN, token); } if (origIntent.getData() != null) { + // TODO(b/72700831): remove populating old extra intent.putExtra(Intent.EXTRA_EPHEMERAL_HOSTNAME, origIntent.getData().getHost()); + intent.putExtra(Intent.EXTRA_INSTANT_APP_HOSTNAME, origIntent.getData().getHost()); } intent.putExtra(Intent.EXTRA_INSTANT_APP_ACTION, origIntent.getAction()); intent.putExtra(Intent.EXTRA_INTENT, sanitizedIntent); @@ -305,8 +309,10 @@ public abstract class InstantAppResolver { | PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE, null /*bOptions*/, userId); - intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, - new IntentSender(failureIntentTarget)); + IntentSender failureSender = new IntentSender(failureIntentTarget); + // TODO(b/72700831): remove populating old extra + intent.putExtra(Intent.EXTRA_EPHEMERAL_FAILURE, failureSender); + intent.putExtra(Intent.EXTRA_INSTANT_APP_FAILURE, failureSender); } catch (RemoteException ignore) { /* ignore; same process */ } } @@ -323,8 +329,10 @@ public abstract class InstantAppResolver { PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE, null /*bOptions*/, userId); - intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, - new IntentSender(successIntentTarget)); + IntentSender successSender = new IntentSender(successIntentTarget); + // TODO(b/72700831): remove populating old extra + intent.putExtra(Intent.EXTRA_EPHEMERAL_SUCCESS, successSender); + intent.putExtra(Intent.EXTRA_INSTANT_APP_SUCCESS, successSender); } catch (RemoteException ignore) { /* ignore; same process */ } if (verificationBundle != null) { intent.putExtra(Intent.EXTRA_VERIFICATION_BUNDLE, verificationBundle); -- GitLab From 7c9f00fa7113ee6ab365e75c9c57fd92b4a89bc1 Mon Sep 17 00:00:00 2001 From: Alan Viverette Date: Tue, 30 Jan 2018 15:15:18 -0500 Subject: [PATCH 206/416] Move to "official" app toolkit Make target Test: make Change-Id: I0830bc8c3fed38097ef69a6f42c8c1987c8aac8e --- packages/SettingsLib/common.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk index 3c2ca2d3188..741db344169 100644 --- a/packages/SettingsLib/common.mk +++ b/packages/SettingsLib/common.mk @@ -16,11 +16,11 @@ ifeq ($(LOCAL_USE_AAPT2),true) LOCAL_STATIC_JAVA_LIBRARIES += \ android-support-annotations \ - apptoolkit-lifecycle-common + android-arch-lifecycle-common LOCAL_STATIC_ANDROID_LIBRARIES += \ android-support-v4 \ - apptoolkit-lifecycle-runtime \ + android-arch-lifecycle-runtime \ android-support-v7-recyclerview \ android-support-v7-preference \ android-support-v7-appcompat \ -- GitLab From a68a28640fc700624369e07582033b0f5a71572c Mon Sep 17 00:00:00 2001 From: Fyodor Kupolov Date: Tue, 30 Jan 2018 18:22:00 -0800 Subject: [PATCH 207/416] Do not call setQuietModeEnabled from the main thread Call into startUserInBackgroundWithListener->onBeforeStartUser sometimes can be blocked for a long time if there is an ongoing dexopt operation. Instead call it from bg thread. Test: manual Bug: 72692931 Change-Id: I872c3a2baf6e5ff95aab186f7a8008afec7533d6 --- .../core/java/com/android/server/pm/UserManagerService.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 92fd9041b32..b53d83b1291 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -88,6 +88,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsService; import com.android.internal.logging.MetricsLogger; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; @@ -387,7 +388,9 @@ public class UserManagerService extends IUserManager.Stub { } final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT); final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL); - setQuietModeEnabled(userHandle, false, target); + // Call setQuietModeEnabled on bg thread to avoid ANR + BackgroundThread.getHandler() + .post(() -> setQuietModeEnabled(userHandle, false, target)); } }; -- GitLab From acf322db1d13ee95529eecef2c915d0426b18346 Mon Sep 17 00:00:00 2001 From: Amin Shaikh Date: Wed, 31 Jan 2018 17:04:56 -0500 Subject: [PATCH 208/416] Add divider between QS and footer. Bug: 70799330 Test: visual Change-Id: I7f20771b0a8ba9c5fef0f48dc01ab3fb58515987 --- packages/SystemUI/res/layout/qs_footer_impl.xml | 14 ++++++++++---- .../src/com/android/systemui/qs/QSFooterImpl.java | 5 +++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index 997fe6dd44b..100c2aa51b4 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -25,16 +25,22 @@ android:baselineAligned="false" android:clickable="false" android:clipChildren="false" - android:clipToPadding="false" - android:paddingTop="0dp" - android:gravity="center_vertical" - android:orientation="horizontal"> + android:clipToPadding="false"> + + Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> @@ -162,6 +162,7 @@ public class QSFooterImpl extends FrameLayout implements QSFooter, @Nullable private TouchAnimator createSettingsAlphaAnimator() { return new TouchAnimator.Builder() + .addFloat(mDivider, "alpha", 0, 1) .addFloat(mCarrierText, "alpha", 0, 1) .addFloat(mActionsContainer, "alpha", 0, 1) .build(); -- GitLab From 304f4b5509bc3b8fb1606df0bc95f7da205ed5af Mon Sep 17 00:00:00 2001 From: Mohamed Abdalkader Date: Tue, 23 Jan 2018 13:09:00 -0800 Subject: [PATCH 209/416] Add and trigger onReady API for SMS over IMS. Test: manual test that normal code path is fine since this code path is not yet exercisable. BUG=69846044 Merged-In: Icb15ca4aa6606fba641f6270dca5e0e06fc4466a Change-Id: Icb15ca4aa6606fba641f6270dca5e0e06fc4466a --- api/system-current.txt | 1 + .../android/telephony/ims/feature/MMTelFeature.java | 11 +++++++++++ .../telephony/ims/internal/stub/SmsImplBase.java | 13 +++++++++++++ .../com/android/ims/internal/IImsMMTelFeature.aidl | 1 + 4 files changed, 26 insertions(+) diff --git a/api/system-current.txt b/api/system-current.txt index 1d01bd11db0..0ac628e9833 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4312,6 +4312,7 @@ package android.telephony.ims.internal.stub { method public void acknowledgeSms(int, int, int); method public void acknowledgeSmsReport(int, int, int); method public java.lang.String getSmsFormat(); + method public void onReady(); method public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException; method public final void onSmsReceived(int, java.lang.String, byte[]) throws java.lang.RuntimeException; method public final void onSmsStatusReportReceived(int, int, java.lang.String, byte[]) throws java.lang.RuntimeException; diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java index 7a406ade9bf..93c316f3dcb 100644 --- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java @@ -209,6 +209,13 @@ public class MMTelFeature extends ImsFeature { return MMTelFeature.this.getSmsFormat(); } } + + @Override + public void onSmsReady() { + synchronized (mLock) { + MMTelFeature.this.onSmsReady(); + } + } }; /** @@ -403,6 +410,10 @@ public class MMTelFeature extends ImsFeature { getSmsImplementation().acknowledgeSmsReport(token, messageRef, result); } + private void onSmsReady() { + getSmsImplementation().onReady(); + } + /** * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default * non-functional implementation is returned. diff --git a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java index c9431fd0e49..57df526b4a4 100644 --- a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java +++ b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java @@ -185,6 +185,8 @@ public class SmsImplBase { * platform will deliver the message to the messages database and notify the IMS provider of the * result by calling {@link #acknowledgeSms(int, int, int)}. * + * This method must not be called before {@link #onReady()} is called. + * * @param token unique token generated by IMS providers that the platform will use to trigger * callbacks for this message. * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and @@ -210,6 +212,8 @@ public class SmsImplBase { * This method should be triggered by the IMS providers to pass the result of the sent message * to the platform. * + * This method must not be called before {@link #onReady()} is called. + * * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])} * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040 * @param status result of sending the SMS. Valid values are: @@ -297,4 +301,13 @@ public class SmsImplBase { public String getSmsFormat() { return SmsMessage.FORMAT_3GPP; } + + /** + * Called when SmsImpl has been initialized and communication with the framework is set up. + * Any attempt by this class to access the framework before this method is called will return + * with an {@link RuntimeException}. + */ + public void onReady() { + // Base Implementation - Should be overridden + } } diff --git a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl index cce39f4daf2..10c7f3e8a2d 100644 --- a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl +++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl @@ -61,4 +61,5 @@ interface IImsMMTelFeature { oneway void acknowledgeSms(int token, int messageRef, int result); oneway void acknowledgeSmsReport(int token, int messageRef, int result); String getSmsFormat(); + oneway void onSmsReady(); } -- GitLab From c4356fb75318967148e8394878af1c13d37080dd Mon Sep 17 00:00:00 2001 From: Matthew Ng Date: Thu, 18 Jan 2018 17:41:34 -0800 Subject: [PATCH 210/416] Uses back and home button for screen pinning when recents is invisible All the text and screen pinning hint shows tells the user that the back and home button should be held to exit screen pinning. The hint also do not have the recents button shown if it is invisible. The toast code has been moved to recents and services calls through to post a toast message depending if the recents button is visible. Test: manual Fixes: 72059911 Change-Id: I93abf5072b97760f33e7e77421544a4b3ad27beb --- .../internal/statusbar/IStatusBar.aidl | 6 ++ .../internal/statusbar/IStatusBarService.aidl | 6 ++ core/res/res/values/strings.xml | 9 -- core/res/res/values/symbols.xml | 3 - .../layout/screen_pinning_request_buttons.xml | 20 ++++- packages/SystemUI/res/values/strings.xml | 11 +++ .../src/com/android/systemui/SysUIToast.java | 8 +- .../recents/ScreenPinningRequest.java | 31 +++++-- .../systemui/statusbar/CommandQueue.java | 30 +++++++ .../phone/NavigationBarFragment.java | 82 +++++++++++++++---- .../statusbar/phone/NavigationBarView.java | 45 +++++----- .../statusbar/phone/ScreenPinningNotify.java | 27 +++--- .../systemui/statusbar/phone/StatusBar.java | 24 +++++- .../android/server/am/LockTaskController.java | 28 ++----- .../statusbar/StatusBarManagerService.java | 21 ++++- .../server/am/LockTaskControllerTest.java | 6 +- 16 files changed, 264 insertions(+), 93 deletions(-) rename services/core/java/com/android/server/am/LockTaskNotify.java => packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenPinningNotify.java (70%) diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index ebb5f9f9e44..f70d3c2a27f 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -132,6 +132,12 @@ oneway interface IStatusBar void clickQsTile(in ComponentName tile); void handleSystemKey(in int key); + /** + * Methods to show toast messages for screen pinning + */ + void showPinningEnterExitToast(boolean entering); + void showPinningEscapeToast(); + void showShutdownUi(boolean isReboot, String reason); // Used to show the dialog when FingerprintService starts authentication diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index cb0b53c495d..adf42878ebb 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -81,6 +81,12 @@ interface IStatusBarService void clickTile(in ComponentName tile); void handleSystemKey(in int key); + /** + * Methods to show toast messages for screen pinning + */ + void showPinningEnterExitToast(boolean entering); + void showPinningEscapeToast(); + // Used to show the dialog when FingerprintService starts authentication void showFingerprintDialog(in Bundle bundle, IFingerprintDialogReceiver receiver); // Used to hide the dialog when a finger is authenticated diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3cde765e04c..51f54195928 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4419,15 +4419,6 @@ sans-serif-medium - - To unpin this screen, touch & hold Back and Overview - buttons - - - Screen pinned - - Screen unpinned - Ask for PIN before unpinning diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a62d49ee7bf..941f411e011 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -767,9 +767,6 @@ - - - diff --git a/packages/SystemUI/res/layout/screen_pinning_request_buttons.xml b/packages/SystemUI/res/layout/screen_pinning_request_buttons.xml index c2b1009c313..a3118b0a5d9 100644 --- a/packages/SystemUI/res/layout/screen_pinning_request_buttons.xml +++ b/packages/SystemUI/res/layout/screen_pinning_request_buttons.xml @@ -84,7 +84,25 @@ android:layout_height="@dimen/screen_pinning_request_button_height" android:layout_weight="0" android:paddingStart="@dimen/screen_pinning_request_frame_padding" - android:paddingEnd="@dimen/screen_pinning_request_frame_padding" > + android:paddingEnd="@dimen/screen_pinning_request_frame_padding" + android:theme="@*android:style/ThemeOverlay.DeviceDefault.Accent"> + + + + Screen is pinned
    This keeps it in view until you unpin. Touch & hold Back and Overview to unpin. + This keeps it in view until you unpin. Touch & hold Back and Home to unpin. This keeps it in view until you unpin. Touch & hold Overview to unpin. + This keeps it in view until you unpin. Touch & hold Home to unpin. + + To unpin this screen, touch & hold Back and Overview + buttons + To unpin this screen, touch & hold Back + and Home buttons Got it No thanks + + Screen pinned + Screen unpinned + Hide %1$s? diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java index 89bc82f8793..43b918dbea7 100644 --- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java +++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java @@ -15,13 +15,19 @@ */ package com.android.systemui; +import android.annotation.StringRes; import android.content.Context; import android.view.WindowManager; import android.widget.Toast; +import static android.widget.Toast.Duration; public class SysUIToast { - public static Toast makeText(Context context, CharSequence text, int duration) { + public static Toast makeText(Context context, @StringRes int resId, @Duration int duration) { + return makeText(context, context.getString(resId), duration); + } + + public static Toast makeText(Context context, CharSequence text, @Duration int duration) { Toast toast = Toast.makeText(context, text, duration); toast.getWindowParams().privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 316ad166189..57f7818eae5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -23,15 +23,12 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.res.Configuration; import android.graphics.PixelFormat; -import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.os.Binder; import android.os.RemoteException; import android.util.DisplayMetrics; import android.view.Gravity; -import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -43,6 +40,9 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.systemui.R; +import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.statusbar.phone.NavigationBarView; +import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.util.leak.RotationUtils; import java.util.ArrayList; @@ -233,11 +233,30 @@ public class ScreenPinningRequest implements View.OnClickListener { .setVisibility(View.INVISIBLE); } + StatusBar statusBar = SysUiServiceProvider.getComponent(mContext, StatusBar.class); + NavigationBarView navigationBarView = statusBar.getNavigationBarView(); + final boolean recentsVisible = navigationBarView != null + && navigationBarView.isRecentsButtonVisible(); boolean touchExplorationEnabled = mAccessibilityService.isTouchExplorationEnabled(); + int descriptionStringResId; + if (recentsVisible) { + mLayout.findViewById(R.id.screen_pinning_recents_group).setVisibility(VISIBLE); + mLayout.findViewById(R.id.screen_pinning_home_bg_light).setVisibility(INVISIBLE); + mLayout.findViewById(R.id.screen_pinning_home_bg).setVisibility(INVISIBLE); + descriptionStringResId = touchExplorationEnabled + ? R.string.screen_pinning_description_accessible + : R.string.screen_pinning_description; + } else { + mLayout.findViewById(R.id.screen_pinning_recents_group).setVisibility(INVISIBLE); + mLayout.findViewById(R.id.screen_pinning_home_bg_light).setVisibility(VISIBLE); + mLayout.findViewById(R.id.screen_pinning_home_bg).setVisibility(VISIBLE); + descriptionStringResId = touchExplorationEnabled + ? R.string.screen_pinning_description_recents_invisible_accessible + : R.string.screen_pinning_description_recents_invisible; + } + ((TextView) mLayout.findViewById(R.id.screen_pinning_description)) - .setText(touchExplorationEnabled - ? R.string.screen_pinning_description_accessible - : R.string.screen_pinning_description); + .setText(descriptionStringResId); final int backBgVisibility = touchExplorationEnabled ? View.INVISIBLE : View.VISIBLE; mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility); mLayout.findViewById(R.id.screen_pinning_back_bg_light).setVisibility(backBgVisibility); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 79e9f7b45d8..f5f62b85ac9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -24,6 +24,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.support.annotation.VisibleForTesting; import android.util.Pair; @@ -90,6 +91,8 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_FINGERPRINT_ERROR = 42 << MSG_SHIFT; private static final int MSG_FINGERPRINT_HIDE = 43 << MSG_SHIFT; private static final int MSG_SHOW_CHARGING_ANIMATION = 44 << MSG_SHIFT; + private static final int MSG_SHOW_PINNING_TOAST_ENTER_EXIT = 45 << MSG_SHIFT; + private static final int MSG_SHOW_PINNING_TOAST_ESCAPE = 46 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -148,6 +151,8 @@ public class CommandQueue extends IStatusBar.Stub { default void clickTile(ComponentName tile) { } default void handleSystemKey(int arg1) { } + default void showPinningEnterExitToast(boolean entering) { } + default void showPinningEscapeToast() { } default void handleShowGlobalActionsMenu() { } default void handleShowShutdownUi(boolean isReboot, String reason) { } @@ -452,6 +457,21 @@ public class CommandQueue extends IStatusBar.Stub { } } + @Override + public void showPinningEnterExitToast(boolean entering) { + synchronized (mLock) { + mHandler.obtainMessage(MSG_SHOW_PINNING_TOAST_ENTER_EXIT, entering).sendToTarget(); + } + } + + @Override + public void showPinningEscapeToast() { + synchronized (mLock) { + mHandler.obtainMessage(MSG_SHOW_PINNING_TOAST_ESCAPE).sendToTarget(); + } + } + + @Override public void showGlobalActionsMenu() { synchronized (mLock) { @@ -767,6 +787,16 @@ public class CommandQueue extends IStatusBar.Stub { mCallbacks.get(i).showChargingAnimation(msg.arg1); } break; + case MSG_SHOW_PINNING_TOAST_ENTER_EXIT: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).showPinningEnterExitToast(msg.arg1 != 0); + } + break; + case MSG_SHOW_PINNING_TOAST_ESCAPE: + for (int i = 0; i < mCallbacks.size(); i++) { + mCallbacks.get(i).showPinningEscapeToast(); + } + break; } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 65c45a3120c..242be71a42a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -22,11 +22,13 @@ import static android.app.StatusBarManager.windowStateToString; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT; import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE; import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions; +import static com.android.systemui.OverviewProxyService.OverviewProxyListener; import android.accessibilityservice.AccessibilityServiceInfo; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; +import android.annotation.IdRes; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerNative; @@ -69,6 +71,7 @@ import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener; +import android.widget.Button; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -153,6 +156,18 @@ public class NavigationBarFragment extends Fragment implements Callbacks { private Animator mRotateShowAnimator; private Animator mRotateHideAnimator; + private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() { + @Override + public void onConnectionChanged(boolean isConnected) { + mNavigationBarView.onOverviewProxyConnectionChanged(isConnected); + updateScreenPinningGestures(); + } + + @Override + public void onRecentsAnimationStarted() { + mNavigationBarView.setRecentsAnimationStarted(true); + } + }; // ----- Fragment Lifecycle Callbacks ----- @@ -239,12 +254,14 @@ public class NavigationBarFragment extends Fragment implements Callbacks { filter.addAction(Intent.ACTION_SCREEN_ON); getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null); notifyNavigationBarScreenOn(); + mOverviewProxyService.addCallback(mOverviewProxyListener); } @Override public void onDestroyView() { super.onDestroyView(); mNavigationBarView.getLightTransitionsController().destroy(getContext()); + mOverviewProxyService.removeCallback(mOverviewProxyListener); getContext().unregisterReceiver(mBroadcastReceiver); } @@ -514,6 +531,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { if (masked != mDisabledFlags1) { mDisabledFlags1 = masked; if (mNavigationBarView != null) mNavigationBarView.setDisabledFlags(state1); + updateScreenPinningGestures(); } } @@ -528,7 +546,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { private boolean shouldDisableNavbarGestures() { return !mStatusBar.isDeviceProvisioned() || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0 - || mOverviewProxyService.getProxy() != null; + || mNavigationBarView.getRecentsButton().getVisibility() != View.VISIBLE; } private void repositionNavigationBar() { @@ -540,6 +558,24 @@ public class NavigationBarFragment extends Fragment implements Callbacks { ((View) mNavigationBarView.getParent()).getLayoutParams()); } + private void updateScreenPinningGestures() { + if (mNavigationBarView == null) { + return; + } + + // Change the cancel pin gesture to home and back if recents button is invisible + boolean recentsVisible = mNavigationBarView.isRecentsButtonVisible(); + ButtonDispatcher homeButton = mNavigationBarView.getHomeButton(); + ButtonDispatcher backButton = mNavigationBarView.getBackButton(); + if (recentsVisible) { + homeButton.setOnLongClickListener(this::onHomeLongClick); + backButton.setOnLongClickListener(this::onLongPressBackRecents); + } else { + homeButton.setOnLongClickListener(this::onLongPressBackHome); + backButton.setOnLongClickListener(this::onLongPressBackHome); + } + } + private void notifyNavigationBarScreenOn() { mNavigationBarView.notifyScreenOn(); } @@ -649,20 +685,29 @@ public class NavigationBarFragment extends Fragment implements Callbacks { mCommandQueue.toggleRecentApps(); } + private boolean onLongPressBackHome(View v) { + return onLongPressNavigationButtons(v, R.id.back, R.id.home); + } + + private boolean onLongPressBackRecents(View v) { + return onLongPressNavigationButtons(v, R.id.back, R.id.recent_apps); + } + /** - * This handles long-press of both back and recents. They are - * handled together to capture them both being long-pressed + * This handles long-press of both back and recents/home. Back is the common button with + * combination of recents if it is visible or home if recents is invisible. + * They are handled together to capture them both being long-pressed * at the same time to exit screen pinning (lock task). * - * When accessibility mode is on, only a long-press from recents + * When accessibility mode is on, only a long-press from recents/home * is required to exit. * * In all other circumstances we try to pass through long-press events * for Back, so that apps can still use it. Which can be from two things. * 1) Not currently in screen pinning (lock task). - * 2) Back is long-pressed without recents. + * 2) Back is long-pressed without recents/home. */ - private boolean onLongPressBackRecents(View v) { + private boolean onLongPressNavigationButtons(View v, @IdRes int btnId1, @IdRes int btnId2) { try { boolean sendBackLongPress = false; IActivityManager activityManager = ActivityManagerNative.getDefault(); @@ -670,6 +715,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { boolean inLockTaskMode = activityManager.isInLockTaskMode(); if (inLockTaskMode && !touchExplorationEnabled) { long time = System.currentTimeMillis(); + // If we recently long-pressed the other button then they were // long-pressed 'together' if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERENCE) { @@ -677,26 +723,32 @@ public class NavigationBarFragment extends Fragment implements Callbacks { // When exiting refresh disabled flags. mNavigationBarView.setDisabledFlags(mDisabledFlags1, true); return true; - } else if ((v.getId() == R.id.back) - && !mNavigationBarView.getRecentsButton().getCurrentView().isPressed()) { - // If we aren't pressing recents right now then they presses - // won't be together, so send the standard long-press action. - sendBackLongPress = true; + } else if (v.getId() == btnId1) { + ButtonDispatcher button = btnId2 == R.id.recent_apps + ? mNavigationBarView.getRecentsButton() + : mNavigationBarView.getHomeButton(); + if (!button.getCurrentView().isPressed()) { + // If we aren't pressing recents/home right now then they presses + // won't be together, so send the standard long-press action. + sendBackLongPress = true; + } } mLastLockToAppLongPress = time; } else { // If this is back still need to handle sending the long-press event. - if (v.getId() == R.id.back) { + if (v.getId() == btnId1) { sendBackLongPress = true; } else if (touchExplorationEnabled && inLockTaskMode) { - // When in accessibility mode a long press that is recents (not back) + // When in accessibility mode a long press that is recents/home (not back) // should stop lock task. activityManager.stopSystemLockTaskMode(); // When exiting refresh disabled flags. mNavigationBarView.setDisabledFlags(mDisabledFlags1, true); return true; - } else if (v.getId() == R.id.recent_apps) { - return onLongPressRecents(); + } else if (v.getId() == btnId2) { + return btnId2 == R.id.recent_apps + ? onLongPressRecents() + : onHomeLongClick(mNavigationBarView.getHomeButton().getCurrentView()); } } if (sendBackLongPress) { 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 de6ecac5b00..cd220a7053a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -207,23 +207,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener