Merge "Migrating Incremental* APIs to PackageManager APIs."
diff --git a/CleanSpec.mk b/CleanSpec.mk
index b84e715..0b7ea28 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -259,6 +259,7 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/framework/service-statsd.jar)
$(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_aidl-cpp-source/)
$(call add-clean-step, rm -rf $(SOONG_OUT_DIR)/.intermediates/frameworks/base/libincremental_manager_aidl-cpp-source/)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
# ******************************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
# ******************************************************************
diff --git a/api/current.txt b/api/current.txt
index bf082d3..86aa80b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -19618,6 +19618,197 @@
}
+package android.icu.number {
+
+ public class CompactNotation extends android.icu.number.Notation {
+ }
+
+ public abstract class CurrencyPrecision extends android.icu.number.Precision {
+ method public android.icu.number.Precision withCurrency(android.icu.util.Currency);
+ }
+
+ public class FormattedNumber implements java.lang.CharSequence {
+ method public char charAt(int);
+ method public int length();
+ method public CharSequence subSequence(int, int);
+ method public java.math.BigDecimal toBigDecimal();
+ method public java.text.AttributedCharacterIterator toCharacterIterator();
+ }
+
+ public class FormattedNumberRange implements java.lang.CharSequence {
+ method public char charAt(int);
+ method public java.math.BigDecimal getFirstBigDecimal();
+ method public android.icu.number.NumberRangeFormatter.RangeIdentityResult getIdentityResult();
+ method public java.math.BigDecimal getSecondBigDecimal();
+ method public int length();
+ method public CharSequence subSequence(int, int);
+ method public java.text.AttributedCharacterIterator toCharacterIterator();
+ }
+
+ public abstract class FractionPrecision extends android.icu.number.Precision {
+ method public android.icu.number.Precision withMaxDigits(int);
+ method public android.icu.number.Precision withMinDigits(int);
+ }
+
+ public class IntegerWidth {
+ method public android.icu.number.IntegerWidth truncateAt(int);
+ method public static android.icu.number.IntegerWidth zeroFillTo(int);
+ }
+
+ public class LocalizedNumberFormatter extends android.icu.number.NumberFormatterSettings<android.icu.number.LocalizedNumberFormatter> {
+ method public android.icu.number.FormattedNumber format(long);
+ method public android.icu.number.FormattedNumber format(double);
+ method public android.icu.number.FormattedNumber format(Number);
+ method public android.icu.number.FormattedNumber format(android.icu.util.Measure);
+ method public java.text.Format toFormat();
+ }
+
+ public class LocalizedNumberRangeFormatter extends android.icu.number.NumberRangeFormatterSettings<android.icu.number.LocalizedNumberRangeFormatter> {
+ method public android.icu.number.FormattedNumberRange formatRange(int, int);
+ method public android.icu.number.FormattedNumberRange formatRange(double, double);
+ method public android.icu.number.FormattedNumberRange formatRange(Number, Number);
+ }
+
+ public class Notation {
+ method public static android.icu.number.CompactNotation compactLong();
+ method public static android.icu.number.CompactNotation compactShort();
+ method public static android.icu.number.ScientificNotation engineering();
+ method public static android.icu.number.ScientificNotation scientific();
+ method public static android.icu.number.SimpleNotation simple();
+ }
+
+ public final class NumberFormatter {
+ method public static android.icu.number.UnlocalizedNumberFormatter with();
+ method public static android.icu.number.LocalizedNumberFormatter withLocale(java.util.Locale);
+ method public static android.icu.number.LocalizedNumberFormatter withLocale(android.icu.util.ULocale);
+ }
+
+ public enum NumberFormatter.DecimalSeparatorDisplay {
+ enum_constant public static final android.icu.number.NumberFormatter.DecimalSeparatorDisplay ALWAYS;
+ enum_constant public static final android.icu.number.NumberFormatter.DecimalSeparatorDisplay AUTO;
+ }
+
+ public enum NumberFormatter.GroupingStrategy {
+ enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy AUTO;
+ enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy MIN2;
+ enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy OFF;
+ enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy ON_ALIGNED;
+ enum_constant public static final android.icu.number.NumberFormatter.GroupingStrategy THOUSANDS;
+ }
+
+ public enum NumberFormatter.SignDisplay {
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING_ALWAYS;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ACCOUNTING_EXCEPT_ZERO;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay ALWAYS;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay AUTO;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay EXCEPT_ZERO;
+ enum_constant public static final android.icu.number.NumberFormatter.SignDisplay NEVER;
+ }
+
+ public enum NumberFormatter.UnitWidth {
+ enum_constant public static final android.icu.number.NumberFormatter.UnitWidth FULL_NAME;
+ enum_constant public static final android.icu.number.NumberFormatter.UnitWidth HIDDEN;
+ enum_constant public static final android.icu.number.NumberFormatter.UnitWidth ISO_CODE;
+ enum_constant public static final android.icu.number.NumberFormatter.UnitWidth NARROW;
+ enum_constant public static final android.icu.number.NumberFormatter.UnitWidth SHORT;
+ }
+
+ public abstract class NumberFormatterSettings<T extends android.icu.number.NumberFormatterSettings<?>> {
+ method public T decimal(android.icu.number.NumberFormatter.DecimalSeparatorDisplay);
+ method public T grouping(android.icu.number.NumberFormatter.GroupingStrategy);
+ method public T integerWidth(android.icu.number.IntegerWidth);
+ method public T notation(android.icu.number.Notation);
+ method public T perUnit(android.icu.util.MeasureUnit);
+ method public T precision(android.icu.number.Precision);
+ method public T roundingMode(java.math.RoundingMode);
+ method public T scale(android.icu.number.Scale);
+ method public T sign(android.icu.number.NumberFormatter.SignDisplay);
+ method public T symbols(android.icu.text.DecimalFormatSymbols);
+ method public T symbols(android.icu.text.NumberingSystem);
+ method public T unit(android.icu.util.MeasureUnit);
+ method public T unitWidth(android.icu.number.NumberFormatter.UnitWidth);
+ }
+
+ public abstract class NumberRangeFormatter {
+ method public static android.icu.number.UnlocalizedNumberRangeFormatter with();
+ method public static android.icu.number.LocalizedNumberRangeFormatter withLocale(java.util.Locale);
+ method public static android.icu.number.LocalizedNumberRangeFormatter withLocale(android.icu.util.ULocale);
+ }
+
+ public enum NumberRangeFormatter.RangeCollapse {
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeCollapse ALL;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeCollapse AUTO;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeCollapse NONE;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeCollapse UNIT;
+ }
+
+ public enum NumberRangeFormatter.RangeIdentityFallback {
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityFallback APPROXIMATELY;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityFallback APPROXIMATELY_OR_SINGLE_VALUE;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityFallback RANGE;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityFallback SINGLE_VALUE;
+ }
+
+ public enum NumberRangeFormatter.RangeIdentityResult {
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityResult EQUAL_AFTER_ROUNDING;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityResult EQUAL_BEFORE_ROUNDING;
+ enum_constant public static final android.icu.number.NumberRangeFormatter.RangeIdentityResult NOT_EQUAL;
+ }
+
+ public abstract class NumberRangeFormatterSettings<T extends android.icu.number.NumberRangeFormatterSettings<?>> {
+ method public T collapse(android.icu.number.NumberRangeFormatter.RangeCollapse);
+ method public T identityFallback(android.icu.number.NumberRangeFormatter.RangeIdentityFallback);
+ method public T numberFormatterBoth(android.icu.number.UnlocalizedNumberFormatter);
+ method public T numberFormatterFirst(android.icu.number.UnlocalizedNumberFormatter);
+ method public T numberFormatterSecond(android.icu.number.UnlocalizedNumberFormatter);
+ }
+
+ public abstract class Precision implements java.lang.Cloneable {
+ method public Object clone();
+ method public static android.icu.number.CurrencyPrecision currency(android.icu.util.Currency.CurrencyUsage);
+ method public static android.icu.number.FractionPrecision fixedFraction(int);
+ method public static android.icu.number.Precision fixedSignificantDigits(int);
+ method public static android.icu.number.Precision increment(java.math.BigDecimal);
+ method public static android.icu.number.FractionPrecision integer();
+ method public static android.icu.number.FractionPrecision maxFraction(int);
+ method public static android.icu.number.Precision maxSignificantDigits(int);
+ method public static android.icu.number.FractionPrecision minFraction(int);
+ method public static android.icu.number.FractionPrecision minMaxFraction(int, int);
+ method public static android.icu.number.Precision minMaxSignificantDigits(int, int);
+ method public static android.icu.number.Precision minSignificantDigits(int);
+ method public static android.icu.number.Precision unlimited();
+ }
+
+ public class Scale {
+ method public static android.icu.number.Scale byBigDecimal(java.math.BigDecimal);
+ method public static android.icu.number.Scale byDouble(double);
+ method public static android.icu.number.Scale byDoubleAndPowerOfTen(double, int);
+ method public static android.icu.number.Scale none();
+ method public static android.icu.number.Scale powerOfTen(int);
+ }
+
+ public class ScientificNotation extends android.icu.number.Notation implements java.lang.Cloneable {
+ method public Object clone();
+ method public android.icu.number.ScientificNotation withExponentSignDisplay(android.icu.number.NumberFormatter.SignDisplay);
+ method public android.icu.number.ScientificNotation withMinExponentDigits(int);
+ }
+
+ public class SimpleNotation extends android.icu.number.Notation {
+ }
+
+ public class UnlocalizedNumberFormatter extends android.icu.number.NumberFormatterSettings<android.icu.number.UnlocalizedNumberFormatter> {
+ method public android.icu.number.LocalizedNumberFormatter locale(java.util.Locale);
+ method public android.icu.number.LocalizedNumberFormatter locale(android.icu.util.ULocale);
+ }
+
+ public class UnlocalizedNumberRangeFormatter extends android.icu.number.NumberRangeFormatterSettings<android.icu.number.UnlocalizedNumberRangeFormatter> {
+ method public android.icu.number.LocalizedNumberRangeFormatter locale(java.util.Locale);
+ method public android.icu.number.LocalizedNumberRangeFormatter locale(android.icu.util.ULocale);
+ }
+
+}
+
package android.icu.text {
public final class AlphabeticIndex<V> implements java.lang.Iterable<android.icu.text.AlphabeticIndex.Bucket<V>> {
diff --git a/api/system-current.txt b/api/system-current.txt
index dc27f22..05e2ca5 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5394,11 +5394,15 @@
ctor public EasyConnectStatusCallback();
method public abstract void onConfiguratorSuccess(int);
method public abstract void onEnrolleeSuccess(int);
- method public abstract void onFailure(int);
+ method public void onFailure(int);
+ method public void onFailure(int, @Nullable String, @NonNull android.util.SparseArray<int[]>, @NonNull int[]);
method public abstract void onProgress(int);
field public static final int EASY_CONNECT_EVENT_FAILURE_AUTHENTICATION = -2; // 0xfffffffe
field public static final int EASY_CONNECT_EVENT_FAILURE_BUSY = -5; // 0xfffffffb
+ field public static final int EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK = -10; // 0xfffffff6
field public static final int EASY_CONNECT_EVENT_FAILURE_CONFIGURATION = -4; // 0xfffffffc
+ field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION = -11; // 0xfffffff5
+ field public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = -12; // 0xfffffff4
field public static final int EASY_CONNECT_EVENT_FAILURE_GENERIC = -7; // 0xfffffff9
field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = -9; // 0xfffffff7
field public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_URI = -1; // 0xffffffff
@@ -5406,7 +5410,10 @@
field public static final int EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED = -8; // 0xfffffff8
field public static final int EASY_CONNECT_EVENT_FAILURE_TIMEOUT = -6; // 0xfffffffa
field public static final int EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS = 0; // 0x0
+ field public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED = 3; // 0x3
+ field public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2; // 0x2
field public static final int EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING = 1; // 0x1
+ field public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED = 1; // 0x1
field public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0; // 0x0
}
@@ -8899,6 +8906,7 @@
}
public class CarrierConfigManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
method @NonNull public static android.os.PersistableBundle getDefaultConfig();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void overrideConfig(int, @Nullable android.os.PersistableBundle);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void updateConfigForPhoneId(int, String);
@@ -9697,6 +9705,7 @@
}
public class ServiceState implements android.os.Parcelable {
+ method public int getDataRegistrationState();
method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int);
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoList();
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForDomain(int);
diff --git a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
index f6e1345..63ee8a6 100644
--- a/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
+++ b/cmds/idmap2/libidmap2/PrettyPrintVisitor.cpp
@@ -16,6 +16,7 @@
#include "idmap2/PrettyPrintVisitor.h"
+#include <istream>
#include <string>
#include "android-base/macros.h"
@@ -28,21 +29,30 @@
#define RESID(pkg, type, entry) (((pkg) << 24) | ((type) << 16) | (entry))
+#define TAB " "
+
void PrettyPrintVisitor::visit(const Idmap& idmap ATTRIBUTE_UNUSED) {
}
void PrettyPrintVisitor::visit(const IdmapHeader& header) {
- stream_ << "target apk path : " << header.GetTargetPath() << std::endl
- << "overlay apk path : " << header.GetOverlayPath() << std::endl;
+ stream_ << "Paths:" << std::endl
+ << TAB "target apk path : " << header.GetTargetPath() << std::endl
+ << TAB "overlay apk path : " << header.GetOverlayPath() << std::endl;
const std::string& debug = header.GetDebugInfo();
if (!debug.empty()) {
- stream_ << debug; // assume newline terminated
+ std::istringstream debug_stream(debug);
+ std::string line;
+ stream_ << "Debug info:" << std::endl;
+ while (std::getline(debug_stream, line)) {
+ stream_ << TAB << line << std::endl;
+ }
}
target_apk_ = ApkAssets::Load(header.GetTargetPath().to_string());
if (target_apk_) {
target_am_.SetApkAssets({target_apk_.get()});
}
+ stream_ << "Mapping:" << std::endl;
}
void PrettyPrintVisitor::visit(const IdmapData::Header& header ATTRIBUTE_UNUSED) {
@@ -55,7 +65,7 @@
const size_t string_pool_offset = data.GetHeader()->GetStringPoolIndexOffset();
for (auto& target_entry : data.GetTargetEntries()) {
- stream_ << base::StringPrintf("0x%08x ->", target_entry.target_id);
+ stream_ << TAB << base::StringPrintf("0x%08x ->", target_entry.target_id);
if (target_entry.data_type != Res_value::TYPE_REFERENCE &&
target_entry.data_type != Res_value::TYPE_DYNAMIC_REFERENCE) {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index a2cfff2..2270974 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -5995,6 +5995,14 @@
USER_GRANTED_ONE_TIME = 10;
// user ignored request by leaving the request screen without choosing any option
USER_IGNORED = 11;
+ // user granted the permission after being linked to settings
+ USER_GRANTED_IN_SETTINGS = 12;
+ // user denied the permission after being linked to settings
+ USER_DENIED_IN_SETTINGS = 13;
+ // user denied the permission with prejudice after being linked to settings
+ USER_DENIED_WITH_PREJUDICE_IN_SETTINGS = 14;
+ // permission was automatically revoked after one-time permission expired
+ AUTO_ONE_TIME_PERMISSION_REVOKED = 15;
}
// The result of the permission grant
optional Result result = 6;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ff581c0..9e0c2fc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -8674,7 +8674,6 @@
* @hide
*/
@RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
- @UnsupportedAppUsage
public void registerRemoteAnimations(RemoteAnimationDefinition definition) {
try {
ActivityTaskManager.getService().registerRemoteAnimations(mToken, definition);
@@ -8683,6 +8682,20 @@
}
}
+ /**
+ * Unregisters all remote animations for this activity.
+ *
+ * @hide
+ */
+ @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+ public void unregisterRemoteAnimations() {
+ try {
+ ActivityTaskManager.getService().unregisterRemoteAnimations(mToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
class HostCallbacks extends FragmentHostCallback<Activity> {
public HostCallbacks() {
super(Activity.this /*activity*/);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index e2b1b86..df5d6c7 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -437,6 +437,11 @@
void registerRemoteAnimations(in IBinder token, in RemoteAnimationDefinition definition);
/**
+ * Unregisters all remote animations for a specific activity.
+ */
+ void unregisterRemoteAnimations(in IBinder token);
+
+ /**
* Registers a remote animation to be run for all activity starts from a certain package during
* a short predefined amount of time.
*/
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index ac8b40c..2507991 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -33,6 +33,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
@@ -40,6 +41,8 @@
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
+import android.graphics.ColorSpace;
+import android.graphics.ImageDecoder;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
@@ -63,11 +66,15 @@
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import android.view.Display;
import android.view.WindowManagerGlobal;
+import com.android.internal.R;
+
import libcore.io.IoUtils;
import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -76,7 +83,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -193,7 +203,13 @@
*/
public static final int FLAG_LOCK = 1 << 1;
+ private static final Object sSync = new Object[0];
+ @UnsupportedAppUsage
+ private static Globals sGlobals;
+
private final Context mContext;
+ private final boolean mWcgEnabled;
+ private final ColorManagementProxy mCmProxy;
/**
* Special drawable that draws a wallpaper as fast as possible. Assumes
@@ -388,13 +404,14 @@
}
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
- @SetWallpaperFlags int which) {
+ @SetWallpaperFlags int which, ColorManagementProxy cmProxy) {
return peekWallpaperBitmap(context, returnDefault, which, context.getUserId(),
- false /* hardware */);
+ false /* hardware */, cmProxy);
}
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault,
- @SetWallpaperFlags int which, int userId, boolean hardware) {
+ @SetWallpaperFlags int which, int userId, boolean hardware,
+ ColorManagementProxy cmProxy) {
if (mService != null) {
try {
if (!mService.isWallpaperSupported(context.getOpPackageName())) {
@@ -412,7 +429,8 @@
mCachedWallpaper = null;
mCachedWallpaperUserId = 0;
try {
- mCachedWallpaper = getCurrentWallpaperLocked(context, userId, hardware);
+ mCachedWallpaper = getCurrentWallpaperLocked(
+ context, userId, hardware, cmProxy);
mCachedWallpaperUserId = userId;
} catch (OutOfMemoryError e) {
Log.w(TAG, "Out of memory loading the current wallpaper: " + e);
@@ -450,7 +468,8 @@
}
}
- private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware) {
+ private Bitmap getCurrentWallpaperLocked(Context context, int userId, boolean hardware,
+ ColorManagementProxy cmProxy) {
if (mService == null) {
Log.w(TAG, "WallpaperService not running");
return null;
@@ -458,21 +477,29 @@
try {
Bundle params = new Bundle();
- ParcelFileDescriptor fd = mService.getWallpaperWithFeature(
+ ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
context.getOpPackageName(), context.getFeatureId(), this, FLAG_SYSTEM,
params, userId);
- if (fd != null) {
- try {
- BitmapFactory.Options options = new BitmapFactory.Options();
- if (hardware) {
- options.inPreferredConfig = Bitmap.Config.HARDWARE;
+
+ if (pfd != null) {
+ try (BufferedInputStream bis = new BufferedInputStream(
+ new ParcelFileDescriptor.AutoCloseInputStream(pfd))) {
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ int data;
+ while ((data = bis.read()) != -1) {
+ baos.write(data);
}
- return BitmapFactory.decodeFileDescriptor(
- fd.getFileDescriptor(), null, options);
- } catch (OutOfMemoryError e) {
+ ImageDecoder.Source src = ImageDecoder.createSource(baos.toByteArray());
+ return ImageDecoder.decodeBitmap(src, ((decoder, info, source) -> {
+ // Mutable and hardware config can't be set at the same time.
+ decoder.setMutableRequired(!hardware);
+ // Let's do color management
+ if (cmProxy != null) {
+ cmProxy.doColorManagement(decoder, info);
+ }
+ }));
+ } catch (OutOfMemoryError | IOException e) {
Log.w(TAG, "Can't decode file", e);
- } finally {
- IoUtils.closeQuietly(fd);
}
}
} catch (RemoteException e) {
@@ -497,10 +524,6 @@
}
}
- private static final Object sSync = new Object[0];
- @UnsupportedAppUsage
- private static Globals sGlobals;
-
static void initGlobals(IWallpaperManager service, Looper looper) {
synchronized (sSync) {
if (sGlobals == null) {
@@ -514,6 +537,10 @@
if (service != null) {
initGlobals(service, context.getMainLooper());
}
+ // Check if supports mixed color spaces composition in hardware.
+ mWcgEnabled = context.getResources().getConfiguration().isScreenWideColorGamut()
+ && context.getResources().getBoolean(R.bool.config_enableWcgMode);
+ mCmProxy = new ColorManagementProxy(context);
}
/**
@@ -531,6 +558,22 @@
}
/**
+ * Indicate whether wcg (Wide Color Gamut) should be enabled.
+ * <p>
+ * Some devices lack of capability of mixed color spaces composition,
+ * enable wcg on such devices might cause memory or battery concern.
+ * <p>
+ * Therefore, in addition to {@link Configuration#isScreenWideColorGamut()},
+ * we also take mixed color spaces composition (config_enableWcgMode) into account.
+ *
+ * @see Configuration#isScreenWideColorGamut()
+ * @return True if wcg should be enabled for this device.
+ */
+ private boolean shouldEnableWideColorGamut() {
+ return mWcgEnabled;
+ }
+
+ /**
* Retrieve the current system wallpaper; if
* no wallpaper is set, the system built-in static wallpaper is returned.
* This is returned as an
@@ -546,7 +589,7 @@
* is not able to access the wallpaper.
*/
public Drawable getDrawable() {
- Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM);
+ Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, mCmProxy);
if (bm != null) {
Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
dr.setDither(false);
@@ -777,7 +820,7 @@
* null pointer if these is none.
*/
public Drawable peekDrawable() {
- Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM);
+ Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, mCmProxy);
if (bm != null) {
Drawable dr = new BitmapDrawable(mContext.getResources(), bm);
dr.setDither(false);
@@ -801,7 +844,7 @@
*/
@RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
public Drawable getFastDrawable() {
- Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM);
+ Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, mCmProxy);
if (bm != null) {
return new FastBitmapDrawable(bm);
}
@@ -817,7 +860,7 @@
*/
@RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
public Drawable peekFastDrawable() {
- Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM);
+ Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, mCmProxy);
if (bm != null) {
return new FastBitmapDrawable(bm);
}
@@ -825,6 +868,27 @@
}
/**
+ * Whether the wallpaper supports Wide Color Gamut or not.
+ * @param which The wallpaper whose image file is to be retrieved. Must be a single
+ * defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
+ * @return true when supported.
+ *
+ * @see #FLAG_LOCK
+ * @see #FLAG_SYSTEM
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+ public boolean wallpaperSupportsWcg(int which) {
+ if (!shouldEnableWideColorGamut()) {
+ return false;
+ }
+ Bitmap bitmap = sGlobals.peekWallpaperBitmap(mContext, false, which, mCmProxy);
+ return bitmap != null && bitmap.getColorSpace() != null
+ && bitmap.getColorSpace() != ColorSpace.get(ColorSpace.Named.SRGB)
+ && mCmProxy.isSupportedColorSpace(bitmap.getColorSpace());
+ }
+
+ /**
* Like {@link #getDrawable()} but returns a Bitmap with default {@link Bitmap.Config}.
*
* @hide
@@ -852,7 +916,8 @@
* @hide
*/
public Bitmap getBitmapAsUser(int userId, boolean hardware) {
- return sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, userId, hardware);
+ return sGlobals.peekWallpaperBitmap(
+ mContext, true, FLAG_SYSTEM, userId, hardware, mCmProxy);
}
/**
@@ -1975,6 +2040,33 @@
return false;
}
+ /**
+ * A private class to help Globals#getCurrentWallpaperLocked handle color management.
+ */
+ private static class ColorManagementProxy {
+ private final Set<ColorSpace> mSupportedColorSpaces = new HashSet<>();
+
+ ColorManagementProxy(Context context) {
+ // Get a list of supported wide gamut color spaces.
+ Display display = context.getDisplay();
+ if (display != null) {
+ mSupportedColorSpaces.addAll(Arrays.asList(display.getSupportedWideColorGamut()));
+ }
+ }
+
+ boolean isSupportedColorSpace(ColorSpace colorSpace) {
+ return colorSpace != null && (colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)
+ || mSupportedColorSpaces.contains(colorSpace));
+ }
+
+ void doColorManagement(ImageDecoder decoder, ImageDecoder.ImageInfo info) {
+ if (!isSupportedColorSpace(info.getColorSpace())) {
+ decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB));
+ Log.w(TAG, "Not supported color space: " + info.getColorSpace());
+ }
+ }
+ }
+
// Private completion callback for setWallpaper() synchronization
private class WallpaperSetCompletion extends IWallpaperManagerCallback.Stub {
final CountDownLatch mLatch;
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 1a6ec4e..b555d20 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -43,7 +43,9 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
* Provides information about the size and density of a logical display.
@@ -382,6 +384,23 @@
/** @hide */
public static final int COLOR_MODE_DISPLAY_P3 = 9;
+ /** @hide **/
+ @IntDef(prefix = {"COLOR_MODE_"}, value = {
+ COLOR_MODE_INVALID,
+ COLOR_MODE_DEFAULT,
+ COLOR_MODE_BT601_625,
+ COLOR_MODE_BT601_625_UNADJUSTED,
+ COLOR_MODE_BT601_525,
+ COLOR_MODE_BT601_525_UNADJUSTED,
+ COLOR_MODE_BT709,
+ COLOR_MODE_DCI_P3,
+ COLOR_MODE_SRGB,
+ COLOR_MODE_ADOBE_RGB,
+ COLOR_MODE_DISPLAY_P3
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ColorMode {}
+
/**
* Indicates that when display is removed, all its activities will be moved to the primary
* display and the topmost activity should become focused.
@@ -960,6 +979,37 @@
}
/**
+ * Gets the supported wide color gamuts of this device.
+ *
+ * @return Supported WCG color spaces.
+ * @hide
+ */
+ public @ColorMode ColorSpace[] getSupportedWideColorGamut() {
+ synchronized (this) {
+ final ColorSpace[] defaultColorSpaces = new ColorSpace[0];
+ updateDisplayInfoLocked();
+ if (!isWideColorGamut()) {
+ return defaultColorSpaces;
+ }
+
+ final int[] colorModes = getSupportedColorModes();
+ final List<ColorSpace> colorSpaces = new ArrayList<>();
+ for (int colorMode : colorModes) {
+ // Refer to DisplayInfo#isWideColorGamut.
+ switch (colorMode) {
+ case COLOR_MODE_DCI_P3:
+ colorSpaces.add(ColorSpace.get(ColorSpace.Named.DCI_P3));
+ break;
+ case COLOR_MODE_DISPLAY_P3:
+ colorSpaces.add(ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+ break;
+ }
+ }
+ return colorSpaces.toArray(defaultColorSpaces);
+ }
+ }
+
+ /**
* Gets the app VSYNC offset, in nanoseconds. This is a positive value indicating
* the phase offset of the VSYNC events provided by Choreographer relative to the
* display refresh. For example, if Choreographer reports that the refresh occurred
diff --git a/core/java/android/view/RemoteAnimationDefinition.java b/core/java/android/view/RemoteAnimationDefinition.java
index c9bd92a..5a8ac54 100644
--- a/core/java/android/view/RemoteAnimationDefinition.java
+++ b/core/java/android/view/RemoteAnimationDefinition.java
@@ -22,9 +22,12 @@
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.ActivityType;
import android.compat.annotation.UnsupportedAppUsage;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.RemoteException;
import android.util.ArraySet;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.WindowManager.TransitionType;
@@ -124,6 +127,20 @@
}
}
+ /**
+ * Links the death of the runner to the provided death recipient.
+ */
+ public void linkToDeath(IBinder.DeathRecipient deathRecipient) {
+ try {
+ for (int i = 0; i < mTransitionAnimationMap.size(); i++) {
+ mTransitionAnimationMap.valueAt(i).adapter.getRunner().asBinder()
+ .linkToDeath(deathRecipient, 0 /* flags */);
+ }
+ } catch (RemoteException e) {
+ Slog.e("RemoteAnimationDefinition", "Failed to link to death recipient");
+ }
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 48853bf..246a92c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -1377,9 +1377,8 @@
for (TargetInfo innerInfo : mti.getTargets()) {
labels.add(innerInfo.getResolveInfo().loadLabel(getPackageManager()));
}
- f = new ResolverTargetActionsDialogFragment(
- mti.getResolveInfo().loadLabel(getPackageManager()), name, mti.getTargets(),
- labels);
+ f = new ResolverTargetActionsDialogFragment(mti.getDisplayLabel(), name,
+ mti.getTargets(), labels);
} else {
f = new ResolverTargetActionsDialogFragment(
ti.getResolveInfo().loadLabel(getPackageManager()), name, pinned);
diff --git a/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
index 4c52411..55200c8 100644
--- a/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
+++ b/core/java/com/android/internal/app/chooser/MultiDisplayResolveInfo.java
@@ -25,7 +25,6 @@
public class MultiDisplayResolveInfo extends DisplayResolveInfo {
List<DisplayResolveInfo> mTargetInfos = new ArrayList<>();
- String mPackageName;
// We'll use this DRI for basic presentation info - eg icon, name.
final DisplayResolveInfo mBaseInfo;
@@ -38,6 +37,12 @@
mTargetInfos.add(firstInfo);
}
+ @Override
+ public CharSequence getExtendedInfo() {
+ // Never show subtitle for stacked apps
+ return null;
+ }
+
/**
* Add another DisplayResolveInfo to the list included for this target.
*/
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index e21eefb..6e0d5d8 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -828,7 +828,7 @@
}
}
- for (dmabufinfo::DmaBuffer buf : dmabufs) {
+ for (const dmabufinfo::DmaBuffer& buf : dmabufs) {
ionPss += buf.size() / 1024;
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 32ec95a..f28f08b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4260,6 +4260,10 @@
<!-- Add packages here -->
</string-array>
+ <!-- Whether or not wcg (wide color gamut) should be enabled on this device,
+ we only enabled it while the device has ability of mixed color spaces composition -->
+ <bool name="config_enableWcgMode">false</bool>
+
<!-- When true, enables the whitelisted app to handle bug reports from power menu short press. -->
<bool name="config_bugReportHandlerEnabled">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d30c7bf..7cf2b78 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3788,6 +3788,9 @@
<java-symbol type="string" name="accessibility_freeform_caption" />
+ <!-- For Wide Color Gamut -->
+ <java-symbol type="bool" name="config_enableWcgMode" />
+
<!-- For contacts provider. -->
<java-symbol type="string" name="config_rawContactsLocalAccountName" />
<java-symbol type="string" name="config_rawContactsLocalAccountType" />
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/media/jni/android_media_MediaMetricsJNI.cpp
index 37aca08..b82d8e2 100644
--- a/media/jni/android_media_MediaMetricsJNI.cpp
+++ b/media/jni/android_media_MediaMetricsJNI.cpp
@@ -18,6 +18,7 @@
#include <binder/Parcel.h>
#include <jni.h>
+#include <media/IMediaMetricsService.h>
#include <media/MediaMetricsItem.h>
#include <nativehelper/JNIHelp.h>
#include <variant>
@@ -150,14 +151,12 @@
return (jint)BAD_VALUE;
}
- // TODO: directly record item to MediaMetrics service.
- mediametrics::Item item;
- if (item.readFromByteString((char *)buffer, length) != NO_ERROR) {
- ALOGW("%s: cannot read from byte string", __func__);
- return (jint)BAD_VALUE;
+ sp<IMediaMetricsService> service = mediametrics::BaseItem::getService();
+ if (service == nullptr) {
+ ALOGW("Cannot retrieve mediametrics service");
+ return (jint)NO_INIT;
}
- item.selfrecord();
- return (jint)NO_ERROR;
+ return (jint)service->submitBuffer((char *)buffer, length);
}
// Helper function to convert a native PersistableBundle to a Java
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java
index 6facec4..41914b8 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraErrorCollector.java
@@ -969,7 +969,7 @@
*/
private static class IntInMatcher extends InMatcher<Integer> {
public IntInMatcher(int[] values) {
- Preconditions.checkNotNull("values", values);
+ Objects.requireNonNull(values, "values");
mValues = new ArrayList<>(values.length);
for (int i : values) {
mValues.add(i);
@@ -1005,7 +1005,7 @@
*/
private static class BooleanInMatcher extends InMatcher<Boolean> {
public BooleanInMatcher(boolean[] values) {
- Preconditions.checkNotNull("values", values);
+ Objects.requireNonNull(values, "values");
mValues = new ArrayList<>(values.length);
for (boolean i : values) {
mValues.add(i);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java
index e25a140..c77a042 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/InMatcher.java
@@ -36,7 +36,7 @@
protected Collection<T> mValues;
public InMatcher(Collection<T> values) {
- Preconditions.checkNotNull("values", values);
+ Objects.requireNonNull(values, "values");
mValues = values;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java
index 18dc185..0c7e56e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityCompat.java
@@ -37,6 +37,13 @@
}
/**
+ * @see Activity#unregisterRemoteAnimations
+ */
+ public void unregisterRemoteAnimations() {
+ mWrapped.unregisterRemoteAnimations();
+ }
+
+ /**
* @see android.view.ViewDebug#dumpv2(View, ByteArrayOutputStream)
*/
public boolean encodeViewHierarchy(ByteArrayOutputStream out) {
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 29a7167..e50d08c 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -108,12 +108,13 @@
if (mController != null) {
mController.addCallback(this /* StateListener */);
}
- mEglHelper = new EglHelper();
- mRenderer = new ImageWallpaperRenderer(context, this /* SurfaceProxy */);
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
+ mEglHelper = new EglHelper();
+ // Deferred init renderer because we need to get wallpaper by display context.
+ mRenderer = new ImageWallpaperRenderer(getDisplayContext(), this /* SurfaceProxy */);
setFixedSizeAllowed(true);
setOffsetNotificationsEnabled(true);
updateSurfaceSize();
@@ -177,14 +178,13 @@
mRenderer = null;
mEglHelper.finish();
mEglHelper = null;
- getSurfaceHolder().getSurface().hwuiDestroy();
});
}
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
mWorker.getThreadHandler().post(() -> {
- mEglHelper.init(holder);
+ mEglHelper.init(holder, needSupportWideColorGamut());
mRenderer.onSurfaceCreated();
});
}
@@ -257,7 +257,7 @@
// Check if we need to recreate egl surface.
if (mEglHelper.hasEglContext() && !mEglHelper.hasEglSurface()) {
- if (!mEglHelper.createEglSurface(getSurfaceHolder())) {
+ if (!mEglHelper.createEglSurface(getSurfaceHolder(), needSupportWideColorGamut())) {
Log.w(TAG, "recreate egl surface failed!");
}
}
@@ -340,6 +340,10 @@
&& mController.getState() == StatusBarState.KEYGUARD;
}
+ private boolean needSupportWideColorGamut() {
+ return mRenderer.isWcgContent();
+ }
+
@Override
protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
super.dump(prefix, fd, out, args);
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
index c6812a7..4b28540 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
@@ -51,40 +51,65 @@
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLUtils;
+import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
/**
* A helper class to handle EGL management.
*/
public class EglHelper {
private static final String TAG = EglHelper.class.getSimpleName();
+ private static final int OPENGLES_VERSION = 2;
// Below two constants make drawing at low priority, so other things can preempt our drawing.
private static final int EGL_CONTEXT_PRIORITY_LEVEL_IMG = 0x3100;
private static final int EGL_CONTEXT_PRIORITY_LOW_IMG = 0x3103;
private static final boolean DEBUG = true;
+
+ private static final int EGL_GL_COLORSPACE_KHR = 0x309D;
+ private static final int EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT = 0x3490;
+
private static final String EGL_IMG_CONTEXT_PRIORITY = "EGL_IMG_context_priority";
+ /**
+ * https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_gl_colorspace.txt
+ */
+ private static final String KHR_GL_COLOR_SPACE = "EGL_KHR_gl_colorspace";
+
+ /**
+ * https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_display_p3_passthrough.txt
+ */
+ private static final String EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH =
+ "EGL_EXT_gl_colorspace_display_p3_passthrough";
+
private EGLDisplay mEglDisplay;
private EGLConfig mEglConfig;
private EGLContext mEglContext;
private EGLSurface mEglSurface;
private final int[] mEglVersion = new int[2];
private boolean mEglReady;
- private boolean mContextPrioritySupported;
+ private final Set<String> mExts;
+
+ public EglHelper() {
+ mExts = new HashSet<>();
+ connectDisplay();
+ }
/**
- * Initialize EGL and prepare EglSurface.
+ * Initialize render context.
* @param surfaceHolder surface holder.
- * @return true if EglSurface is ready.
+ * @param wideColorGamut claim if a wcg surface is necessary.
+ * @return true if the render context is ready.
*/
- public boolean init(SurfaceHolder surfaceHolder) {
- mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- if (mEglDisplay == EGL_NO_DISPLAY) {
- Log.w(TAG, "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError()));
+ public boolean init(SurfaceHolder surfaceHolder, boolean wideColorGamut) {
+ if (!hasEglDisplay() && !connectDisplay()) {
+ Log.w(TAG, "Can not connect display, abort!");
return false;
}
@@ -105,25 +130,38 @@
return false;
}
- if (!createEglSurface(surfaceHolder)) {
+ if (!createEglSurface(surfaceHolder, wideColorGamut)) {
Log.w(TAG, "Can't create EGLSurface!");
return false;
}
- mContextPrioritySupported = isContextPrioritySuppported();
-
mEglReady = true;
return true;
}
- private boolean isContextPrioritySuppported() {
- String[] extensions = eglQueryString(mEglDisplay, EGL_EXTENSIONS).split(" ");
- for (String extension : extensions) {
- if (extension.equals(EGL_IMG_CONTEXT_PRIORITY)) {
- return true;
- }
+ private boolean connectDisplay() {
+ mExts.clear();
+ mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ if (!hasEglDisplay()) {
+ Log.w(TAG, "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError()));
+ return false;
}
- return false;
+ String queryString = eglQueryString(mEglDisplay, EGL_EXTENSIONS);
+ if (!TextUtils.isEmpty(queryString)) {
+ Collections.addAll(mExts, queryString.split(" "));
+ }
+ return true;
+ }
+
+ private boolean checkExtensionCapability(String extName) {
+ return mExts.contains(extName);
+ }
+
+ private int getWcgCapability() {
+ if (checkExtensionCapability(EXT_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH)) {
+ return EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+ }
+ return 0;
}
private EGLConfig chooseEglConfig() {
@@ -148,7 +186,7 @@
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
- EGL_ALPHA_SIZE, 8,
+ EGL_ALPHA_SIZE, 0,
EGL_DEPTH_SIZE, 0,
EGL_STENCIL_SIZE, 0,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
@@ -160,21 +198,27 @@
/**
* Prepare an EglSurface.
* @param surfaceHolder surface holder.
+ * @param wcg if need to support wcg.
* @return true if EglSurface is ready.
*/
- public boolean createEglSurface(SurfaceHolder surfaceHolder) {
+ public boolean createEglSurface(SurfaceHolder surfaceHolder, boolean wcg) {
if (DEBUG) {
Log.d(TAG, "createEglSurface start");
}
if (hasEglDisplay()) {
- mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null, 0);
+ int[] attrs = null;
+ int wcgCapability = getWcgCapability();
+ if (wcg && checkExtensionCapability(KHR_GL_COLOR_SPACE) && wcgCapability > 0) {
+ attrs = new int[] {EGL_GL_COLORSPACE_KHR, wcgCapability, EGL_NONE};
+ }
+ mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, attrs, 0);
} else {
Log.w(TAG, "mEglDisplay is null");
return false;
}
- if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
+ if (!hasEglSurface()) {
Log.w(TAG, "createWindowSurface failed: " + GLUtils.getEGLErrorString(eglGetError()));
return false;
}
@@ -221,12 +265,12 @@
int[] attrib_list = new int[5];
int idx = 0;
attrib_list[idx++] = EGL_CONTEXT_CLIENT_VERSION;
- attrib_list[idx++] = 2;
- if (mContextPrioritySupported) {
+ attrib_list[idx++] = OPENGLES_VERSION;
+ if (checkExtensionCapability(EGL_IMG_CONTEXT_PRIORITY)) {
attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LEVEL_IMG;
attrib_list[idx++] = EGL_CONTEXT_PRIORITY_LOW_IMG;
}
- attrib_list[idx++] = EGL_NONE;
+ attrib_list[idx] = EGL_NONE;
if (hasEglDisplay()) {
mEglContext = eglCreateContext(mEglDisplay, mEglConfig, EGL_NO_CONTEXT, attrib_list, 0);
} else {
@@ -234,7 +278,7 @@
return false;
}
- if (mEglContext == EGL_NO_CONTEXT) {
+ if (!hasEglContext()) {
Log.w(TAG, "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError()));
return false;
}
@@ -260,7 +304,7 @@
* @return true if EglContext is ready.
*/
public boolean hasEglContext() {
- return mEglContext != null;
+ return mEglContext != null && mEglContext != EGL_NO_CONTEXT;
}
/**
@@ -268,7 +312,7 @@
* @return true if EglDisplay is ready.
*/
public boolean hasEglDisplay() {
- return mEglDisplay != null;
+ return mEglDisplay != null && mEglDisplay != EGL_NO_DISPLAY;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java
index 60ea1cdf..88ab9ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java
@@ -27,6 +27,11 @@
public interface GLWallpaperRenderer {
/**
+ * Check if the content to render is a WCG content.
+ */
+ boolean isWcgContent();
+
+ /**
* Called when the surface is created or recreated.
*/
void onSurfaceCreated();
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
index 24a4b9e..54eca0e 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
@@ -116,7 +116,8 @@
int width = bitmap.getWidth();
int height = bitmap.getHeight();
- Bitmap grayscale = Bitmap.createBitmap(width, height, bitmap.getConfig());
+ Bitmap grayscale = Bitmap.createBitmap(width, height, bitmap.getConfig(),
+ false /* hasAlpha */, bitmap.getColorSpace());
Canvas canvas = new Canvas(grayscale);
ColorMatrix cm = new ColorMatrix(LUMINOSITY_MATRIX);
Paint paint = new Paint();
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index a8371e3..fa8269d 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -62,6 +62,7 @@
private boolean mScissorMode;
private float mXOffset;
private float mYOffset;
+ private boolean mWcgContent;
public ImageWallpaperRenderer(Context context, SurfaceProxy proxy) {
mWallpaperManager = context.getSystemService(WallpaperManager.class);
@@ -94,6 +95,11 @@
}
@Override
+ public boolean isWcgContent() {
+ return mWcgContent;
+ }
+
+ @Override
public void onSurfaceCreated() {
glClearColor(0f, 0f, 0f, 1.0f);
mProgram.useGLProgram(
@@ -112,7 +118,8 @@
Log.d(TAG, "loadBitmap: mBitmap=" + mBitmap);
}
if (mWallpaperManager != null && mBitmap == null) {
- mBitmap = mWallpaperManager.getBitmap();
+ mBitmap = mWallpaperManager.getBitmap(false /* hardware */);
+ mWcgContent = mWallpaperManager.wallpaperSupportsWcg(WallpaperManager.FLAG_SYSTEM);
mWallpaperManager.forgetLoadedWallpaper();
if (mBitmap != null) {
float scale = (float) mScissor.height() / mBitmap.getHeight();
@@ -231,6 +238,7 @@
out.print(prefix); out.print("mYOffset="); out.print(mYOffset);
out.print(prefix); out.print("threshold="); out.print(mImageProcessHelper.getThreshold());
out.print(prefix); out.print("mReveal="); out.print(mImageRevealHelper.getReveal());
+ out.print(prefix); out.print("mWcgContent="); out.print(mWcgContent);
mWallpaper.dump(prefix, fd, out, args);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 00220ce..c9813db 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -16,8 +16,6 @@
package com.android.systemui.screenshot;
-import static android.content.Context.NOTIFICATION_SERVICE;
-import static android.os.AsyncTask.THREAD_POOL_EXECUTOR;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -31,14 +29,10 @@
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.Notification;
-import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -55,7 +49,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.DisplayMetrics;
@@ -74,20 +67,13 @@
import android.widget.TextView;
import android.widget.Toast;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.systemui.R;
-import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.dagger.qualifiers.MainResources;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.util.NotificationChannels;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -108,29 +94,20 @@
* POD used in the AsyncTask which saves an image in the background.
*/
static class SaveImageInBackgroundData {
- public Context context;
public Bitmap image;
public Uri imageUri;
public Consumer<Uri> finisher;
public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
- public int iconSize;
- public int previewWidth;
- public int previewheight;
public int errorMsgResId;
void clearImage() {
image = null;
imageUri = null;
- iconSize = 0;
- }
-
- void clearContext() {
- context = null;
}
}
abstract static class ActionsReadyListener {
- abstract void onActionsReady(PendingIntent shareAction, PendingIntent editAction);
+ abstract void onActionsReady(Uri imageUri, List<Notification.Action> actions);
}
// These strings are used for communicating the action invoked to
@@ -165,13 +142,12 @@
private static final float SCREENSHOT_CORNER_MIN_SCALE_OFFSET = .1f;
private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 8000;
private static final int MESSAGE_CORNER_TIMEOUT = 2;
- private final int mPreviewWidth;
- private final int mPreviewHeight;
+
+ private final ScreenshotNotificationsController mNotificationsController;
private Context mContext;
private WindowManager mWindowManager;
private WindowManager.LayoutParams mWindowLayoutParams;
- private NotificationManager mNotificationManager;
private Display mDisplay;
private DisplayMetrics mDisplayMetrics;
@@ -182,13 +158,9 @@
private ImageView mScreenshotView;
private ImageView mScreenshotFlash;
private LinearLayout mActionsView;
- private TextView mShareAction;
- private TextView mEditAction;
- private TextView mScrollAction;
private AnimatorSet mScreenshotAnimation;
- private int mNotificationIconSize;
private float mBgPadding;
private float mBgPaddingScale;
@@ -213,9 +185,11 @@
* @param context everything needs a context :(
*/
@Inject
- public GlobalScreenshot(Context context, @MainResources Resources resources,
- LayoutInflater layoutInflater) {
+ public GlobalScreenshot(
+ Context context, @MainResources Resources resources, LayoutInflater layoutInflater,
+ ScreenshotNotificationsController screenshotNotificationsController) {
mContext = context;
+ mNotificationsController = screenshotNotificationsController;
// Inflate the screenshot layout
mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
@@ -223,21 +197,6 @@
mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot);
mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
- mShareAction = (TextView) layoutInflater.inflate(
- R.layout.global_screenshot_action_chip, mActionsView, false);
- mEditAction = (TextView) layoutInflater.inflate(
- R.layout.global_screenshot_action_chip, mActionsView, false);
- mScrollAction = (TextView) layoutInflater.inflate(
- R.layout.global_screenshot_action_chip, mActionsView, false);
-
- mShareAction.setText(com.android.internal.R.string.share);
- mEditAction.setText(com.android.internal.R.string.screenshot_edit);
- mScrollAction.setText("Scroll"); // TODO (mkephart): Add to resources and translate
-
- mActionsView.addView(mShareAction);
- mActionsView.addView(mEditAction);
- mActionsView.addView(mScrollAction);
-
mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
mScreenshotLayout.setFocusable(true);
@@ -260,32 +219,14 @@
mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */);
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- mNotificationManager =
- (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
mDisplay = mWindowManager.getDefaultDisplay();
mDisplayMetrics = new DisplayMetrics();
mDisplay.getRealMetrics(mDisplayMetrics);
- // Get the various target sizes
- mNotificationIconSize =
- resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
-
// Scale has to account for both sides of the bg
mBgPadding = (float) resources.getDimensionPixelSize(R.dimen.global_screenshot_bg_padding);
mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
- // determine the optimal preview size
- int panelWidth = 0;
- try {
- panelWidth = resources.getDimensionPixelSize(R.dimen.notification_panel_width);
- } catch (Resources.NotFoundException e) {
- }
- if (panelWidth <= 0) {
- // includes notification_panel_width==match_parent (-1)
- panelWidth = mDisplayMetrics.widthPixels;
- }
- mPreviewWidth = panelWidth;
- mPreviewHeight = resources.getDimensionPixelSize(R.dimen.notification_max_height);
// Setup the Camera shutter sound
mCameraSound = new MediaActionSound();
@@ -298,22 +239,20 @@
private void saveScreenshotInWorkerThread(
Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) {
SaveImageInBackgroundData data = new SaveImageInBackgroundData();
- data.context = mContext;
data.image = mScreenBitmap;
- data.iconSize = mNotificationIconSize;
data.finisher = finisher;
data.mActionsReadyListener = actionsReadyListener;
- data.previewWidth = mPreviewWidth;
- data.previewheight = mPreviewHeight;
if (mSaveInBgTask != null) {
mSaveInBgTask.cancel(false);
}
- mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data, mNotificationManager)
- .execute();
- }
- private void saveScreenshotInWorkerThread(Consumer<Uri> finisher) {
- saveScreenshotInWorkerThread(finisher, null);
+ if (!DeviceConfig.getBoolean(
+ NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false)) {
+ mNotificationsController.reset();
+ mNotificationsController.setImage(mScreenBitmap);
+ mNotificationsController.showSavingScreenshotNotification();
+ }
+ mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
}
/**
@@ -328,7 +267,7 @@
// Take the screenshot
mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
if (mScreenBitmap == null) {
- notifyScreenshotError(mContext, mNotificationManager,
+ mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
finisher.accept(null);
return;
@@ -373,12 +312,8 @@
if (rect != null) {
if (rect.width() != 0 && rect.height() != 0) {
// Need mScreenshotLayout to handle it after the view disappears
- mScreenshotLayout.post(new Runnable() {
- public void run() {
- takeScreenshot(finisher, statusBarVisible, navBarVisible,
- rect);
- }
- });
+ mScreenshotLayout.post(() -> takeScreenshot(
+ finisher, statusBarVisible, navBarVisible, rect));
}
}
@@ -464,15 +399,30 @@
public void onAnimationEnd(Animator animation) {
// Save the screenshot once we have a bit of time now
if (!useCornerFlow) {
- saveScreenshotInWorkerThread(finisher);
+ saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
+ @Override
+ void onActionsReady(Uri uri, List<Notification.Action> actions) {
+ if (uri == null) {
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ } else {
+ mNotificationsController
+ .showScreenshotActionsNotification(uri, actions);
+ }
+ }
+ });
clearScreenshot();
} else {
saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
@Override
- void onActionsReady(PendingIntent shareAction, PendingIntent editAction) {
- mScreenshotHandler.post(() ->
- createScreenshotActionsShadeAnimation(shareAction, editAction)
- .start());
+ void onActionsReady(Uri uri, List<Notification.Action> actions) {
+ if (uri == null) {
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ } else {
+ mScreenshotHandler.post(() ->
+ createScreenshotActionsShadeAnimation(actions).start());
+ }
}
});
mScreenshotHandler.sendMessageDelayed(
@@ -679,8 +629,33 @@
return anim;
}
- private ValueAnimator createScreenshotActionsShadeAnimation(
- PendingIntent shareAction, PendingIntent editAction) {
+ private ValueAnimator createScreenshotActionsShadeAnimation(List<Notification.Action> actions) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ mActionsView.removeAllViews();
+
+ for (Notification.Action action : actions) {
+ TextView actionChip = (TextView) inflater.inflate(
+ R.layout.global_screenshot_action_chip, mActionsView, false);
+ actionChip.setText(action.title);
+ actionChip.setOnClickListener(v -> {
+ try {
+ action.actionIntent.send();
+ clearScreenshot();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG,
+ String.format("Intent cancelled (title: %s)", action.title), e);
+ }
+ });
+ mActionsView.addView(actionChip);
+ }
+ TextView scrollChip = (TextView) inflater.inflate(
+ R.layout.global_screenshot_action_chip, mActionsView, false);
+ Toast scrollNotImplemented = Toast.makeText(
+ mContext, "Not implemented", Toast.LENGTH_SHORT);
+ scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate
+ scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
+ mActionsView.addView(scrollChip);
+
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
mActionsView.setY(mDisplayMetrics.heightPixels);
mActionsView.setVisibility(VISIBLE);
@@ -698,158 +673,11 @@
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mScreenshotView.requestFocus();
- mShareAction.setOnClickListener(v -> {
- try {
- shareAction.send();
- clearScreenshot();
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Share intent cancelled", e);
- }
- });
- mEditAction.setOnClickListener(v -> {
- try {
- editAction.send();
- clearScreenshot();
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Edit intent cancelled", e);
- }
- });
- Toast scrollNotImplemented = Toast.makeText(
- mContext, "Not implemented", Toast.LENGTH_SHORT);
- mScrollAction.setOnClickListener(v -> scrollNotImplemented.show());
}
});
return animator;
}
- static void notifyScreenshotError(Context context, NotificationManager nManager, int msgResId) {
- Resources r = context.getResources();
- String errorMsg = r.getString(msgResId);
-
- // Repurpose the existing notification to notify the user of the error
- Notification.Builder b = new Notification.Builder(context, NotificationChannels.ALERTS)
- .setTicker(r.getString(R.string.screenshot_failed_title))
- .setContentTitle(r.getString(R.string.screenshot_failed_title))
- .setContentText(errorMsg)
- .setSmallIcon(R.drawable.stat_notify_image_error)
- .setWhen(System.currentTimeMillis())
- .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
- .setCategory(Notification.CATEGORY_ERROR)
- .setAutoCancel(true)
- .setColor(context.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- final Intent intent = dpm.createAdminSupportIntent(
- DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
- if (intent != null) {
- final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
- context, 0, intent, 0, null, UserHandle.CURRENT);
- b.setContentIntent(pendingIntent);
- }
-
- SystemUI.overrideNotificationAppName(context, b, true);
-
- Notification n = new Notification.BigTextStyle(b)
- .bigText(errorMsg)
- .build();
- nManager.notify(SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
- }
-
- @VisibleForTesting
- static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId,
- Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
- boolean smartActionsEnabled, boolean isManagedProfile) {
- if (!smartActionsEnabled) {
- Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
- return CompletableFuture.completedFuture(Collections.emptyList());
- }
- if (image.getConfig() != Bitmap.Config.HARDWARE) {
- Slog.w(TAG, String.format(
- "Bitmap expected: Hardware, Bitmap found: %s. Returning empty list.",
- image.getConfig()));
- return CompletableFuture.completedFuture(Collections.emptyList());
- }
-
- Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
- CompletableFuture<List<Notification.Action>> smartActionsFuture;
- long startTimeMs = SystemClock.uptimeMillis();
- try {
- ActivityManager.RunningTaskInfo runningTask =
- ActivityManagerWrapper.getInstance().getRunningTask();
- ComponentName componentName =
- (runningTask != null && runningTask.topActivity != null)
- ? runningTask.topActivity
- : new ComponentName("", "");
- smartActionsFuture = smartActionsProvider.getActions(screenshotId, image,
- componentName,
- isManagedProfile);
- } catch (Throwable e) {
- long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
- smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
- Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
- notifyScreenshotOp(screenshotId, smartActionsProvider,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
- waitTimeMs);
- }
- return smartActionsFuture;
- }
-
- @VisibleForTesting
- static List<Notification.Action> getSmartActions(String screenshotId,
- CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
- ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
- long startTimeMs = SystemClock.uptimeMillis();
- try {
- List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
- TimeUnit.MILLISECONDS);
- long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
- Slog.d(TAG, String.format("Got %d smart actions. Wait time: %d ms",
- actions.size(), waitTimeMs));
- notifyScreenshotOp(screenshotId, smartActionsProvider,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
- waitTimeMs);
- return actions;
- } catch (Throwable e) {
- long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
- Slog.e(TAG, String.format("Error getting smart actions. Wait time: %d ms", waitTimeMs),
- e);
- ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status =
- (e instanceof TimeoutException)
- ? ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
- : ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR;
- notifyScreenshotOp(screenshotId, smartActionsProvider,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
- status, waitTimeMs);
- return Collections.emptyList();
- }
- }
-
- static void notifyScreenshotOp(String screenshotId,
- ScreenshotNotificationSmartActionsProvider smartActionsProvider,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOp op,
- ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) {
- try {
- smartActionsProvider.notifyOp(screenshotId, op, status, durationMs);
- } catch (Throwable e) {
- Slog.e(TAG, "Error in notifyScreenshotOp: ", e);
- }
- }
-
- static void notifyScreenshotAction(Context context, String screenshotId, String action,
- boolean isSmartAction) {
- try {
- ScreenshotNotificationSmartActionsProvider provider =
- SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
- context, THREAD_POOL_EXECUTOR, new Handler());
- provider.notifyAction(screenshotId, action, isSmartAction);
- } catch (Throwable e) {
- Slog.e(TAG, "Error in notifyScreenshotAction: ", e);
- }
- }
-
/**
* Receiver to proxy the share or edit intent, used to clean up the notification and send
* appropriate signals to the system (ie. to dismiss the keyguard if necessary).
@@ -878,7 +706,7 @@
Intent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) {
- cancelScreenshotNotification(context);
+ ScreenshotNotificationsController.cancelScreenshotNotification(context);
}
ActivityOptions opts = ActivityOptions.makeBasic();
opts.setDisallowEnterPictureInPictureWhileLaunching(
@@ -897,8 +725,8 @@
if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
String actionType = Intent.ACTION_EDIT.equals(intent.getAction()) ? ACTION_TYPE_EDIT
: ACTION_TYPE_SHARE;
- notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
- actionType, false);
+ ScreenshotSmartActions.notifyScreenshotAction(
+ context, intent.getStringExtra(EXTRA_ID), actionType, false);
}
}
}
@@ -910,7 +738,7 @@
@Override
public void onReceive(Context context, Intent intent) {
// Clear the notification only after the user has chosen a share action
- cancelScreenshotNotification(context);
+ ScreenshotNotificationsController.cancelScreenshotNotification(context);
}
}
@@ -925,15 +753,14 @@
}
// Clear the notification when the image is deleted
- cancelScreenshotNotification(context);
+ ScreenshotNotificationsController.cancelScreenshotNotification(context);
// And delete the image from the media store
final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID));
new DeleteImageInBackgroundTask(context).execute(uri);
if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
- notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
- ACTION_TYPE_DELETE,
- false);
+ ScreenshotSmartActions.notifyScreenshotAction(
+ context, intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false);
}
}
}
@@ -952,15 +779,8 @@
context.startActivityAsUser(actionIntent, opts.toBundle(),
UserHandle.CURRENT);
- notifyScreenshotAction(context, intent.getStringExtra(EXTRA_ID),
- actionType,
- true);
+ ScreenshotSmartActions.notifyScreenshotAction(
+ context, intent.getStringExtra(EXTRA_ID), actionType, true);
}
}
-
- private static void cancelScreenshotNotification(Context context) {
- final NotificationManager nm =
- (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
- nm.cancel(SystemMessage.NOTE_GLOBAL_SCREENSHOT);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 2f401e5..6bad15c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -18,7 +18,6 @@
import android.app.ActivityTaskManager;
import android.app.Notification;
-import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -30,12 +29,7 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Picture;
+import android.graphics.drawable.Icon;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.AsyncTask;
@@ -57,13 +51,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.R;
-import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
-import com.android.systemui.util.NotificationChannels;
-
-import libcore.io.IoUtils;
import java.io.File;
import java.io.IOException;
@@ -76,6 +65,7 @@
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@@ -93,22 +83,17 @@
private static final String SCREENSHOT_ID_TEMPLATE = "Screenshot_%s";
private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)";
+ private final Context mContext;
private final GlobalScreenshot.SaveImageInBackgroundData mParams;
- private final NotificationManager mNotificationManager;
- private final Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
private final String mImageFileName;
private final long mImageTime;
- private final Notification.BigPictureStyle mNotificationStyle;
- private final int mImageWidth;
- private final int mImageHeight;
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
private final String mScreenshotId;
private final boolean mSmartActionsEnabled;
private final Random mRandom = new Random();
- SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data,
- NotificationManager nManager) {
- Resources r = context.getResources();
+ SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
+ mContext = context;
// Prepare all the output metadata
mParams = data;
@@ -129,143 +114,6 @@
// If smart actions is not enabled use empty implementation.
mSmartActionsProvider = new ScreenshotNotificationSmartActionsProvider();
}
-
- // Create the large notification icon
- mImageWidth = data.image.getWidth();
- mImageHeight = data.image.getHeight();
- int iconSize = data.iconSize;
- int previewWidth = data.previewWidth;
- int previewHeight = data.previewheight;
-
- Paint paint = new Paint();
- ColorMatrix desat = new ColorMatrix();
- desat.setSaturation(0.25f);
- paint.setColorFilter(new ColorMatrixColorFilter(desat));
- Matrix matrix = new Matrix();
- int overlayColor = 0x40FFFFFF;
-
- matrix.setTranslate((previewWidth - mImageWidth) / 2,
- (previewHeight - mImageHeight) / 2);
- Bitmap picture = generateAdjustedHwBitmap(data.image, previewWidth, previewHeight,
- matrix, paint, overlayColor);
-
- // Note, we can't use the preview for the small icon, since it is non-square
- float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight);
- matrix.setScale(scale, scale);
- matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2,
- (iconSize - (scale * mImageHeight)) / 2);
- Bitmap icon = generateAdjustedHwBitmap(data.image, iconSize, iconSize, matrix, paint,
- overlayColor);
-
- mNotificationManager = nManager;
- final long now = System.currentTimeMillis();
-
- // Setup the notification
- mNotificationStyle = new Notification.BigPictureStyle()
- .bigPicture(picture.createAshmemBitmap());
-
- // The public notification will show similar info but with the actual screenshot omitted
- mPublicNotificationBuilder =
- new Notification.Builder(context, NotificationChannels.SCREENSHOTS_HEADSUP)
- .setContentTitle(r.getString(R.string.screenshot_saving_title))
- .setSmallIcon(R.drawable.stat_notify_image)
- .setCategory(Notification.CATEGORY_PROGRESS)
- .setWhen(now)
- .setShowWhen(true)
- .setColor(r.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- SystemUI.overrideNotificationAppName(context, mPublicNotificationBuilder, true);
-
- mNotificationBuilder = new Notification.Builder(context,
- NotificationChannels.SCREENSHOTS_HEADSUP)
- .setContentTitle(r.getString(R.string.screenshot_saving_title))
- .setSmallIcon(R.drawable.stat_notify_image)
- .setWhen(now)
- .setShowWhen(true)
- .setColor(r.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setStyle(mNotificationStyle)
- .setPublicVersion(mPublicNotificationBuilder.build());
- mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
- SystemUI.overrideNotificationAppName(context, mNotificationBuilder, true);
-
- mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
- mNotificationBuilder.build());
-
- /**
- * NOTE: The following code prepares the notification builder for updating the
- * notification after the screenshot has been written to disk.
- */
-
- // On the tablet, the large icon makes the notification appear as if it is clickable
- // (and on small devices, the large icon is not shown) so defer showing the large icon
- // until we compose the final post-save notification below.
- mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
- // But we still don't set it for the expanded view, allowing the smallIcon to show here.
- mNotificationStyle.bigLargeIcon((Bitmap) null);
- }
-
- private int getUserHandleOfForegroundApplication(Context context) {
- // This logic matches
- // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
- try {
- return ActivityTaskManager.getService().getLastResumedActivityUserId();
- } catch (RemoteException e) {
- Slog.w(TAG, "getUserHandleOfForegroundApplication: ", e);
- return context.getUserId();
- }
- }
-
- private boolean isManagedProfile(Context context) {
- UserManager manager = UserManager.get(context);
- UserInfo info = manager.getUserInfo(getUserHandleOfForegroundApplication(context));
- return info.isManagedProfile();
- }
-
- private List<Notification.Action> buildSmartActions(
- List<Notification.Action> actions, Context context) {
- List<Notification.Action> broadcastActions = new ArrayList<>();
- for (Notification.Action action : actions) {
- // Proxy smart actions through {@link GlobalScreenshot.SmartActionsReceiver}
- // for logging smart actions.
- Bundle extras = action.getExtras();
- String actionType = extras.getString(
- ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
- ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
- Intent intent = new Intent(context,
- GlobalScreenshot.SmartActionsReceiver.class).putExtra(
- GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent);
- addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
- PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
- mRandom.nextInt(),
- intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
- broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
- broadcastIntent).setContextual(true).addExtras(extras).build());
- }
- return broadcastActions;
- }
-
- private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
- boolean smartActionsEnabled) {
- intent
- .putExtra(GlobalScreenshot.EXTRA_ACTION_TYPE, actionType)
- .putExtra(GlobalScreenshot.EXTRA_ID, screenshotId)
- .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
- }
-
- /**
- * Generates a new hardware bitmap with specified values, copying the content from the
- * passed in bitmap.
- */
- private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
- Paint paint, int color) {
- Picture picture = new Picture();
- Canvas canvas = picture.beginRecording(width, height);
- canvas.drawColor(color);
- canvas.drawBitmap(bitmap, matrix, paint);
- picture.endRecording();
- return Bitmap.createBitmap(picture);
}
@Override
@@ -278,15 +126,15 @@
// so bump it back up so that we save a little quicker.
Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
- Context context = mParams.context;
- ContentResolver resolver = context.getContentResolver();
+ ContentResolver resolver = mContext.getContentResolver();
Bitmap image = mParams.image;
- Resources r = context.getResources();
+ Resources r = mContext.getResources();
try {
CompletableFuture<List<Notification.Action>> smartActionsFuture =
- GlobalScreenshot.getSmartActionsFuture(mScreenshotId, image,
- mSmartActionsProvider, mSmartActionsEnabled, isManagedProfile(context));
+ ScreenshotSmartActions.getSmartActionsFuture(
+ mScreenshotId, image, mSmartActionsProvider,
+ mSmartActionsEnabled, isManagedProfile(mContext));
// Save the screenshot to the MediaStore
final ContentValues values = new ContentValues();
@@ -347,8 +195,9 @@
throw e;
}
- populateNotificationActions(context, r, uri, smartActionsFuture, mNotificationBuilder);
-
+ List<Notification.Action> actions =
+ populateNotificationActions(mContext, r, uri, smartActionsFuture);
+ mParams.mActionsReadyListener.onActionsReady(uri, actions);
mParams.imageUri = uri;
mParams.image = null;
mParams.errorMsgResId = 0;
@@ -358,6 +207,7 @@
Slog.e(TAG, "unable to save screenshot", e);
mParams.clearImage();
mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
+ mParams.mActionsReadyListener.onActionsReady(null, null);
}
// Recycle the bitmap data
@@ -368,10 +218,24 @@
return null;
}
+ @Override
+ protected void onPostExecute(Void params) {
+ mParams.finisher.accept(mParams.imageUri);
+ }
+
+ @Override
+ protected void onCancelled(Void params) {
+ // If we are cancelled while the task is running in the background, we may get null
+ // params. The finisher is expected to always be called back, so just use the baked-in
+ // params from the ctor in any case.
+ mParams.mActionsReadyListener.onActionsReady(null, null);
+ mParams.finisher.accept(null);
+ mParams.clearImage();
+ }
+
@VisibleForTesting
- void populateNotificationActions(Context context, Resources r, Uri uri,
- CompletableFuture<List<Notification.Action>> smartActionsFuture,
- Notification.Builder notificationBuilder) {
+ List<Notification.Action> populateNotificationActions(Context context, Resources r, Uri uri,
+ CompletableFuture<List<Notification.Action>> smartActionsFuture) {
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
// order to do some common work like dismissing the keyguard and sending
// closeSystemWindows
@@ -399,10 +263,10 @@
PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
- Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null,
- chooserAction.getIntentSender())
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
- .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ Intent sharingChooserIntent =
+ Intent.createChooser(sharingIntent, null, chooserAction.getIntentSender())
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// Create a share action for the notification
PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
@@ -414,10 +278,10 @@
mSmartActionsEnabled)
.setAction(Intent.ACTION_SEND),
PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
- R.drawable.ic_screenshot_share,
+ Icon.createWithResource(r, R.drawable.ic_screenshot_share),
r.getString(com.android.internal.R.string.share), shareAction);
- notificationBuilder.addAction(shareActionBuilder.build());
// Create an edit intent, if a specific package is provided as the editor, then
// launch that directly
@@ -443,12 +307,8 @@
.setAction(Intent.ACTION_EDIT),
PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
- R.drawable.ic_screenshot_edit,
+ Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
- notificationBuilder.addAction(editActionBuilder.build());
- if (mParams.mActionsReadyListener != null) {
- mParams.mActionsReadyListener.onActionsReady(shareAction, editAction);
- }
// Create a delete action for the notification
PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
@@ -459,88 +319,73 @@
mSmartActionsEnabled),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
- R.drawable.ic_screenshot_delete,
+ Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
r.getString(com.android.internal.R.string.delete), deleteAction);
- notificationBuilder.addAction(deleteActionBuilder.build());
+ ArrayList<Notification.Action> actions = new ArrayList<>(
+ Arrays.asList(shareActionBuilder.build(), editActionBuilder.build(),
+ deleteActionBuilder.build()));
if (mSmartActionsEnabled) {
- int timeoutMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags
- .SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
+ int timeoutMs = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS,
1000);
- List<Notification.Action> smartActions = GlobalScreenshot.getSmartActions(mScreenshotId,
- smartActionsFuture, timeoutMs, mSmartActionsProvider);
- smartActions = buildSmartActions(smartActions, context);
- for (Notification.Action action : smartActions) {
- notificationBuilder.addAction(action);
- }
+ actions.addAll(buildSmartActions(
+ ScreenshotSmartActions.getSmartActions(
+ mScreenshotId, smartActionsFuture, timeoutMs, mSmartActionsProvider),
+ context));
+ }
+ return actions;
+ }
+
+ private int getUserHandleOfForegroundApplication(Context context) {
+ // This logic matches
+ // com.android.systemui.statusbar.phone.PhoneStatusBarPolicy#updateManagedProfile
+ try {
+ return ActivityTaskManager.getService().getLastResumedActivityUserId();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "getUserHandleOfForegroundApplication: ", e);
+ return context.getUserId();
}
}
- @Override
- protected void onPostExecute(Void params) {
- if (mParams.errorMsgResId != 0) {
- // Show a message that we've failed to save the image to disk
- GlobalScreenshot.notifyScreenshotError(mParams.context, mNotificationManager,
- mParams.errorMsgResId);
- } else {
- if (mParams.mActionsReadyListener != null) {
- // Cancel the "saving screenshot" notification
- mNotificationManager.cancel(
- SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
- } else {
- // Show the final notification to indicate screenshot saved
- Context context = mParams.context;
- Resources r = context.getResources();
+ private boolean isManagedProfile(Context context) {
+ UserManager manager = UserManager.get(context);
+ UserInfo info = manager.getUserInfo(getUserHandleOfForegroundApplication(context));
+ return info.isManagedProfile();
+ }
- // Create the intent to show the screenshot in gallery
- Intent launchIntent = new Intent(Intent.ACTION_VIEW);
- launchIntent.setDataAndType(mParams.imageUri, "image/png");
- launchIntent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- final long now = System.currentTimeMillis();
-
- // Update the text and the icon for the existing notification
- mPublicNotificationBuilder
- .setContentTitle(r.getString(R.string.screenshot_saved_title))
- .setContentText(r.getString(R.string.screenshot_saved_text))
- .setContentIntent(
- PendingIntent.getActivity(mParams.context, 0, launchIntent, 0))
- .setWhen(now)
- .setAutoCancel(true)
- .setColor(context.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- mNotificationBuilder
- .setContentTitle(r.getString(R.string.screenshot_saved_title))
- .setContentText(r.getString(R.string.screenshot_saved_text))
- .setContentIntent(PendingIntent.getActivity(mParams.context, 0,
- launchIntent, 0))
- .setWhen(now)
- .setAutoCancel(true)
- .setColor(context.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setPublicVersion(mPublicNotificationBuilder.build())
- .setFlag(Notification.FLAG_NO_CLEAR, false);
-
- mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
- mNotificationBuilder.build());
- }
+ private List<Notification.Action> buildSmartActions(
+ List<Notification.Action> actions, Context context) {
+ List<Notification.Action> broadcastActions = new ArrayList<>();
+ for (Notification.Action action : actions) {
+ // Proxy smart actions through {@link GlobalScreenshot.SmartActionsReceiver}
+ // for logging smart actions.
+ Bundle extras = action.getExtras();
+ String actionType = extras.getString(
+ ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
+ ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
+ Intent intent = new Intent(context,
+ GlobalScreenshot.SmartActionsReceiver.class).putExtra(
+ GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent);
+ addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
+ PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
+ mRandom.nextInt(),
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT);
+ broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
+ broadcastIntent).setContextual(true).addExtras(extras).build());
}
- mParams.finisher.accept(mParams.imageUri);
- mParams.clearContext();
+ return broadcastActions;
}
- @Override
- protected void onCancelled(Void params) {
- // If we are cancelled while the task is running in the background, we may get null
- // params. The finisher is expected to always be called back, so just use the baked-in
- // params from the ctor in any case.
- mParams.finisher.accept(null);
- mParams.clearImage();
- mParams.clearContext();
-
- // Cancel the posted notification
- mNotificationManager.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
+ private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
+ boolean smartActionsEnabled) {
+ intent
+ .putExtra(GlobalScreenshot.EXTRA_ACTION_TYPE, actionType)
+ .putExtra(GlobalScreenshot.EXTRA_ID, screenshotId)
+ .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
}
+
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
new file mode 100644
index 0000000..42fca94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.screenshot;
+
+import static android.content.Context.NOTIFICATION_SERVICE;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Picture;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+import com.android.systemui.util.NotificationChannels;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * Convenience class to handle showing and hiding notifications while taking a screenshot.
+ */
+public class ScreenshotNotificationsController {
+ private static final String TAG = "ScreenshotNotificationManager";
+
+ private final Context mContext;
+ private final Resources mResources;
+ private final NotificationManager mNotificationManager;
+ private final Notification.BigPictureStyle mNotificationStyle;
+
+ private int mIconSize;
+ private int mPreviewWidth, mPreviewHeight;
+ private Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
+
+ @Inject
+ ScreenshotNotificationsController(Context context, WindowManager windowManager) {
+ mContext = context;
+ mResources = context.getResources();
+
+ mNotificationManager =
+ (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
+
+ mIconSize = mResources.getDimensionPixelSize(
+ android.R.dimen.notification_large_icon_height);
+
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
+
+
+ // determine the optimal preview size
+ int panelWidth = 0;
+ try {
+ panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
+ } catch (Resources.NotFoundException e) {
+ }
+ if (panelWidth <= 0) {
+ // includes notification_panel_width==match_parent (-1)
+ panelWidth = displayMetrics.widthPixels;
+ }
+ mPreviewWidth = panelWidth;
+ mPreviewHeight = mResources.getDimensionPixelSize(R.dimen.notification_max_height);
+
+ // Setup the notification
+ mNotificationStyle = new Notification.BigPictureStyle();
+ }
+
+ /**
+ * Resets the notification builders.
+ */
+ public void reset() {
+ // The public notification will show similar info but with the actual screenshot omitted
+ mPublicNotificationBuilder =
+ new Notification.Builder(mContext, NotificationChannels.SCREENSHOTS_HEADSUP);
+ mNotificationBuilder =
+ new Notification.Builder(mContext, NotificationChannels.SCREENSHOTS_HEADSUP);
+ }
+
+ /**
+ * Sets the current screenshot bitmap.
+ *
+ * @param image the bitmap of the current screenshot (used for preview)
+ */
+ public void setImage(Bitmap image) {
+ // Create the large notification icon
+ int imageWidth = image.getWidth();
+ int imageHeight = image.getHeight();
+
+ Paint paint = new Paint();
+ ColorMatrix desat = new ColorMatrix();
+ desat.setSaturation(0.25f);
+ paint.setColorFilter(new ColorMatrixColorFilter(desat));
+ Matrix matrix = new Matrix();
+ int overlayColor = 0x40FFFFFF;
+
+ matrix.setTranslate((mPreviewWidth - imageWidth) / 2f, (mPreviewHeight - imageHeight) / 2f);
+
+ Bitmap picture = generateAdjustedHwBitmap(
+ image, mPreviewWidth, mPreviewHeight, matrix, paint, overlayColor);
+
+ mNotificationStyle.bigPicture(picture.createAshmemBitmap());
+
+ // Note, we can't use the preview for the small icon, since it is non-square
+ float scale = (float) mIconSize / Math.min(imageWidth, imageHeight);
+ matrix.setScale(scale, scale);
+ matrix.postTranslate(
+ (mIconSize - (scale * imageWidth)) / 2,
+ (mIconSize - (scale * imageHeight)) / 2);
+ Bitmap icon =
+ generateAdjustedHwBitmap(image, mIconSize, mIconSize, matrix, paint, overlayColor);
+
+ /**
+ * NOTE: The following code prepares the notification builder for updating the
+ * notification after the screenshot has been written to disk.
+ */
+
+ // On the tablet, the large icon makes the notification appear as if it is clickable
+ // (and on small devices, the large icon is not shown) so defer showing the large icon
+ // until we compose the final post-save notification below.
+ mNotificationBuilder.setLargeIcon(icon.createAshmemBitmap());
+ // But we still don't set it for the expanded view, allowing the smallIcon to show here.
+ mNotificationStyle.bigLargeIcon((Bitmap) null);
+ }
+
+ /**
+ * Shows a notification to inform the user that a screenshot is currently being saved.
+ */
+ public void showSavingScreenshotNotification() {
+ final long now = System.currentTimeMillis();
+
+ mPublicNotificationBuilder
+ .setContentTitle(mResources.getString(R.string.screenshot_saving_title))
+ .setSmallIcon(R.drawable.stat_notify_image)
+ .setCategory(Notification.CATEGORY_PROGRESS)
+ .setWhen(now)
+ .setShowWhen(true)
+ .setColor(mResources.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ SystemUI.overrideNotificationAppName(mContext, mPublicNotificationBuilder, true);
+
+ mNotificationBuilder
+ .setContentTitle(mResources.getString(R.string.screenshot_saving_title))
+ .setSmallIcon(R.drawable.stat_notify_image)
+ .setWhen(now)
+ .setShowWhen(true)
+ .setColor(mResources.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setStyle(mNotificationStyle)
+ .setPublicVersion(mPublicNotificationBuilder.build());
+ mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
+ SystemUI.overrideNotificationAppName(mContext, mNotificationBuilder, true);
+
+ mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
+ mNotificationBuilder.build());
+ }
+
+ /**
+ * Shows a notification with the saved screenshot and actions that can be taken with it.
+ *
+ * @param imageUri URI for the saved image
+ * @param actions a list of notification actions which can be taken
+ */
+ public void showScreenshotActionsNotification(
+ Uri imageUri,
+ List<Notification.Action> actions) {
+ for (Notification.Action action : actions) {
+ mNotificationBuilder.addAction(action);
+ }
+
+ // Create the intent to show the screenshot in gallery
+ Intent launchIntent = new Intent(Intent.ACTION_VIEW);
+ launchIntent.setDataAndType(imageUri, "image/png");
+ launchIntent.setFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ final long now = System.currentTimeMillis();
+
+ // Update the text and the icon for the existing notification
+ mPublicNotificationBuilder
+ .setContentTitle(mResources.getString(R.string.screenshot_saved_title))
+ .setContentText(mResources.getString(R.string.screenshot_saved_text))
+ .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0))
+ .setWhen(now)
+ .setAutoCancel(true)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ mNotificationBuilder
+ .setContentTitle(mResources.getString(R.string.screenshot_saved_title))
+ .setContentText(mResources.getString(R.string.screenshot_saved_text))
+ .setContentIntent(PendingIntent.getActivity(mContext, 0, launchIntent, 0))
+ .setWhen(now)
+ .setAutoCancel(true)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setPublicVersion(mPublicNotificationBuilder.build())
+ .setFlag(Notification.FLAG_NO_CLEAR, false);
+
+ mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
+ mNotificationBuilder.build());
+ }
+
+ /**
+ * Sends a notification that the screenshot capture has failed.
+ */
+ public void notifyScreenshotError(int msgResId) {
+ Resources res = mContext.getResources();
+ String errorMsg = res.getString(msgResId);
+
+ // Repurpose the existing notification to notify the user of the error
+ Notification.Builder b = new Notification.Builder(mContext, NotificationChannels.ALERTS)
+ .setTicker(res.getString(R.string.screenshot_failed_title))
+ .setContentTitle(res.getString(R.string.screenshot_failed_title))
+ .setContentText(errorMsg)
+ .setSmallIcon(R.drawable.stat_notify_image_error)
+ .setWhen(System.currentTimeMillis())
+ .setVisibility(Notification.VISIBILITY_PUBLIC) // ok to show outside lockscreen
+ .setCategory(Notification.CATEGORY_ERROR)
+ .setAutoCancel(true)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color));
+ final DevicePolicyManager dpm =
+ (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ final Intent intent =
+ dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
+ if (intent != null) {
+ final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
+ mContext, 0, intent, 0, null, UserHandle.CURRENT);
+ b.setContentIntent(pendingIntent);
+ }
+
+ SystemUI.overrideNotificationAppName(mContext, b, true);
+
+ Notification n = new Notification.BigTextStyle(b)
+ .bigText(errorMsg)
+ .build();
+ mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
+ }
+
+ /**
+ * Cancels the current screenshot notification.
+ */
+ public void cancelNotification() {
+ mNotificationManager.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
+ }
+
+ /**
+ * Generates a new hardware bitmap with specified values, copying the content from the
+ * passed in bitmap.
+ */
+ private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
+ Paint paint, int color) {
+ Picture picture = new Picture();
+ Canvas canvas = picture.beginRecording(width, height);
+ canvas.drawColor(color);
+ canvas.drawBitmap(bitmap, matrix, paint);
+ picture.endRecording();
+ return Bitmap.createBitmap(picture);
+ }
+
+ static void cancelScreenshotNotification(Context context) {
+ final NotificationManager nm =
+ (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
+ nm.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
index fc2a1e4..522f729 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotServiceErrorReceiver.java
@@ -16,10 +16,10 @@
package com.android.systemui.screenshot;
-import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.view.WindowManager;
import com.android.systemui.R;
@@ -32,9 +32,9 @@
@Override
public void onReceive(final Context context, Intent intent) {
// Show a message that we've failed to save the image to disk
- NotificationManager nm = (NotificationManager)
- context.getSystemService(Context.NOTIFICATION_SERVICE);
- GlobalScreenshot.notifyScreenshotError(context, nm,
- R.string.screenshot_failed_to_save_unknown_text);
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ ScreenshotNotificationsController controller =
+ new ScreenshotNotificationsController(context, wm);
+ controller.notifyScreenshotError(R.string.screenshot_failed_to_save_unknown_text);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
new file mode 100644
index 0000000..e76e37e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.screenshot;
+
+import static android.os.AsyncTask.THREAD_POOL_EXECUTOR;
+
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.SystemUIFactory;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Collects the static functions for retrieving and acting on smart actions.
+ */
+public class ScreenshotSmartActions {
+ private static final String TAG = "ScreenshotSmartActions";
+
+ @VisibleForTesting
+ static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(String screenshotId,
+ Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider,
+ boolean smartActionsEnabled, boolean isManagedProfile) {
+ if (!smartActionsEnabled) {
+ Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
+ return CompletableFuture.completedFuture(Collections.emptyList());
+ }
+ if (image.getConfig() != Bitmap.Config.HARDWARE) {
+ Slog.w(TAG, String.format(
+ "Bitmap expected: Hardware, Bitmap found: %s. Returning empty list.",
+ image.getConfig()));
+ return CompletableFuture.completedFuture(Collections.emptyList());
+ }
+
+ Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
+ CompletableFuture<List<Notification.Action>> smartActionsFuture;
+ long startTimeMs = SystemClock.uptimeMillis();
+ try {
+ ActivityManager.RunningTaskInfo runningTask =
+ ActivityManagerWrapper.getInstance().getRunningTask();
+ ComponentName componentName =
+ (runningTask != null && runningTask.topActivity != null)
+ ? runningTask.topActivity
+ : new ComponentName("", "");
+ smartActionsFuture = smartActionsProvider.getActions(screenshotId, image,
+ componentName,
+ isManagedProfile);
+ } catch (Throwable e) {
+ long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+ smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
+ Slog.e(TAG, "Failed to get future for screenshot notification smart actions.", e);
+ notifyScreenshotOp(screenshotId, smartActionsProvider,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
+ waitTimeMs);
+ }
+ return smartActionsFuture;
+ }
+
+ @VisibleForTesting
+ static List<Notification.Action> getSmartActions(String screenshotId,
+ CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
+ ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
+ long startTimeMs = SystemClock.uptimeMillis();
+ try {
+ List<Notification.Action> actions = smartActionsFuture.get(timeoutMs,
+ TimeUnit.MILLISECONDS);
+ long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+ Slog.d(TAG, String.format("Got %d smart actions. Wait time: %d ms",
+ actions.size(), waitTimeMs));
+ notifyScreenshotOp(screenshotId, smartActionsProvider,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
+ waitTimeMs);
+ return actions;
+ } catch (Throwable e) {
+ long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
+ Slog.e(TAG, String.format("Error getting smart actions. Wait time: %d ms", waitTimeMs),
+ e);
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status =
+ (e instanceof TimeoutException)
+ ? ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
+ : ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR;
+ notifyScreenshotOp(screenshotId, smartActionsProvider,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+ status, waitTimeMs);
+ return Collections.emptyList();
+ }
+ }
+
+ static void notifyScreenshotOp(String screenshotId,
+ ScreenshotNotificationSmartActionsProvider smartActionsProvider,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp op,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) {
+ try {
+ smartActionsProvider.notifyOp(screenshotId, op, status, durationMs);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Error in notifyScreenshotOp: ", e);
+ }
+ }
+
+ static void notifyScreenshotAction(Context context, String screenshotId, String action,
+ boolean isSmartAction) {
+ try {
+ ScreenshotNotificationSmartActionsProvider provider =
+ SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
+ context, THREAD_POOL_EXECUTOR, new Handler());
+ provider.notifyAction(screenshotId, action, isSmartAction);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Error in notifyScreenshotAction: ", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 6243b4b..29b96a9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -42,14 +42,11 @@
@Override
public void handleMessage(Message msg) {
final Messenger callback = msg.replyTo;
- Consumer<Uri> finisher = new Consumer<Uri>() {
- @Override
- public void accept(Uri uri) {
- Message reply = Message.obtain(null, 1, uri);
- try {
- callback.send(reply);
- } catch (RemoteException e) {
- }
+ Consumer<Uri> finisher = uri -> {
+ Message reply = Message.obtain(null, 1, uri);
+ try {
+ callback.send(reply);
+ } catch (RemoteException e) {
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index f5c1587..af218c49 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -160,6 +160,7 @@
private boolean mHovering = false;
private boolean mShowActiveStreamOnly;
private boolean mConfigChanged = false;
+ private boolean mIsAnimatingDismiss = false;
private boolean mHasSeenODICaptionsTooltip;
private ViewStub mODICaptionsTooltipViewStub;
private View mODICaptionsTooltipView = null;
@@ -693,6 +694,7 @@
initSettingsH();
mShowing = true;
+ mIsAnimatingDismiss = false;
mDialog.show();
Events.writeEvent(Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
mController.notifyVisible(true);
@@ -737,6 +739,10 @@
}
mHandler.removeMessages(H.DISMISS);
mHandler.removeMessages(H.SHOW);
+ if (mIsAnimatingDismiss) {
+ return;
+ }
+ mIsAnimatingDismiss = true;
mDialogView.animate().cancel();
if (mShowing) {
mShowing = false;
@@ -752,6 +758,7 @@
.withEndAction(() -> mHandler.postDelayed(() -> {
mDialog.dismiss();
tryToRemoveCaptionsTooltip();
+ mIsAnimatingDismiss = false;
}, 50));
if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2.0f);
animator.start();
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
index aa56ffb..aed90eb 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayWindowController.java
@@ -99,13 +99,17 @@
if (mDisplays.get(displayId) != null) {
return;
}
+ Display display = getDisplay(displayId);
+ if (display == null) {
+ // It's likely that the display is private to some app and thus not
+ // accessible by system-ui.
+ return;
+ }
DisplayRecord record = new DisplayRecord();
record.mDisplayId = displayId;
- // TODO(b/146566787): disabled for MultiDisplayActivityLaunchTests
- // Display display = getDisplay(displayId);
- // record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext
- // : mContext.createDisplayContext(display);
- // record.mDisplayLayout = new DisplayLayout(record.mContext, display);
+ record.mContext = (displayId == Display.DEFAULT_DISPLAY) ? mContext
+ : mContext.createDisplayContext(display);
+ record.mDisplayLayout = new DisplayLayout(record.mContext, display);
mDisplays.put(displayId, record);
for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
mDisplayChangedListeners.get(i).onDisplayAdded(displayId);
@@ -124,14 +128,13 @@
+ " display.");
return;
}
- // TODO(b/146566787): disabled for MultiDisplaySystemDecorationTests
- // Display display = getDisplay(displayId);
- // Context perDisplayContext = mContext;
- // if (displayId != Display.DEFAULT_DISPLAY) {
- // perDisplayContext = mContext.createDisplayContext(display);
- // }
- // dr.mContext = perDisplayContext.createConfigurationContext(newConfig);
- // dr.mDisplayLayout = new DisplayLayout(dr.mContext, display);
+ Display display = getDisplay(displayId);
+ Context perDisplayContext = mContext;
+ if (displayId != Display.DEFAULT_DISPLAY) {
+ perDisplayContext = mContext.createDisplayContext(display);
+ }
+ dr.mContext = perDisplayContext.createConfigurationContext(newConfig);
+ dr.mDisplayLayout = new DisplayLayout(dr.mContext, display);
for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
displayId, newConfig);
@@ -144,6 +147,9 @@
public void onDisplayRemoved(int displayId) {
mHandler.post(() -> {
synchronized (mDisplays) {
+ if (mDisplays.get(displayId) == null) {
+ return;
+ }
for (int i = mDisplayChangedListeners.size() - 1; i >= 0; --i) {
mDisplayChangedListeners.get(i).onDisplayRemoved(displayId);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
index b4a60d6..a5722e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/glwallpaper/EglHelperTest.java
@@ -52,7 +52,7 @@
@Test
public void testInit_finish() {
- mEglHelper.init(mSurfaceHolder);
+ mEglHelper.init(mSurfaceHolder, false /* wideColorGamut */);
mEglHelper.finish();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 6d85d37..2c7cee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.screenshot;
-import static android.content.Context.NOTIFICATION_SERVICE;
-
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -29,7 +27,6 @@
import static org.mockito.Mockito.when;
import android.app.Notification;
-import android.app.NotificationManager;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
@@ -42,7 +39,6 @@
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.NotificationChannels;
import org.junit.Assert;
import org.junit.Before;
@@ -84,7 +80,7 @@
eq(false))).thenThrow(
RuntimeException.class);
CompletableFuture<List<Notification.Action>> smartActionsFuture =
- GlobalScreenshot.getSmartActionsFuture("", bitmap,
+ ScreenshotSmartActions.getSmartActionsFuture("", bitmap,
smartActionsProvider, true, false);
Assert.assertNotNull(smartActionsFuture);
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
@@ -101,7 +97,7 @@
int timeoutMs = 1000;
when(smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)).thenThrow(
RuntimeException.class);
- List<Notification.Action> actions = GlobalScreenshot.getSmartActions(
+ List<Notification.Action> actions = ScreenshotSmartActions.getSmartActions(
"", smartActionsFuture, timeoutMs, mSmartActionsProvider);
Assert.assertEquals(Collections.emptyList(), actions);
}
@@ -112,7 +108,7 @@
throws Exception {
doThrow(RuntimeException.class).when(mSmartActionsProvider).notifyOp(any(), any(), any(),
anyLong());
- GlobalScreenshot.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1);
+ ScreenshotSmartActions.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1);
}
// Tests for a non-hardware bitmap, ScreenshotNotificationSmartActionsProvider is never invoked
@@ -123,7 +119,7 @@
Bitmap bitmap = mock(Bitmap.class);
when(bitmap.getConfig()).thenReturn(Bitmap.Config.RGB_565);
CompletableFuture<List<Notification.Action>> smartActionsFuture =
- GlobalScreenshot.getSmartActionsFuture("", bitmap,
+ ScreenshotSmartActions.getSmartActionsFuture("", bitmap,
mSmartActionsProvider, true, true);
verify(mSmartActionsProvider, never()).getActions(any(), any(), any(),
eq(false));
@@ -137,7 +133,7 @@
public void testScreenshotNotificationSmartActionsProviderInvokedOnce() {
Bitmap bitmap = mock(Bitmap.class);
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
- GlobalScreenshot.getSmartActionsFuture("", bitmap, mSmartActionsProvider,
+ ScreenshotSmartActions.getSmartActionsFuture("", bitmap, mSmartActionsProvider,
true, true);
verify(mSmartActionsProvider, times(1))
.getActions(any(), any(), any(), eq(true));
@@ -153,7 +149,7 @@
SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
mContext, null, mHandler);
CompletableFuture<List<Notification.Action>> smartActionsFuture =
- GlobalScreenshot.getSmartActionsFuture("", bitmap,
+ ScreenshotSmartActions.getSmartActionsFuture("", bitmap,
actionsProvider,
true, true);
Assert.assertNotNull(smartActionsFuture);
@@ -167,31 +163,23 @@
if (Looper.myLooper() == null) {
Looper.prepare();
}
- NotificationManager notificationManager =
- (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE);
+
GlobalScreenshot.SaveImageInBackgroundData
data = new GlobalScreenshot.SaveImageInBackgroundData();
- data.context = mContext;
data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
- data.iconSize = 10;
data.finisher = null;
data.mActionsReadyListener = null;
- data.previewWidth = 10;
- data.previewheight = 10;
- SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data,
- notificationManager);
- Notification.Builder notificationBuilder = new Notification.Builder(mContext,
- NotificationChannels.SCREENSHOTS_HEADSUP);
- task.populateNotificationActions(mContext, mContext.getResources(),
+ SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
+ List<Notification.Action> actions = task.populateNotificationActions(
+ mContext, mContext.getResources(),
Uri.parse("Screenshot_123.png"),
- CompletableFuture.completedFuture(Collections.emptyList()), notificationBuilder);
+ CompletableFuture.completedFuture(Collections.emptyList()));
- Notification notification = notificationBuilder.build();
- Assert.assertEquals(notification.actions.length, 3);
+ Assert.assertEquals(actions.size(), 3);
boolean isShareFound = false;
boolean isEditFound = false;
boolean isDeleteFound = false;
- for (Notification.Action action : notification.actions) {
+ for (Notification.Action action : actions) {
Intent intent = action.actionIntent.getIntent();
Assert.assertNotNull(intent);
Bundle bundle = intent.getExtras();
diff --git a/packages/Tethering/CleanSpec.mk b/packages/Tethering/CleanSpec.mk
deleted file mode 100644
index 30bdd58..0000000
--- a/packages/Tethering/CleanSpec.mk
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# If you don't need to do a full clean build but would like to touch
-# a file or delete some intermediate files, add a clean step to the end
-# of the list. These steps will only be run once, if they haven't been
-# run before.
-#
-# E.g.:
-# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
-# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
-#
-# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
-# files that are missing or have been moved.
-#
-# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
-# Use $(OUT_DIR) to refer to the "out" directory.
-#
-# If you need to re-do something that's already mentioned, just copy
-# the command and add it to the bottom of the list. E.g., if a change
-# that you made last week required touching a file and a change you
-# made today requires touching the same file, just copy the old
-# touch step and add it to the end of the list.
-#
-# *****************************************************************
-# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THE BANNER
-# *****************************************************************
-
-# For example:
-#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
-#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
-#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
-#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
-
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/Tethering)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/priv-app/InProcessTethering)
-$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/InProcessTethering*)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/APPS/InProcessTethering*)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system_other/system/priv-app/InProcessTethering)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/apex/com.android.tethering)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/apex/com.android.tethering.apex)
-$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/ETC/com.android.tethering*)
-
-# ******************************************************************
-# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER
-# ******************************************************************
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index 3eb9049..eecc101 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -42,7 +42,7 @@
IconPackRoundedLauncherOverlay \
IconPackRoundedSettingsOverlay \
IconPackRoundedSystemUIOverlay \
- IconPackRoundedThemePickerUIOverlay \
+ IconPackRoundedThemePickerOverlay \
IconShapeRoundedRectOverlay \
IconShapeSquircleOverlay \
IconShapeTeardropOverlay \
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 21c4784..00fc6d0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -355,8 +355,15 @@
mConnectedDevices.replace(key, di);
}
}
- if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
- BtHelper.getName(btDevice), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
+ final int res = AudioSystem.handleDeviceConfigChange(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
+ BtHelper.getName(btDevice), a2dpCodec);
+
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "APM handleDeviceConfigChange failed for A2DP device addr="
+ + address + " codec=" + a2dpCodec).printLog(TAG));
+
int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
// force A2DP device disconnection in case of error so that AudioService state is
// consistent with audio policy manager state
@@ -364,6 +371,10 @@
btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
false /* suppressNoisyIntent */, musicDevice,
-1 /* a2dpVolume */);
+ } else {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "APM handleDeviceConfigChange success for A2DP device addr="
+ + address + " codec=" + a2dpCodec).printLog(TAG));
}
}
}
@@ -702,8 +713,20 @@
mDeviceBroker.setBluetoothA2dpOnInt(true, eventSource);
// at this point there could be another A2DP device already connected in APM, but it
// doesn't matter as this new one will overwrite the previous one
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ final int res = AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
+
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "APM failed to make available A2DP device addr=" + address
+ + " error=" + res).printLog(TAG));
+ // TODO: connection failed, stop here
+ // TODO: return;
+ } else {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "A2DP device addr=" + address + " now available").printLog(TAG));
+ }
+
// Reset A2DP suspend state each time a new sink is connected
AudioSystem.setParameters("A2dpSuspended=false");
@@ -738,8 +761,19 @@
// device to remove was visible by APM, update APM
mDeviceBroker.setAvrcpAbsoluteVolumeSupported(false);
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+ final int res = AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
AudioSystem.DEVICE_STATE_UNAVAILABLE, address, "", a2dpCodec);
+
+ if (res != AudioSystem.AUDIO_STATUS_OK) {
+ AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
+ "APM failed to make unavailable A2DP device addr=" + address
+ + " error=" + res).printLog(TAG));
+ // TODO: failed to disconnect, stop here
+ // TODO: return;
+ } else {
+ AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
+ "A2DP device addr=" + address + " made unavailable")).printLog(TAG));
+ }
mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
// Remove A2DP routes as well
setCurrentAudioRouteNameIfPossible(null);
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index fdbb7d9..73a815a 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -27,6 +27,9 @@
import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
+import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
import android.content.integrity.AtomicFormula;
import android.content.integrity.CompoundFormula;
@@ -36,36 +39,16 @@
import com.android.server.integrity.model.BitOutputStream;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
public class RuleBinarySerializer implements RuleSerializer {
- // Get the byte representation for a list of rules, and write them to an output stream.
- @Override
- public void serialize(
- List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream)
- throws RuleSerializeException {
- try {
- BitOutputStream bitOutputStream = new BitOutputStream();
-
- int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
- bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
- outputStream.write(bitOutputStream.toByteArray());
-
- for (Rule rule : rules) {
- bitOutputStream.clear();
- serializeRule(rule, bitOutputStream);
- outputStream.write(bitOutputStream.toByteArray());
- }
- } catch (Exception e) {
- throw new RuleSerializeException(e.getMessage(), e);
- }
- }
-
// Get the byte representation for a list of rules.
@Override
public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
@@ -79,6 +62,45 @@
}
}
+ // Get the byte representation for a list of rules, and write them to an output stream.
+ @Override
+ public void serialize(
+ List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream)
+ throws RuleSerializeException {
+ try {
+ // Determine the indexing groups and the order of the rules within each indexed group.
+ Map<Integer, List<Rule>> indexedRules =
+ RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
+
+ serializeRuleFileMetadata(formatVersion, outputStream);
+
+ serializeIndexedRules(indexedRules.get(PACKAGE_NAME_INDEXED), outputStream);
+ serializeIndexedRules(indexedRules.get(APP_CERTIFICATE_INDEXED), outputStream);
+ serializeIndexedRules(indexedRules.get(NOT_INDEXED), outputStream);
+ } catch (Exception e) {
+ throw new RuleSerializeException(e.getMessage(), e);
+ }
+ }
+
+ private void serializeRuleFileMetadata(
+ Optional<Integer> formatVersion, OutputStream outputStream) throws IOException {
+ int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
+
+ BitOutputStream bitOutputStream = new BitOutputStream();
+ bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
+ outputStream.write(bitOutputStream.toByteArray());
+ }
+
+ private void serializeIndexedRules(List<Rule> rules, OutputStream outputStream)
+ throws IOException {
+ BitOutputStream bitOutputStream = new BitOutputStream();
+ for (Rule rule : rules) {
+ bitOutputStream.clear();
+ serializeRule(rule, bitOutputStream);
+ outputStream.write(bitOutputStream.toByteArray());
+ }
+ }
+
private void serializeRule(Rule rule, BitOutputStream bitOutputStream) {
if (rule == null) {
throw new IllegalArgumentException("Null rule can not be serialized");
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
index 7d5e836..f9c7912 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
@@ -62,7 +62,14 @@
// Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the
// entries sorted by their index key.
for (Rule rule : rules) {
- RuleIndexingDetails indexingDetails = getIndexingDetails(rule.getFormula());
+ RuleIndexingDetails indexingDetails;
+ try {
+ indexingDetails = getIndexingDetails(rule.getFormula());
+ } catch (Exception e) {
+ throw new IllegalArgumentException(
+ String.format("Malformed rule identified. [%s]", rule.toString()));
+ }
+
String ruleKey =
indexingDetails.getIndexType() != NOT_INDEXED
? indexingDetails.getRuleKey()
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9114996..1503282 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1811,7 +1811,7 @@
} else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else if (taskSwitch && allowTaskSnapshot) {
- return snapshot == null ? STARTING_WINDOW_TYPE_SPLASH_SCREEN
+ return snapshot == null ? STARTING_WINDOW_TYPE_NONE
: snapshotOrientationSameAsTask(snapshot) || fromRecents
? STARTING_WINDOW_TYPE_SNAPSHOT : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
} else {
@@ -6060,6 +6060,13 @@
void registerRemoteAnimations(RemoteAnimationDefinition definition) {
mRemoteAnimationDefinition = definition;
+ if (definition != null) {
+ definition.linkToDeath(this::unregisterRemoteAnimations);
+ }
+ }
+
+ void unregisterRemoteAnimations() {
+ mRemoteAnimationDefinition = null;
}
RemoteAnimationDefinition getRemoteAnimationDefinition() {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 45b4818..60f051c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4700,6 +4700,24 @@
}
@Override
+ public void unregisterRemoteAnimations(IBinder token) {
+ mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
+ "unregisterRemoteAnimations");
+ synchronized (mGlobalLock) {
+ final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+ if (r == null) {
+ return;
+ }
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ r.unregisterRemoteAnimations();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
+
+ @Override
public void registerRemoteAnimationForNextActivityStart(String packageName,
RemoteAnimationAdapter adapter) {
mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS,
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 39091a6..b255b5e 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -754,8 +754,7 @@
// Only apply the input consumer if it is enabled, it is not the target (home/recents)
// being revealed with the transition, and we are actively animating the app as a part of
// the animation
- return mInputConsumerEnabled && mTargetActivityRecord != activity
- && isAnimatingApp(activity);
+ return mInputConsumerEnabled && !isTargetApp(activity) && isAnimatingApp(activity);
}
boolean updateInputConsumerForApp(InputWindowHandle inputWindowHandle,
@@ -810,7 +809,9 @@
PooledLambda.__(ActivityRecord.class));
boolean isAnimatingApp = task.forAllActivities(f);
f.recycle();
- return isAnimatingApp;
+ if (isAnimatingApp) {
+ return true;
+ }
}
return false;
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 456068c..3182a72 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -281,7 +281,8 @@
*
* @return true if the state of the task is ok to proceed
*/
- private boolean prepareTaskSnapshot(Task task, float scaleFraction, int pixelFormat,
+ @VisibleForTesting
+ boolean prepareTaskSnapshot(Task task, float scaleFraction, int pixelFormat,
TaskSnapshot.Builder builder) {
if (!mService.mPolicy.isScreenOn()) {
if (DEBUG_SCREENSHOT) {
@@ -339,6 +340,7 @@
&& (!activity.fillsParent() || isWindowTranslucent);
builder.setTopActivityComponent(activity.mActivityComponent);
+ builder.setPixelFormat(pixelFormat);
builder.setIsTranslucent(isTranslucent);
builder.setOrientation(activity.getTask().getConfiguration().orientation);
builder.setWindowingMode(task.getWindowingMode());
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5771f2c..1313eeb 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2691,18 +2691,18 @@
return mActivityRecord.getTask().getTaskStack().shouldIgnoreInput()
|| !mActivityRecord.mVisibleRequested
- || isAnimatingToRecents();
+ || isRecentsAnimationConsumingAppInput();
}
/**
- * Returns {@code true} if the window is animating to home as part of the recents animation.
+ * Returns {@code true} if the window is animating to home as part of the recents animation and
+ * it is consuming input from the app.
*/
- private boolean isAnimatingToRecents() {
+ private boolean isRecentsAnimationConsumingAppInput() {
final RecentsAnimationController recentsAnimationController =
mWmService.getRecentsAnimationController();
return recentsAnimationController != null
- && recentsAnimationController.isAnimatingTask(getTask())
- && !recentsAnimationController.isTargetApp(mActivityRecord);
+ && recentsAnimationController.shouldApplyInputConsumer(mActivityRecord);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
index 901277d..2304bc6 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
@@ -47,13 +47,18 @@
import org.junit.runners.JUnit4;
import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.List;
import java.util.Optional;
@RunWith(JUnit4.class)
public class RuleBinarySerializerTest {
+ private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
+ private static final String SAMPLE_INSTALLER_CERT = "installer_cert";
+
private static final String COMPOUND_FORMULA_START_BITS =
getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS);
private static final String COMPOUND_FORMULA_END_BITS =
@@ -67,6 +72,9 @@
private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS);
+ private static final String INSTALLER_NAME = getBits(AtomicFormula.INSTALLER_NAME, KEY_BITS);
+ private static final String INSTALLER_CERTIFICATE =
+ getBits(AtomicFormula.INSTALLER_CERTIFICATE, KEY_BITS);
private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS);
private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS);
@@ -83,17 +91,28 @@
getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
@Test
- public void testBinaryString_serializeEmptyRule() throws Exception {
- Rule rule = null;
+ public void testBinaryString_serializeNullRules() {
RuleSerializer binarySerializer = new RuleBinarySerializer();
assertExpectException(
RuleSerializeException.class,
- /* expectedExceptionMessageRegex= */ "Null rule can not be serialized",
+ /* expectedExceptionMessageRegex= */
+ "Index buckets cannot be created for null rule list.",
() ->
- binarySerializer.serialize(
- Collections.singletonList(rule),
- /* formatVersion= */ Optional.empty()));
+ binarySerializer.serialize(null, /* formatVersion= */ Optional.empty()));
+ }
+
+ @Test
+ public void testBinaryString_emptyRules() throws Exception {
+ ByteArrayOutputStream expectedArrayOutputStream = new ByteArrayOutputStream();
+ expectedArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ binarySerializer.serialize(
+ Collections.emptyList(), /* formatVersion= */ Optional.empty(), outputStream);
+
+ assertThat(outputStream.toByteArray()).isEqualTo(expectedArrayOutputStream.toByteArray());
}
@Test
@@ -381,7 +400,7 @@
assertExpectException(
RuleSerializeException.class,
- /* expectedExceptionMessageRegex= */ "Invalid formula type",
+ /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
() ->
binarySerializer.serialize(
Collections.singletonList(rule),
@@ -402,6 +421,165 @@
assertThat(actualRules).isEqualTo(expectedRules);
}
+ @Test
+ public void testBinaryString_serializeComplexCompoundFormula_indexingOrderValid()
+ throws Exception {
+ String packageNameA = "aaa";
+ String packageNameB = "bbb";
+ String packageNameC = "ccc";
+ String appCert1 = "cert1";
+ String appCert2 = "cert2";
+ String appCert3 = "cert3";
+ Rule installerRule =
+ new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME,
+ SAMPLE_INSTALLER_NAME,
+ /* isHashedValue= */ false),
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_CERTIFICATE,
+ SAMPLE_INSTALLER_CERT,
+ /* isHashedValue= */ false))),
+ Rule.DENY);
+
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ List<Rule> ruleList = new ArrayList();
+ ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(appCert3));
+ ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(appCert2));
+ ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(appCert1));
+ ruleList.add(getRuleWithPackageNameAndSampleInstallerName(packageNameB));
+ ruleList.add(getRuleWithPackageNameAndSampleInstallerName(packageNameC));
+ ruleList.add(getRuleWithPackageNameAndSampleInstallerName(packageNameA));
+ ruleList.add(installerRule);
+ byte[] actualRules =
+ binarySerializer.serialize(ruleList, /* formatVersion= */ Optional.empty());
+
+
+ // Note that ordering is important here and the test verifies that the rules are written
+ // in this sorted order.
+ ByteArrayOutputStream expectedArrayOutputStream = new ByteArrayOutputStream();
+ expectedArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
+ expectedArrayOutputStream.write(
+ getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+ packageNameA)));
+ expectedArrayOutputStream.write(
+ getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+ packageNameB)));
+ expectedArrayOutputStream.write(
+ getBytes(getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+ packageNameC)));
+ expectedArrayOutputStream.write(
+ getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+ appCert1)));
+ expectedArrayOutputStream.write(
+ getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+ appCert2)));
+ expectedArrayOutputStream.write(
+ getBytes(getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+ appCert3)));
+ String expectedBitsForInstallerRule =
+ START_BIT
+ + COMPOUND_FORMULA_START_BITS
+ + AND
+ + ATOMIC_FORMULA_START_BITS
+ + INSTALLER_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
+ + getValueBits(SAMPLE_INSTALLER_NAME)
+ + ATOMIC_FORMULA_START_BITS
+ + INSTALLER_CERTIFICATE
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(SAMPLE_INSTALLER_CERT.length(), VALUE_SIZE_BITS)
+ + getValueBits(SAMPLE_INSTALLER_CERT)
+ + COMPOUND_FORMULA_END_BITS
+ + DENY
+ + END_BIT;
+ expectedArrayOutputStream.write(getBytes(expectedBitsForInstallerRule));
+
+ assertThat(actualRules).isEqualTo(expectedArrayOutputStream.toByteArray());
+ }
+
+ private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) {
+ return new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME,
+ packageName,
+ /* isHashedValue= */ false),
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME,
+ SAMPLE_INSTALLER_NAME,
+ /* isHashedValue= */ false))),
+ Rule.DENY);
+ }
+
+ private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+ String packageName) {
+ return START_BIT
+ + COMPOUND_FORMULA_START_BITS
+ + AND
+ + ATOMIC_FORMULA_START_BITS
+ + PACKAGE_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(packageName.length(), VALUE_SIZE_BITS)
+ + getValueBits(packageName)
+ + ATOMIC_FORMULA_START_BITS
+ + INSTALLER_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
+ + getValueBits(SAMPLE_INSTALLER_NAME)
+ + COMPOUND_FORMULA_END_BITS
+ + DENY
+ + END_BIT;
+ }
+
+ private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) {
+ return new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE,
+ certificate,
+ /* isHashedValue= */ false),
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME,
+ SAMPLE_INSTALLER_NAME,
+ /* isHashedValue= */ false))),
+ Rule.DENY);
+ }
+
+ private String getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+ String appCertificate) {
+ return START_BIT
+ + COMPOUND_FORMULA_START_BITS
+ + AND
+ + ATOMIC_FORMULA_START_BITS
+ + APP_CERTIFICATE
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(appCertificate.length(), VALUE_SIZE_BITS)
+ + getValueBits(appCertificate)
+ + ATOMIC_FORMULA_START_BITS
+ + INSTALLER_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
+ + getValueBits(SAMPLE_INSTALLER_NAME)
+ + COMPOUND_FORMULA_END_BITS
+ + DENY
+ + END_BIT;
+ }
+
private static Formula getInvalidFormula() {
return new Formula() {
@Override
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
index 90ec19e..94e11c6 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
@@ -129,7 +129,7 @@
assertExpectException(
IllegalArgumentException.class,
- /* expectedExceptionMessageRegex= */ "Invalid formula tag type.",
+ /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
() -> splitRulesIntoIndexBuckets(ruleList));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index e17e0d8..8fe0cdb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -19,10 +19,13 @@
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_APP_THEME;
import static com.android.server.wm.TaskSnapshotController.SNAPSHOT_MODE_REAL;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -175,4 +178,22 @@
}
}
}
+
+ @Test
+ public void testPrepareTaskSnapshot() {
+ mAppWindow.mWinAnimator.mLastAlpha = 1f;
+ spyOn(mAppWindow.mWinAnimator);
+ doReturn(true).when(mAppWindow.mWinAnimator).getShown();
+ doReturn(true).when(mAppWindow.mActivityRecord).isSurfaceShowing();
+
+ final ActivityManager.TaskSnapshot.Builder builder =
+ new ActivityManager.TaskSnapshot.Builder();
+ final float scaleFraction = 0.8f;
+ mWm.mTaskSnapshotController.prepareTaskSnapshot(mAppWindow.mActivityRecord.getTask(),
+ scaleFraction, PixelFormat.UNKNOWN, builder);
+
+ assertEquals(scaleFraction, builder.getScaleFraction(), 0 /* delta */);
+ // The pixel format should be selected automatically.
+ assertNotEquals(PixelFormat.UNKNOWN, builder.getPixelFormat());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 0018c63..6d23b2e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -596,7 +596,7 @@
// Mock active recents animation
RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
- when(recentsController.isAnimatingTask(win0.mActivityRecord.getTask())).thenReturn(true);
+ when(recentsController.shouldApplyInputConsumer(win0.mActivityRecord)).thenReturn(true);
mWm.setRecentsAnimationController(recentsController);
assertTrue(win0.cantReceiveTouchInput());
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 813b9fa..4bb237f 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4050,13 +4050,28 @@
}
}
- /** {@hide} */
+ /**
+ * Gets the package name for a default carrier service.
+ * @return the package name for a default carrier service; empty string if not available.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getDefaultCarrierServicePackageName() {
try {
- return getICarrierConfigLoader().getDefaultCarrierServicePackageName();
- } catch (Throwable t) {
- return null;
+ ICarrierConfigLoader loader = getICarrierConfigLoader();
+ if (loader == null) {
+ Rlog.w(TAG, "getDefaultCarrierServicePackageName ICarrierConfigLoader is null");
+ return "";
+ }
+ return loader.getDefaultCarrierServicePackageName();
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "getDefaultCarrierServicePackageName ICarrierConfigLoader is null"
+ + ex.toString());
}
+ return "";
}
/**
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index d7d85c2..3f065f8 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -65,6 +65,13 @@
static final boolean DBG = false;
static final boolean VDBG = false; // STOPSHIP if true
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "STATE_",
+ value = {STATE_IN_SERVICE, STATE_OUT_OF_SERVICE, STATE_EMERGENCY_ONLY,
+ STATE_POWER_OFF})
+ public @interface RegState {}
+
/**
* Normal operation condition, the phone is registered
* with an operator either in home network or in roaming.
@@ -83,6 +90,7 @@
/**
* The phone is registered and locked. Only emergency numbers are allowed. {@more}
*/
+ //TODO: This state is not used anymore. It should be deprecated in a future release.
public static final int STATE_EMERGENCY_ONLY =
TelephonyProtoEnums.SERVICE_STATE_EMERGENCY_ONLY; // 2
@@ -501,13 +509,15 @@
}
/**
- * Get current data service state
+ * Get current data registration state.
*
* @see #STATE_IN_SERVICE
* @see #STATE_OUT_OF_SERVICE
* @see #STATE_EMERGENCY_ONLY
* @see #STATE_POWER_OFF
*
+ * @return current data registration state {@link RegState}
+ *
* @hide
*/
@UnsupportedAppUsage
@@ -516,6 +526,23 @@
}
/**
+ * Get current data registration state.
+ *
+ * @see #STATE_IN_SERVICE
+ * @see #STATE_OUT_OF_SERVICE
+ * @see #STATE_EMERGENCY_ONLY
+ * @see #STATE_POWER_OFF
+ *
+ * @return current data registration state {@link RegState}
+ *
+ * @hide
+ */
+ @SystemApi
+ public @RegState int getDataRegistrationState() {
+ return getDataRegState();
+ }
+
+ /**
* Get the current duplex mode
*
* @see #DUPLEX_MODE_UNKNOWN
@@ -1408,7 +1435,15 @@
return getRilDataRadioTechnology();
}
- /** @hide */
+ /**
+ * Transform RIL radio technology {@link RilRadioTechnology} value to Network
+ * type {@link NetworkType}.
+ *
+ * @param rat The RIL radio technology {@link RilRadioTechnology}.
+ * @return The network type {@link NetworkType}.
+ *
+ * @hide
+ */
public static int rilRadioTechnologyToNetworkType(@RilRadioTechnology int rat) {
switch(rat) {
case RIL_RADIO_TECHNOLOGY_GPRS:
@@ -1490,7 +1525,15 @@
}
}
- /** @hide */
+ /**
+ * Transform network type {@link NetworkType} value to RIL radio technology
+ * {@link RilRadioTechnology}.
+ *
+ * @param networkType The network type {@link NetworkType}.
+ * @return The RIL radio technology {@link RilRadioTechnology}.
+ *
+ * @hide
+ */
public static int networkTypeToRilRadioTechnology(int networkType) {
switch(networkType) {
case TelephonyManager.NETWORK_TYPE_GPRS:
@@ -1691,7 +1734,14 @@
return bearerBitmask;
}
- /** @hide */
+ /**
+ * Convert network type bitmask to bearer bitmask.
+ *
+ * @param networkTypeBitmask The network type bitmask value
+ * @return The bearer bitmask value.
+ *
+ * @hide
+ */
public static int convertNetworkTypeBitmaskToBearerBitmask(int networkTypeBitmask) {
if (networkTypeBitmask == 0) {
return 0;
@@ -1705,7 +1755,14 @@
return bearerBitmask;
}
- /** @hide */
+ /**
+ * Convert bearer bitmask to network type bitmask.
+ *
+ * @param bearerBitmask The bearer bitmask value.
+ * @return The network type bitmask value.
+ *
+ * @hide
+ */
public static int convertBearerBitmaskToNetworkTypeBitmask(int bearerBitmask) {
if (bearerBitmask == 0) {
return 0;
diff --git a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
index b8c82fd..4fa93ee 100644
--- a/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
+++ b/wifi/java/android/net/wifi/EasyConnectStatusCallback.java
@@ -17,32 +17,46 @@
package android.net.wifi;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.os.Handler;
+import android.util.SparseArray;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
/**
* Easy Connect (DPP) Status Callback. Use this callback to get status updates (success, failure,
* progress) from the Easy Connect operation started with
- * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String,
- * int, int, Handler, EasyConnectStatusCallback)} or
- * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String,
- * Handler, EasyConnectStatusCallback)}
+ * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int, int, Executor,
+ * EasyConnectStatusCallback)} or {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String,
+ * Executor, EasyConnectStatusCallback)}
*
* @hide
*/
@SystemApi
public abstract class EasyConnectStatusCallback {
/**
- * Easy Connect Success event: Configuration sent (Configurator mode).
+ * Easy Connect R1 Success event: Configuration sent (Configurator mode). This is the last
+ * and final Easy Connect event when either the local device or remote device implement R1.
+ * If both devices implement R2, this event will never be received, and the
+ * {@link EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED} will be received.
*/
public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT = 0;
+ /**
+ * East Connect R2 Success event: Configuration applied by Enrollee (Configurator mode).
+ * This is the last and final Easy Connect event when both the local device and remote device
+ * implement R2. If either the local device or remote device implement R1, this event will never
+ * be received, and the {@link EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT} will be received.
+ */
+ public static final int EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED = 1;
+
/** @hide */
@IntDef(prefix = {"EASY_CONNECT_EVENT_SUCCESS_"}, value = {
EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_SENT,
+ EASY_CONNECT_EVENT_SUCCESS_CONFIGURATION_APPLIED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EasyConnectSuccessStatusCode {
@@ -58,10 +72,22 @@
*/
public static final int EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING = 1;
+ /**
+ * Easy Connect R2 Progress event: Configuration sent to Enrollee, waiting for response
+ */
+ public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE = 2;
+
+ /**
+ * Easy Connect R2 Progress event: Configuration accepted by Enrollee, waiting for response
+ */
+ public static final int EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED = 3;
+
/** @hide */
@IntDef(prefix = {"EASY_CONNECT_EVENT_PROGRESS_"}, value = {
EASY_CONNECT_EVENT_PROGRESS_AUTHENTICATION_SUCCESS,
EASY_CONNECT_EVENT_PROGRESS_RESPONSE_PENDING,
+ EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_SENT_WAITING_RESPONSE,
+ EASY_CONNECT_EVENT_PROGRESS_CONFIGURATION_ACCEPTED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EasyConnectProgressStatusCode {
@@ -114,6 +140,20 @@
*/
public static final int EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK = -9;
+ /**
+ * Easy Connect R2 Failure event: Enrollee cannot find the network.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK = -10;
+
+ /**
+ * Easy Connect R2 Failure event: Enrollee failed to authenticate with the network.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION = -11;
+
+ /**
+ * Easy Connect R2 Failure event: Enrollee rejected the configuration.
+ */
+ public static final int EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION = -12;
/** @hide */
@IntDef(prefix = {"EASY_CONNECT_EVENT_FAILURE_"}, value = {
@@ -126,6 +166,9 @@
EASY_CONNECT_EVENT_FAILURE_GENERIC,
EASY_CONNECT_EVENT_FAILURE_NOT_SUPPORTED,
EASY_CONNECT_EVENT_FAILURE_INVALID_NETWORK,
+ EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK,
+ EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION,
+ EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EasyConnectFailureStatusCode {
@@ -138,9 +181,8 @@
* current Easy Connect
* session, and no further callbacks will be called. This callback is the successful outcome
* of a Easy Connect flow starting with
- * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String,
- * Handler,
- * EasyConnectStatusCallback)}.
+ * {@link WifiManager#startEasyConnectAsEnrolleeInitiator(String, Executor,
+ * EasyConnectStatusCallback)} .
*
* @param newNetworkId New Wi-Fi configuration with a network ID received from the configurator
*/
@@ -148,13 +190,11 @@
/**
* Called when a Easy Connect success event takes place, except for when configuration is
- * received from
- * an external Configurator. The callback onSuccessConfigReceived will be used in this case.
- * This callback marks the successful end of the current Easy Connect session, and no further
- * callbacks will be called. This callback is the successful outcome of a Easy Connect flow
- * starting with
- * {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int, int, Handler,
- * EasyConnectStatusCallback)}.
+ * received from an external Configurator. The callback onSuccessConfigReceived will be used in
+ * this case. This callback marks the successful end of the current Easy Connect session, and no
+ * further callbacks will be called. This callback is the successful outcome of a Easy Connect
+ * flow starting with {@link WifiManager#startEasyConnectAsConfiguratorInitiator(String, int,
+ * int, Executor,EasyConnectStatusCallback)}.
*
* @param code Easy Connect success status code.
*/
@@ -162,12 +202,36 @@
/**
* Called when a Easy Connect Failure event takes place. This callback marks the unsuccessful
- * end of the
- * current Easy Connect session, and no further callbacks will be called.
+ * end of the current Easy Connect session, and no further callbacks will be called.
*
* @param code Easy Connect failure status code.
*/
- public abstract void onFailure(@EasyConnectFailureStatusCode int code);
+ public void onFailure(@EasyConnectFailureStatusCode int code) {}
+
+ /**
+ * Called when a Easy Connect Failure event takes place. This callback marks the unsuccessful
+ * end of the current Easy Connect session, and no further callbacks will be called.
+ *
+ * Note: Easy Connect (DPP) R2, provides additional details for the Configurator when the
+ * remote Enrollee is unable to connect to a network. The ssid, channelList and bandList
+ * inputs are initialized only for the EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK failure
+ * code, and the ssid and bandList are initialized for the
+ * EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION failure code.
+ *
+ * @param code Easy Connect failure status code.
+ * @param ssid SSID of the network the Enrollee tried to connect to.
+ * @param channelListArray List of Global Operating classes and channel sets the Enrollee used
+ * to scan to find the network, see the "DPP Connection Status Object"
+ * section in the specification for the format, and Table E-4 in
+ * IEEE Std 802.11-2016 - Global operating classes for more details.
+ * @param operatingClassArray Array of bands the Enrollee supports as expressed as the Global
+ * Operating Class, see Table E-4 in IEEE Std 802.11-2016 - Global
+ * operating classes.
+ */
+ public void onFailure(@EasyConnectFailureStatusCode int code, @Nullable String ssid,
+ @NonNull SparseArray<int[]> channelListArray, @NonNull int[] operatingClassArray) {
+ onFailure(code);
+ }
/**
* Called when Easy Connect events that indicate progress take place. Can be used by UI elements
diff --git a/wifi/java/android/net/wifi/IDppCallback.aidl b/wifi/java/android/net/wifi/IDppCallback.aidl
index c452c76..d7a958a 100644
--- a/wifi/java/android/net/wifi/IDppCallback.aidl
+++ b/wifi/java/android/net/wifi/IDppCallback.aidl
@@ -38,7 +38,7 @@
/**
* Called when DPP Failure events take place.
*/
- void onFailure(int status);
+ void onFailure(int status, String ssid, String channelList, in int[] bandArray);
/**
* Called when DPP events that indicate progress take place. Can be used by UI elements
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index b98d64d..5ab0583 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -54,6 +54,7 @@
import android.util.CloseGuard;
import android.util.Log;
import android.util.Pair;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -70,6 +71,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.StringTokenizer;
import java.util.concurrent.Executor;
/**
@@ -5177,6 +5179,7 @@
@Override
public void onSuccessConfigReceived(int newNetworkId) {
Log.d(TAG, "Easy Connect onSuccessConfigReceived callback");
+ Binder.clearCallingIdentity();
mExecutor.execute(() -> {
mEasyConnectStatusCallback.onEnrolleeSuccess(newNetworkId);
});
@@ -5185,22 +5188,28 @@
@Override
public void onSuccess(int status) {
Log.d(TAG, "Easy Connect onSuccess callback");
+ Binder.clearCallingIdentity();
mExecutor.execute(() -> {
mEasyConnectStatusCallback.onConfiguratorSuccess(status);
});
}
@Override
- public void onFailure(int status) {
+ public void onFailure(int status, String ssid, String channelList,
+ int[] operatingClassArray) {
Log.d(TAG, "Easy Connect onFailure callback");
+ Binder.clearCallingIdentity();
mExecutor.execute(() -> {
- mEasyConnectStatusCallback.onFailure(status);
+ SparseArray<int[]> channelListArray = parseDppChannelList(channelList);
+ mEasyConnectStatusCallback.onFailure(status, ssid, channelListArray,
+ operatingClassArray);
});
}
@Override
public void onProgress(int status) {
Log.d(TAG, "Easy Connect onProgress callback");
+ Binder.clearCallingIdentity();
mExecutor.execute(() -> {
mEasyConnectStatusCallback.onProgress(status);
});
@@ -5532,4 +5541,77 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Parse the list of channels the DPP enrollee reports when it fails to find an AP.
+ *
+ * @param channelList List of channels in the format defined in the DPP specification.
+ * @return A parsed sparse array, where the operating class is the key.
+ * @hide
+ */
+ @VisibleForTesting
+ public static SparseArray<int[]> parseDppChannelList(String channelList) {
+ SparseArray<int[]> channelListArray = new SparseArray<>();
+
+ if (TextUtils.isEmpty(channelList)) {
+ return channelListArray;
+ }
+ StringTokenizer str = new StringTokenizer(channelList, ",");
+ String classStr = null;
+ List<Integer> channelsInClass = new ArrayList<>();
+
+ try {
+ while (str.hasMoreElements()) {
+ String cur = str.nextToken();
+
+ /**
+ * Example for a channel list:
+ *
+ * 81/1,2,3,4,5,6,7,8,9,10,11,115/36,40,44,48,118/52,56,60,64,121/100,104,108,112,
+ * 116,120,124,128,132,136,140,0/144,124/149,153,157,161,125/165
+ *
+ * Detect operating class by the delimiter of '/' and use a string tokenizer with
+ * ',' as a delimiter.
+ */
+ int classDelim = cur.indexOf('/');
+ if (classDelim != -1) {
+ if (classStr != null) {
+ // Store the last channel array in the sparse array, where the operating
+ // class is the key (as an integer).
+ int[] channelsArray = new int[channelsInClass.size()];
+ for (int i = 0; i < channelsInClass.size(); i++) {
+ channelsArray[i] = channelsInClass.get(i);
+ }
+ channelListArray.append(Integer.parseInt(classStr), channelsArray);
+ channelsInClass = new ArrayList<>();
+ }
+
+ // Init a new operating class and store the first channel
+ classStr = cur.substring(0, classDelim);
+ String channelStr = cur.substring(classDelim + 1);
+ channelsInClass.add(Integer.parseInt(channelStr));
+ } else {
+ if (classStr == null) {
+ // Invalid format
+ Log.e(TAG, "Cannot parse DPP channel list");
+ return new SparseArray<>();
+ }
+ channelsInClass.add(Integer.parseInt(cur));
+ }
+ }
+
+ // Store the last array
+ if (classStr != null) {
+ int[] channelsArray = new int[channelsInClass.size()];
+ for (int i = 0; i < channelsInClass.size(); i++) {
+ channelsArray[i] = channelsInClass.get(i);
+ }
+ channelListArray.append(Integer.parseInt(classStr), channelsArray);
+ }
+ return channelListArray;
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Cannot parse DPP channel list");
+ return new SparseArray<>();
+ }
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java b/wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java
new file mode 100644
index 0000000..b101414
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/EasyConnectStatusCallbackTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.SparseArray;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.EasyConnectStatusCallbackTest}.
+ */
+@SmallTest
+public class EasyConnectStatusCallbackTest {
+ private EasyConnectStatusCallback mEasyConnectStatusCallback = new EasyConnectStatusCallback() {
+ @Override
+ public void onEnrolleeSuccess(int newNetworkId) {
+
+ }
+
+ @Override
+ public void onConfiguratorSuccess(int code) {
+
+ }
+
+ @Override
+ public void onProgress(int code) {
+
+ }
+
+ @Override
+ public void onFailure(int code) {
+ mOnFailureR1EventReceived = true;
+ mLastCode = code;
+ }
+ };
+ private boolean mOnFailureR1EventReceived;
+ private int mLastCode;
+
+ @Before
+ public void setUp() {
+ mOnFailureR1EventReceived = false;
+ mLastCode = 0;
+ }
+
+ /**
+ * Test that the legacy R1 onFailure is called by default if the R2 onFailure is not overridden
+ * by the app.
+ */
+ @Test
+ public void testR1OnFailureCalled() {
+
+ SparseArray<int[]> channelList = new SparseArray<>();
+ int[] channelArray = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+
+ channelList.append(81, channelArray);
+ mEasyConnectStatusCallback.onFailure(
+ EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK,
+ "SomeSSID", channelList, new int[] {81});
+
+ assertTrue(mOnFailureR1EventReceived);
+ assertEquals(mLastCode,
+ EasyConnectStatusCallback.EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK);
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index 4a46744..8216611 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -89,6 +89,7 @@
import android.os.RemoteException;
import android.os.connectivity.WifiActivityEnergyInfo;
import android.os.test.TestLooper;
+import android.util.SparseArray;
import androidx.test.filters.SmallTest;
@@ -2089,4 +2090,63 @@
.thenReturn(new Long(~WifiManager.WIFI_FEATURE_WAPI));
assertFalse(mWifiManager.isWapiSupported());
}
+
+ /*
+ * Test that DPP channel list is parsed correctly
+ */
+ @Test
+ public void testparseDppChannelList() throws Exception {
+ String channelList = "81/1,2,3,4,5,6,7,8,9,10,11,115/36,40,44,48";
+ SparseArray<int[]> expectedResult = new SparseArray<>();
+ expectedResult.append(81, new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11});
+ expectedResult.append(115, new int[]{36, 40, 44, 48});
+
+ SparseArray<int[]> result = WifiManager.parseDppChannelList(channelList);
+ assertEquals(expectedResult.size(), result.size());
+
+ int index = 0;
+ int key;
+
+ // Compare the two primitive int arrays
+ do {
+ try {
+ key = result.keyAt(index);
+ } catch (java.lang.ArrayIndexOutOfBoundsException e) {
+ break;
+ }
+ int[] expected = expectedResult.get(key);
+ int[] output = result.get(key);
+ assertEquals(expected.length, output.length);
+ for (int i = 0; i < output.length; i++) {
+ assertEquals(expected[i], output[i]);
+ }
+ index++;
+ } while (true);
+ }
+
+ /*
+ * Test that DPP channel list parser gracefully fails for invalid input
+ */
+ @Test
+ public void testparseDppChannelListWithInvalidFormats() throws Exception {
+ String channelList = "1,2,3,4,5,6,7,8,9,10,11,36,40,44,48";
+ SparseArray<int[]> result = WifiManager.parseDppChannelList(channelList);
+ assertEquals(result.size(), 0);
+
+ channelList = "ajgalskgjalskjg3-09683dh";
+ result = WifiManager.parseDppChannelList(channelList);
+ assertEquals(result.size(), 0);
+
+ channelList = "13/abc,46////";
+ result = WifiManager.parseDppChannelList(channelList);
+ assertEquals(result.size(), 0);
+
+ channelList = "11/4,5,13/";
+ result = WifiManager.parseDppChannelList(channelList);
+ assertEquals(result.size(), 0);
+
+ channelList = "/24,6";
+ result = WifiManager.parseDppChannelList(channelList);
+ assertEquals(result.size(), 0);
+ }
}