Merge "Keep PluginManager reference to avoid NPE"
diff --git a/Android.bp b/Android.bp
index abeeb43..3095ca5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -536,6 +536,7 @@
"telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl",
"telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl",
"telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl",
+ "telephony/java/android/telephony/ICellInfoCallback.aidl",
"telephony/java/android/telephony/INetworkService.aidl",
"telephony/java/android/telephony/INetworkServiceCallback.aidl",
"telephony/java/com/android/ims/internal/IImsCallSession.aidl",
diff --git a/api/current.txt b/api/current.txt
index 99ea60f..b458fac 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -5370,6 +5370,7 @@
field public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
field public static final int SEMANTIC_ACTION_ARCHIVE = 5; // 0x5
field public static final int SEMANTIC_ACTION_CALL = 10; // 0xa
+ field public static final int SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION = 11; // 0xb
field public static final int SEMANTIC_ACTION_DELETE = 4; // 0x4
field public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; // 0x2
field public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; // 0x3
@@ -24464,6 +24465,7 @@
}
public static final class MediaExtractor.CasInfo {
+ method public byte[] getPrivateData();
method public android.media.MediaCas.Session getSession();
method public int getSystemId();
}
@@ -43555,6 +43557,7 @@
method public java.lang.String getCountryIso();
method public int getDataRoaming();
method public java.lang.CharSequence getDisplayName();
+ method public java.lang.String getGroupUuid();
method public java.lang.String getIccId();
method public int getIconTint();
method public deprecated int getMcc();
@@ -43562,7 +43565,6 @@
method public deprecated int getMnc();
method public java.lang.String getMncString();
method public java.lang.String getNumber();
- method public int getParentSubId();
method public int getSimSlotIndex();
method public int getSubscriptionId();
method public boolean isEmbedded();
@@ -43596,6 +43598,7 @@
method public static boolean isValidSubscriptionId(int);
method public void removeOnOpportunisticSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+ method public java.lang.String setSubscriptionGroup(int[]);
method public void setSubscriptionOverrideCongested(int, boolean, long);
method public void setSubscriptionOverrideUnmetered(int, boolean, long);
method public void setSubscriptionPlans(int, java.util.List<android.telephony.SubscriptionPlan>);
@@ -43727,6 +43730,7 @@
method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
method public boolean isWorldPhone();
method public void listen(android.telephony.PhoneStateListener, int);
+ method public void requestCellInfoUpdate(java.util.concurrent.Executor, android.telephony.TelephonyManager.CellInfoCallback);
method public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
method public void sendDialerSpecialCode(java.lang.String);
method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
@@ -43829,6 +43833,11 @@
field public static final java.lang.String VVM_TYPE_OMTP = "vvm_type_omtp";
}
+ public static abstract class TelephonyManager.CellInfoCallback {
+ ctor public TelephonyManager.CellInfoCallback();
+ method public abstract void onCellInfo(java.util.List<android.telephony.CellInfo>);
+ }
+
public static abstract class TelephonyManager.UssdResponseCallback {
ctor public TelephonyManager.UssdResponseCallback();
method public void onReceiveUssdResponse(android.telephony.TelephonyManager, java.lang.String, java.lang.CharSequence);
@@ -54358,6 +54367,9 @@
method public void show(float, float);
method public void show(float, float, float, float);
method public void update();
+ field public static final int SOURCE_BOUND_MAX_IN_SURFACE = 0; // 0x0
+ field public static final int SOURCE_BOUND_MAX_IN_VIEW = 1; // 0x1
+ field public static final int SOURCE_BOUND_MAX_VISIBLE = 2; // 0x2
}
public static class Magnifier.Builder {
@@ -54368,6 +54380,7 @@
method public android.widget.Magnifier.Builder setElevation(float);
method public android.widget.Magnifier.Builder setForcePositionWithinWindowSystemInsetsBounds(boolean);
method public android.widget.Magnifier.Builder setSize(int, int);
+ method public android.widget.Magnifier.Builder setSourceBounds(int, int, int, int);
method public android.widget.Magnifier.Builder setZoom(float);
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 8eb5507..cdd711f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1206,6 +1206,7 @@
public abstract class PackageManager {
method public abstract void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
method public abstract boolean arePermissionsIndividuallyControlled();
+ method public boolean canSuspendPackage(java.lang.String);
method public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
method public android.content.pm.dex.ArtManager getArtManager();
method public abstract java.lang.String getDefaultBrowserPackageNameAsUser(int);
@@ -3640,17 +3641,16 @@
public class WifiManager {
method public void connect(android.net.wifi.WifiConfiguration, android.net.wifi.WifiManager.ActionListener);
+ method public java.util.List<android.net.wifi.WifiConfiguration> getAllMatchingWifiConfigs(java.util.List<android.net.wifi.ScanResult>);
+ method public java.util.List<android.net.wifi.hotspot2.OsuProvider> getMatchingOsuProviders(java.util.List<android.net.wifi.ScanResult>);
method public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
method public android.net.wifi.WifiConfiguration getWifiApConfiguration();
method public int getWifiApState();
method public boolean isDeviceToApRttSupported();
method public boolean isDeviceToDeviceRttSupported();
- method public boolean isOweSupported();
method public boolean isPortableHotspotSupported();
method public boolean isWifiApEnabled();
method public boolean isWifiScannerSupported();
- method public boolean isWpa3SaeSupported();
- method public boolean isWpa3SuiteBSupported();
method public void registerNetworkRequestMatchCallback(android.net.wifi.WifiManager.NetworkRequestMatchCallback, android.os.Handler);
method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
method public boolean startScan(android.os.WorkSource);
@@ -4110,6 +4110,7 @@
}
public final class PowerManager {
+ method public void dream(long);
method public int getPowerSaveMode();
method public boolean setDynamicPowerSavings(boolean, int);
method public boolean setPowerSaveMode(boolean);
@@ -4784,6 +4785,16 @@
}
+package android.service.carrier {
+
+ public abstract class ApnService extends android.app.Service {
+ ctor public ApnService();
+ method public abstract java.util.List<android.content.ContentValues> onRestoreApns(int);
+ method public android.os.IBinder onBind(android.content.Intent);
+ }
+
+}
+
package android.service.euicc {
public final class EuiccProfileInfo implements android.os.Parcelable {
@@ -5697,6 +5708,7 @@
method public deprecated boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle);
method public boolean needsOtaServiceProvisioning();
method public boolean rebootRadio();
+ method public void requestCellInfoUpdate(android.os.WorkSource, java.util.concurrent.Executor, android.telephony.TelephonyManager.CellInfoCallback);
method public boolean resetRadioConfig();
method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
method public void setCarrierDataEnabled(boolean);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 244974d..5ca5aa3 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -27,6 +27,7 @@
import "frameworks/base/core/proto/android/bluetooth/enums.proto";
import "frameworks/base/core/proto/android/os/enums.proto";
import "frameworks/base/core/proto/android/server/enums.proto";
+import "frameworks/base/core/proto/android/server/location/enums.proto";
import "frameworks/base/core/proto/android/service/procstats_enum.proto";
import "frameworks/base/core/proto/android/stats/enums.proto";
import "frameworks/base/core/proto/android/stats/launcher/launcher.proto";
@@ -118,7 +119,7 @@
ResourceConfigurationChanged resource_configuration_changed = 66;
BluetoothEnabledStateChanged bluetooth_enabled_state_changed = 67;
BluetoothConnectionStateChanged bluetooth_connection_state_changed = 68;
- // 69 is blank but need not be.
+ GpsSignalQualityChanged gps_signal_quality_changed = 69;
UsbConnectorStateChanged usb_connector_state_changed = 70;
SpeakerImpedanceReported speaker_impedance_reported = 71;
HardwareFailed hardware_failed = 72;
@@ -148,6 +149,11 @@
UserRestrictionChanged user_restriction_changed = 96;
SettingsUIChanged settings_ui_changed = 97;
ConnectivityStateChanged connectivity_state_changed = 98;
+ // TODO: service state change is very noisy shortly after boot, as well
+ // as at other transitions - coming out of doze, device plugged in, etc.
+ // Consider removing this if it becomes a problem
+ ServiceStateChanged service_state_changed = 99;
+ ServiceLaunchReported service_launch_reported = 100;
}
// Pulled events will start at field 10000.
@@ -489,7 +495,6 @@
optional State state = 3;
}
-
/**
* Logs when GPS state changes.
*
@@ -506,6 +511,16 @@
optional State state = 2;
}
+/**
+ * Logs when GPS signal quality.
+ *
+ * Logged from:
+ * /frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+ */
+message GpsSignalQualityChanged {
+ optional android.server.location.GpsSignalQualityEnum level = 1;
+}
+
/**
* Logs when a sync manager sync state changes.
@@ -2138,16 +2153,51 @@
* Logged in StatsCompanionService.java
*/
message ConnectivityStateChanged {
- // Id of the network.
- optional int32 net_id = 1;
+ // Id of the network.
+ optional int32 net_id = 1;
+
+ enum State {
+ UNKNOWN = 0;
+ CONNECTED = 1;
+ DISCONNECTED = 2;
+ }
+ // Connected state of a network.
+ optional State state = 2;
+}
+
+/**
+ * Logs when a service starts and stops.
+ * Logged from:
+ * services/core/java/com/android/server/am/ActiveServices.java
+ */
+message ServiceStateChanged {
+
+ optional int32 uid = 1 [(is_uid) = true];
+
+ optional string package_name = 2;
+
+ optional string service_name = 3;
enum State {
- UNKNOWN = 0;
- CONNECTED = 1;
- DISCONNECTED = 2;
+ START = 1;
+ STOP = 2;
}
- // Connected state of a network.
- optional State state = 2;
+
+ optional State state = 4;
+}
+
+/**
+ * Logs when a service is launched.
+ * Logged from:
+ * services/core/java/com/android/server/am/ActiveServices.java
+ */
+message ServiceLaunchReported {
+
+ optional int32 uid = 1 [(is_uid) = true];
+
+ optional string package_name = 2;
+
+ optional string service_name = 3;
}
//////////////////////////////////////////////////////////////////////
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index fcd9a05..8bb704d 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2277,6 +2277,15 @@
}
@Override
+ public boolean canSuspendPackage(String packageName) {
+ try {
+ return mPM.canSuspendPackageForUser(packageName, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
public Bundle getSuspendedPackageAppExtras() {
final PersistableBundle extras;
try {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index df37a02..450efdf 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -207,7 +207,8 @@
private static final int MAX_REPLY_HISTORY = 5;
/**
- * Maximum numbers of action buttons in a notification.
+ * Maximum number of (generic) action buttons in a notification (contextual action buttons are
+ * handled separately).
* @hide
*/
public static final int MAX_ACTION_BUTTONS = 3;
@@ -1421,6 +1422,12 @@
*/
public static final int SEMANTIC_ACTION_CALL = 10;
+ /**
+ * {@code SemanticAction}: Contextual action - dependent on the current notification. E.g.
+ * open a Map application with an address shown in the notification.
+ */
+ public static final int SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION = 11;
+
private final Bundle mExtras;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Icon mIcon;
@@ -2042,7 +2049,8 @@
SEMANTIC_ACTION_UNMUTE,
SEMANTIC_ACTION_THUMBS_UP,
SEMANTIC_ACTION_THUMBS_DOWN,
- SEMANTIC_ACTION_CALL
+ SEMANTIC_ACTION_CALL,
+ SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION
})
@Retention(RetentionPolicy.SOURCE)
public @interface SemanticAction {}
@@ -4962,6 +4970,18 @@
result);
}
+ private static List<Notification.Action> filterOutContextualActions(
+ List<Notification.Action> actions) {
+ List<Notification.Action> nonContextualActions = new ArrayList<>();
+ for (Notification.Action action : actions) {
+ if (action.getSemanticAction()
+ != Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION) {
+ nonContextualActions.add(action);
+ }
+ }
+ return nonContextualActions;
+ }
+
private RemoteViews applyStandardTemplateWithActions(int layoutId,
StandardTemplateParams p, TemplateBindResult result) {
RemoteViews big = applyStandardTemplate(layoutId, p, result);
@@ -4970,7 +4990,11 @@
boolean validRemoteInput = false;
- int N = mActions.size();
+ // In the UI contextual actions appear separately from the standard actions, so we
+ // filter them out here.
+ List<Notification.Action> nonContextualActions = filterOutContextualActions(mActions);
+
+ int N = nonContextualActions.size();
boolean emphazisedMode = mN.fullScreenIntent != null && !p.ambient;
big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
if (N > 0) {
@@ -4979,7 +5003,8 @@
big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
for (int i=0; i<N; i++) {
- Action action = mActions.get(i);
+ Action action = nonContextualActions.get(i);
+
boolean actionHasValidInput = hasValidRemoteInput(action);
validRemoteInput |= actionHasValidInput;
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index a87ee57..d0eff2e 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -276,6 +276,8 @@
in PersistableBundle appExtras, in PersistableBundle launcherExtras,
in SuspendDialogInfo dialogInfo, String callingPackage, int userId);
+ boolean canSuspendPackageForUser(String packageName, int userId);
+
boolean isPackageSuspendedForUser(String packageName, int userId);
PersistableBundle getSuspendedPackageAppExtras(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index d3e4045..e14b17e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5797,6 +5797,30 @@
}
/**
+ * Returns whether or not a given package can be suspended via a call to {@link
+ * #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
+ * SuspendDialogInfo) setPackagesSuspended}. The platform prevents suspending certain critical
+ * packages to keep the device in a functioning state, e.g. the default dialer.
+ * Apps need to hold {@link Manifest.permission#SUSPEND_APPS SUSPEND_APPS} to call this api.
+ *
+ * <p>
+ * Note that this set of critical packages can change with time, so <em>a value of {@code true}
+ * returned by this api does not guarantee that a following call to {@link
+ * #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
+ * SuspendDialogInfo) setPackagesSuspended} for the same package will succeed</em>, especially
+ * if considerable time elapsed between the two calls.
+ *
+ * @param packageName The package to check.
+ * @return {@code true} if the given package can be suspended, {@code false} otherwise.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.SUSPEND_APPS)
+ public boolean canSuspendPackage(@NonNull String packageName) {
+ throw new UnsupportedOperationException("canSuspendPackage not implemented");
+ }
+
+ /**
* @see #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, String)
* @param packageName The name of the package to get the suspended status of.
* @param userId The user id.
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index 2717c4e..a83a33e 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -19,7 +19,6 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.PendingIntent;
-import android.content.Intent;
import android.os.RemoteException;
import com.android.internal.util.Preconditions;
@@ -49,13 +48,25 @@
*/
private final ContextHubInfo mAttachedHub;
- private final CloseGuard mCloseGuard = CloseGuard.get();
+ private final CloseGuard mCloseGuard;
private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
- /* package */ ContextHubClient(ContextHubInfo hubInfo) {
+ /*
+ * True if this is a persistent client (i.e. does not have to close the connection when the
+ * resource is freed from the system).
+ */
+ private final boolean mPersistent;
+
+ /* package */ ContextHubClient(ContextHubInfo hubInfo, boolean persistent) {
mAttachedHub = hubInfo;
- mCloseGuard.open("close");
+ mPersistent = persistent;
+ if (mPersistent) {
+ mCloseGuard = null;
+ } else {
+ mCloseGuard = CloseGuard.get();
+ mCloseGuard.open("close");
+ }
}
/**
@@ -88,11 +99,18 @@
* Closes the connection for this client and the Context Hub Service.
*
* When this function is invoked, the messaging associated with this client is invalidated.
- * All futures messages targeted for this client are dropped at the service.
+ * All futures messages targeted for this client are dropped at the service, and the
+ * ContextHubClient is unregistered from the service.
+ *
+ * If this object has a PendingIntent, i.e. the object was generated via
+ * {@link ContextHubManager.createClient(PendingIntent, ContextHubInfo, long)}, then the
+ * Intent events corresponding to the PendingIntent will no longer be triggered.
*/
public void close() {
if (!mIsClosed.getAndSet(true)) {
- mCloseGuard.close();
+ if (mCloseGuard != null) {
+ mCloseGuard.close();
+ }
try {
mClientProxy.close();
} catch (RemoteException e) {
@@ -102,72 +120,6 @@
}
/**
- * Registers to receive persistent intents for a given nanoapp.
- *
- * This method should be used if the caller wants to receive notifications even after the
- * process exits. The client must have an open connection with the Context Hub Service (i.e. it
- * cannot have been closed through the {@link #close()} method). Only one PendingIntent can be
- * registered at a time for a single ContextHubClient, and the PendingIntent cannot be
- * registered if already registered by a ContextHubClient. If registered successfully, intents
- * will be delivered regarding events for the specified nanoapp from the attached Context Hub.
- * Any unicast messages for this client will also be delivered. The intent will have an extra
- * {@link ContextHubManager.EXTRA_CONTEXT_HUB_INFO} of type {@link ContextHubInfo}, which
- * describes the Context Hub the intent event was for. The intent will also have an extra
- * {@link ContextHubManager.EXTRA_EVENT_TYPE} of type {@link ContextHubManager.Event}, which
- * will contain the type of the event. See {@link ContextHubManager.Event} for description of
- * each event type, along with event-specific extra fields. A client can use
- * {@link ContextHubIntentEvent.fromIntent(Intent)} to parse the Intent generated by the event.
- *
- * When the intent is received, this client can be recreated through
- * {@link ContextHubManager.createClient(PendingIntent, ContextHubInfo,
- * ContextHubClientCallback, Exectutor)}. When recreated, the client can be treated as the
- * same endpoint entity from a nanoapp's perspective, and can be continued to be used to send
- * messages even if the original process has exited.
- *
- * Intents will be delivered until it is unregistered through
- * {@link #unregisterIntent(PendingIntent)}. Note that the registration of this client will
- * continued to be maintained at the Context Hub Service until
- * {@link #unregisterIntent(PendingIntent)} is called for registered intents.
- *
- * @param pendingIntent the PendingIntent to register for this client
- * @param nanoAppId the unique ID of the nanoapp to receive events for
- * @return true on success, false otherwise
- *
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public boolean registerIntent(@NonNull PendingIntent pendingIntent, long nanoAppId) {
- Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");
-
- try {
- return mClientProxy.registerIntent(pendingIntent, nanoAppId);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Unregisters an intent previously registered via {@link #registerIntent(PendingIntent, long)}.
- * If this intent has not been registered for this client, this method returns false.
- *
- * @param pendingIntent the PendingIntent to unregister
- *
- * @return true on success, false otherwise
- *
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- public boolean unregisterIntent(@NonNull PendingIntent pendingIntent) {
- Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");
-
- try {
- return mClientProxy.unregisterIntent(pendingIntent);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Sends a message to a nanoapp through the Context Hub Service.
*
* This function returns RESULT_SUCCESS if the message has reached the HAL, but
@@ -200,7 +152,9 @@
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
}
- close();
+ if (!mPersistent) {
+ close();
+ }
} finally {
super.finalize();
}
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index 36123e3..51daa92 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -15,6 +15,7 @@
*/
package android.hardware.location;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.hardware.contexthub.V1_0.ContextHub;
import android.os.Parcel;
@@ -267,6 +268,34 @@
return retVal;
}
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (object == this) {
+ return true;
+ }
+
+ boolean isEqual = false;
+ if (object instanceof ContextHubInfo) {
+ ContextHubInfo other = (ContextHubInfo) object;
+ isEqual = (other.getId() == mId)
+ && other.getName().equals(mName)
+ && other.getVendor().equals(mVendor)
+ && other.getToolchain().equals(mToolchain)
+ && (other.getToolchainVersion() == mToolchainVersion)
+ && (other.getStaticSwVersion() == getStaticSwVersion())
+ && (other.getChrePlatformId() == mChrePlatformId)
+ && (other.getPeakMips() == mPeakMips)
+ && (other.getStoppedPowerDrawMw() == mStoppedPowerDrawMw)
+ && (other.getSleepPowerDrawMw() == mSleepPowerDrawMw)
+ && (other.getPeakPowerDrawMw() == mPeakPowerDrawMw)
+ && (other.getMaxPacketLengthBytes() == mMaxPacketLengthBytes)
+ && Arrays.equals(other.getSupportedSensors(), mSupportedSensors)
+ && Arrays.equals(other.getMemoryRegions(), mMemoryRegions);
+ }
+
+ return isEqual;
+ }
+
private ContextHubInfo(Parcel in) {
mId = in.readInt();
mName = in.readString();
diff --git a/core/java/android/hardware/location/ContextHubIntentEvent.java b/core/java/android/hardware/location/ContextHubIntentEvent.java
index 96e7496..b2aa268 100644
--- a/core/java/android/hardware/location/ContextHubIntentEvent.java
+++ b/core/java/android/hardware/location/ContextHubIntentEvent.java
@@ -16,6 +16,7 @@
package android.hardware.location;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Intent;
@@ -206,6 +207,37 @@
return out + "]";
}
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (object == this) {
+ return true;
+ }
+
+ boolean isEqual = false;
+ if (object instanceof ContextHubIntentEvent) {
+ ContextHubIntentEvent other = (ContextHubIntentEvent) object;
+ if (other.getEventType() == mEventType
+ && other.getContextHubInfo().equals(mContextHubInfo)) {
+ isEqual = true;
+ try {
+ if (mEventType != ContextHubManager.EVENT_HUB_RESET) {
+ isEqual &= (other.getNanoAppId() == mNanoAppId);
+ }
+ if (mEventType == ContextHubManager.EVENT_NANOAPP_ABORTED) {
+ isEqual &= (other.getNanoAppAbortCode() == mNanoAppAbortCode);
+ }
+ if (mEventType == ContextHubManager.EVENT_NANOAPP_MESSAGE) {
+ isEqual &= other.getNanoAppMessage().equals(mNanoAppMessage);
+ }
+ } catch (UnsupportedOperationException e) {
+ isEqual = false;
+ }
+ }
+ }
+
+ return isEqual;
+ }
+
private static void hasExtraOrThrow(Intent intent, String extra) {
if (!intent.hasExtra(extra)) {
throw new IllegalArgumentException("Intent did not have extra: " + extra);
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 9acefa5..843db66 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -25,6 +25,7 @@
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
@@ -757,7 +758,7 @@
Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
Preconditions.checkNotNull(executor, "Executor cannot be null");
- ContextHubClient client = new ContextHubClient(hubInfo);
+ ContextHubClient client = new ContextHubClient(hubInfo, false /* persistent */);
IContextHubClientCallback clientInterface = createClientCallback(
client, callback, executor);
@@ -793,43 +794,55 @@
}
/**
- * Creates a ContextHubClient based on an Intent received by the Context Hub Service.
+ * Creates a ContextHubClient that will receive notifications based on Intent events.
*
- * This method is intended to be used after receiving an Intent received as a result of
- * {@link ContextHubClient.registerIntent(PendingIntent, long)}, and must have been created
- * through {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} or
- * equivalent at an earlier time.
+ * This method should be used instead of {@link #createClient(ContextHubInfo,
+ * ContextHubClientCallback)} and the equivalent API if the caller wants to preserve the
+ * messaging endpoint of a ContextHubClient, even after a process exits. If the PendingIntent
+ * with the provided nanoapp has already been registered at the service previously, then the
+ * same ContextHubClient will be regenerated without creating a new client connection at the
+ * service. Note that the PendingIntent, nanoapp, and Context Hub must all match in identifying
+ * a previously registered ContextHubClient. If a client is regenerated, it can be treated as
+ * the same endpoint entity from a nanoapp's perspective, and can be continued to be
+ * used to send messages even if the original process has exited.
*
- * @param pendingIntent the PendingIntent that has been registered with a client
+ * If registered successfully, intents will be delivered regarding events or messages from the
+ * specified nanoapp from the attached Context Hub. The intent will have an extra
+ * {@link ContextHubManager.EXTRA_CONTEXT_HUB_INFO} of type {@link ContextHubInfo}, which
+ * describes the Context Hub the intent event was for. The intent will also have an extra
+ * {@link ContextHubManager.EXTRA_EVENT_TYPE} of type {@link ContextHubManager.Event}, which
+ * will contain the type of the event. See {@link ContextHubManager.Event} for description of
+ * each event type, along with event-specific extra fields. The client can also use
+ * {@link ContextHubIntentEvent.fromIntent(Intent)} to parse the Intent generated by the event.
+ *
+ * Intent events will be delivered until it is unregistered through
+ * {@link ContextHubClient.close()}. Note that the registration of this
+ * ContextHubClient at the Context Hub Service will continued to be maintained until
+ * {@link ContextHubClient.close()} is called.
+ *
* @param hubInfo the hub to attach this client to
- * @param callback the notification callback to register
- * @param executor the executor to invoke the callback
+ * @param pendingIntent the PendingIntent to register to the client
+ * @param nanoAppId the ID of the nanoapp that Intent events will be generated for
* @return the registered client object
*
- * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or pendingIntent
- * was not associated with a client
- * @throws IllegalStateException if the client is already registered to a valid callback
- * @throws NullPointerException if pendingIntent, hubInfo, callback, or executor is null
+ * @throws IllegalArgumentException if hubInfo does not represent a valid hub
+ * @throws IllegalStateException if there were too many registered clients at the service
+ * @throws NullPointerException if pendingIntent or hubInfo is null
*
* @hide
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
@NonNull public ContextHubClient createClient(
- @NonNull PendingIntent pendingIntent, @NonNull ContextHubInfo hubInfo,
- @NonNull ContextHubClientCallback callback,
- @NonNull @CallbackExecutor Executor executor) {
- Preconditions.checkNotNull(pendingIntent, "PendingIntent cannot be null");
- Preconditions.checkNotNull(callback, "Callback cannot be null");
- Preconditions.checkNotNull(hubInfo, "ContextHubInfo cannot be null");
- Preconditions.checkNotNull(executor, "Executor cannot be null");
+ @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) {
+ Preconditions.checkNotNull(pendingIntent);
+ Preconditions.checkNotNull(hubInfo);
- ContextHubClient client = new ContextHubClient(hubInfo);
- IContextHubClientCallback clientInterface = createClientCallback(
- client, callback, executor);
+ ContextHubClient client = new ContextHubClient(hubInfo, true /* persistent */);
IContextHubClient clientProxy;
try {
- clientProxy = mService.bindClient(pendingIntent, clientInterface, hubInfo.getId());
+ clientProxy = mService.createPendingIntentClient(
+ hubInfo.getId(), pendingIntent, nanoAppId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -839,30 +852,6 @@
}
/**
- * Equivalent to {@link #createClient(PendingIntent, ContextHubInfo, ContextHubClientCallback,
- * Executor)} with the executor using the main thread's Looper.
- *
- * @param pendingIntent the PendingIntent that has been registered with a client
- * @param hubInfo the hub to attach this client to
- * @param callback the notification callback to register
- * @return the registered client object
- *
- * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or pendingIntent
- * was not associated with a client
- * @throws IllegalStateException if the client is already registered to a valid callback
- * @throws NullPointerException if pendingIntent, hubInfo, or callback is null
- *
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
- @NonNull public ContextHubClient createClient(
- @NonNull PendingIntent pendingIntent, @NonNull ContextHubInfo hubInfo,
- @NonNull ContextHubClientCallback callback) {
- return createClient(
- pendingIntent, hubInfo, callback, new HandlerExecutor(Handler.getMain()));
- }
-
- /**
* Unregister a callback for receive messages from the context hub.
*
* @see Callback
diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl
index b539414..e33545c 100644
--- a/core/java/android/hardware/location/IContextHubClient.aidl
+++ b/core/java/android/hardware/location/IContextHubClient.aidl
@@ -29,10 +29,4 @@
// Closes the connection with the Context Hub
void close();
-
- // Registers a PendingIntent with the client
- boolean registerIntent(in PendingIntent pendingIntent, long nanoAppId);
-
- // Unregisters a PendingIntent from the client
- boolean unregisterIntent(in PendingIntent pendingIntent);
}
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 9b0acdf..19ed694 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -61,10 +61,9 @@
// Creates a client to send and receive messages
IContextHubClient createClient(in IContextHubClientCallback client, int contextHubId);
- // Binds an existing client to a new callback interface, provided a previously registered
- // PendingIntent
- IContextHubClient bindClient(
- in PendingIntent pendingIntent, in IContextHubClientCallback client, int contextHubId);
+ // Creates a PendingIntent-based client to send and receive messages
+ IContextHubClient createPendingIntentClient(
+ int contextHubId, in PendingIntent pendingIntent, long nanoAppId);
// Returns a list of ContextHub objects of available hubs
List<ContextHubInfo> getContextHubs();
diff --git a/core/java/android/hardware/location/MemoryRegion.java b/core/java/android/hardware/location/MemoryRegion.java
index 857434e..3d9e859 100644
--- a/core/java/android/hardware/location/MemoryRegion.java
+++ b/core/java/android/hardware/location/MemoryRegion.java
@@ -16,6 +16,7 @@
package android.hardware.location;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -106,6 +107,25 @@
}
@Override
+ public boolean equals(@Nullable Object object) {
+ if (object == this) {
+ return true;
+ }
+
+ boolean isEqual = false;
+ if (object instanceof MemoryRegion) {
+ MemoryRegion other = (MemoryRegion) object;
+ isEqual = (other.getCapacityBytes() == mSizeBytes)
+ && (other.getFreeCapacityBytes() == mSizeBytesFree)
+ && (other.isReadable() == mIsReadable)
+ && (other.isWritable() == mIsWritable)
+ && (other.isExecutable() == mIsExecutable);
+ }
+
+ return isEqual;
+ }
+
+ @Override
public int describeContents() {
return 0;
}
diff --git a/core/java/android/hardware/location/NanoAppMessage.java b/core/java/android/hardware/location/NanoAppMessage.java
index 6635258..9f90d59 100644
--- a/core/java/android/hardware/location/NanoAppMessage.java
+++ b/core/java/android/hardware/location/NanoAppMessage.java
@@ -15,10 +15,13 @@
*/
package android.hardware.location;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Arrays;
+
/**
* A class describing messages send to or from nanoapps through the Context Hub Service.
*
@@ -168,4 +171,22 @@
return ret;
}
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ if (object == this) {
+ return true;
+ }
+
+ boolean isEqual = false;
+ if (object instanceof NanoAppMessage) {
+ NanoAppMessage other = (NanoAppMessage) object;
+ isEqual = (other.getNanoAppId() == mNanoAppId)
+ && (other.getMessageType() == mMessageType)
+ && (other.isBroadcastMessage() == mIsBroadcasted)
+ && Arrays.equals(other.getMessageBody(), mMessageBody);
+ }
+
+ return isEqual;
+ }
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index a307cd8..1c1db68 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -24,6 +24,7 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
+import android.service.dreams.Sandman;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
@@ -1001,6 +1002,29 @@
}
/**
+ * Requests the device to start dreaming.
+ * <p>
+ * If dream can not be started, for example if another {@link PowerManager} transition is in
+ * progress, does nothing. Unlike {@link #nap(long)}, this does not put device to sleep when
+ * dream ends.
+ * </p><p>
+ * Requires the {@link android.Manifest.permission#WRITE_DREAM_STATE} permission.
+ * </p>
+ *
+ * @param time The time when the request to nap was issued, in the
+ * {@link SystemClock#uptimeMillis()} time base. This timestamp may be used to correctly
+ * order the dream request with other power management functions. It should be set
+ * to the timestamp of the input event that caused the request to dream.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)
+ public void dream(long time) {
+ Sandman.startDreamByUserRequest(mContext);
+ }
+
+ /**
* Boosts the brightness of the screen to maximum for a predetermined
* period of time. This is used to make the screen more readable in bright
* daylight for a short duration.
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index f4dcce1..15ded8d 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -154,7 +154,7 @@
// User authenticators.
public static final int HW_AUTH_PASSWORD = 1 << 0;
- public static final int HW_AUTH_FINGERPRINT = 1 << 1;
+ public static final int HW_AUTH_BIOMETRIC = 1 << 1;
// Error codes.
public static final int KM_ERROR_OK = 0;
diff --git a/core/java/android/service/carrier/ApnService.java b/core/java/android/service/carrier/ApnService.java
new file mode 100644
index 0000000..d53eb37
--- /dev/null
+++ b/core/java/android/service/carrier/ApnService.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.carrier;
+
+import android.annotation.SystemApi;
+import android.annotation.WorkerThread;
+import android.app.Service;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.android.internal.telephony.IApnSourceService;
+
+import java.util.List;
+
+/**
+ * A service that the system can call to restore default APNs.
+ * <p>
+ * To extend this class, specify the full name of your implementation in the resource file
+ * {@code packages/providers/TelephonyProvider/res/values/config.xml} as the
+ * {@code apn_source_service}.
+ * </p>
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class ApnService extends Service {
+
+ private static final String LOG_TAG = "ApnService";
+
+ private final IApnSourceService.Stub mBinder = new IApnSourceService.Stub() {
+ /**
+ * Retreive APNs for the default slot index.
+ */
+ @Override
+ public ContentValues[] getApns(int subId) {
+ try {
+ List<ContentValues> apns = ApnService.this.onRestoreApns(subId);
+ return apns.toArray(new ContentValues[apns.size()]);
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Error in getApns for subId=" + subId + ": " + e.getMessage(), e);
+ return null;
+ }
+ }
+ };
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ /**
+ * Override this method to restore default user APNs with a carrier service instead of the
+ * built in platform xml APNs list.
+ * <p>
+ * This method is called by the TelephonyProvider when the user requests restoring the default
+ * APNs. It should return a list of ContentValues representing the default APNs for the given
+ * subId.
+ */
+ @WorkerThread
+ public abstract List<ContentValues> onRestoreApns(int subId);
+}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 2d5f3bf..5504427 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -2372,52 +2372,6 @@
public Directions(int[] dirs) {
mDirections = dirs;
}
-
- /**
- * Returns number of BiDi runs.
- *
- * @hide
- */
- public @IntRange(from = 0) int getRunCount() {
- return mDirections.length / 2;
- }
-
- /**
- * Returns the start offset of the BiDi run.
- *
- * @param runIndex the index of the BiDi run
- * @return the start offset of the BiDi run.
- * @hide
- */
- public @IntRange(from = 0) int getRunStart(@IntRange(from = 0) int runIndex) {
- return mDirections[runIndex * 2];
- }
-
- /**
- * Returns the length of the BiDi run.
- *
- * Note that this method may return too large number due to reducing the number of object
- * allocations. The too large number means the remaining part is assigned to this run. The
- * caller must clamp the returned value.
- *
- * @param runIndex the index of the BiDi run
- * @return the length of the BiDi run.
- * @hide
- */
- public @IntRange(from = 0) int getRunLength(@IntRange(from = 0) int runIndex) {
- return mDirections[runIndex * 2 + 1] & RUN_LENGTH_MASK;
- }
-
- /**
- * Returns true if the BiDi run is RTL.
- *
- * @param runIndex the index of the BiDi run
- * @return true if the BiDi run is RTL.
- * @hide
- */
- public boolean isRunRtl(int runIndex) {
- return (mDirections[runIndex * 2 + 1] & RUN_RTL_FLAG) != 0;
- }
}
/**
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 6eb433a..44dfd11 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -16,7 +16,6 @@
package android.text;
-import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
@@ -52,8 +51,6 @@
public class TextLine {
private static final boolean DEBUG = false;
- private static final char TAB_CHAR = '\t';
-
private TextPaint mPaint;
@UnsupportedAppUsage
private CharSequence mText;
@@ -201,7 +198,7 @@
}
}
- mCharsValid = hasReplacement;
+ mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
if (mCharsValid) {
if (mChars == null || mChars.length < mLen) {
@@ -235,10 +232,6 @@
mEllipsisEnd = ellipsisStart != ellipsisEnd ? ellipsisEnd : 0;
}
- private char charAt(int i) {
- return mCharsValid ? mChars[i] : mText.charAt(i + mStart);
- }
-
/**
* Justify the line to the given width.
*/
@@ -268,23 +261,51 @@
* @param bottom the bottom of the line
*/
void draw(Canvas c, float x, int top, int y, int bottom) {
+ if (!mHasTabs) {
+ if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
+ drawRun(c, 0, mLen, false, x, top, y, bottom, false);
+ return;
+ }
+ if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
+ drawRun(c, 0, mLen, true, x, top, y, bottom, false);
+ return;
+ }
+ }
+
float h = 0;
- final int runCount = mDirections.getRunCount();
- for (int runIndex = 0; runIndex < runCount; runIndex++) {
- final int runStart = mDirections.getRunStart(runIndex);
- final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
- final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ int[] runs = mDirections.mDirections;
- int segStart = runStart;
+ int lastRunIndex = runs.length - 2;
+ for (int i = 0; i < runs.length; i += 2) {
+ int runStart = runs[i];
+ int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
+ if (runLimit > mLen) {
+ runLimit = mLen;
+ }
+ boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
+
+ int segstart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
- if (j == runLimit || charAt(j) == TAB_CHAR) {
- h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
- runIndex != (runCount - 1) || j != mLen);
+ int codept = 0;
+ if (mHasTabs && j < runLimit) {
+ codept = mChars[j];
+ if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
+ codept = Character.codePointAt(mChars, j);
+ if (codept > 0xFFFF) {
+ ++j;
+ continue;
+ }
+ }
+ }
- if (j != runLimit) { // charAt(j) == TAB_CHAR
+ if (j == runLimit || codept == '\t') {
+ h += drawRun(c, segstart, j, runIsRtl, x+h, top, y, bottom,
+ i != lastRunIndex || j != mLen);
+
+ if (codept == '\t') {
h = mDir * nextTab(h * mDir);
}
- segStart = j + 1;
+ segstart = j + 1;
}
}
}
@@ -302,81 +323,75 @@
}
/**
- * Returns the signed graphical offset from the leading margin.
+ * Returns information about a position on the line.
*
- * Following examples are all for measuring offset=3. LX(e.g. L0, L1, ...) denotes a
- * character which has LTR BiDi property. On the other hand, RX(e.g. R0, R1, ...) denotes a
- * character which has RTL BiDi property. Assuming all character has 1em width.
- *
- * Example 1: All LTR chars within LTR context
- * Input Text (logical) : L0 L1 L2 L3 L4 L5 L6 L7 L8
- * Input Text (visual) : L0 L1 L2 L3 L4 L5 L6 L7 L8
- * Output(trailing=true) : |--------| (Returns 3em)
- * Output(trailing=false): |--------| (Returns 3em)
- *
- * Example 2: All RTL chars within RTL context.
- * Input Text (logical) : R0 R1 R2 R3 R4 R5 R6 R7 R8
- * Input Text (visual) : R8 R7 R6 R5 R4 R3 R2 R1 R0
- * Output(trailing=true) : |--------| (Returns -3em)
- * Output(trailing=false): |--------| (Returns -3em)
- *
- * Example 3: BiDi chars within LTR context.
- * Input Text (logical) : L0 L1 L2 R3 R4 R5 L6 L7 L8
- * Input Text (visual) : L0 L1 L2 R5 R4 R3 L6 L7 L8
- * Output(trailing=true) : |-----------------| (Returns 6em)
- * Output(trailing=false): |--------| (Returns 3em)
- *
- * Example 4: BiDi chars within RTL context.
- * Input Text (logical) : L0 L1 L2 R3 R4 R5 L6 L7 L8
- * Input Text (visual) : L6 L7 L8 R5 R4 R3 L0 L1 L2
- * Output(trailing=true) : |-----------------| (Returns -6em)
- * Output(trailing=false): |--------| (Returns -3em)
- *
- * @param offset the line-relative character offset, between 0 and the line length, inclusive
- * @param trailing no effect if the offset is not on the BiDi transition offset. If the offset
- * is on the BiDi transition offset and true is passed, the offset is regarded
- * as the edge of the trailing run's edge. If false, the offset is regarded as
- * the edge of the preceding run's edge. See example above.
- * @param fmi receives metrics information about the requested character, can be null
- * @return the signed graphical offset from the leading margin to the requested character edge.
- * The positive value means the offset is right from the leading edge. The negative
- * value means the offset is left from the leading edge.
+ * @param offset the line-relative character offset, between 0 and the
+ * line length, inclusive
+ * @param trailing true to measure the trailing edge of the character
+ * before offset, false to measure the leading edge of the character
+ * at offset.
+ * @param fmi receives metrics information about the requested
+ * character, can be null.
+ * @return the signed offset from the leading margin to the requested
+ * character edge.
*/
- public float measure(@IntRange(from = 0) int offset, boolean trailing,
- @NonNull FontMetricsInt fmi) {
- if (offset > mLen) {
- throw new IndexOutOfBoundsException(
- "offset(" + offset + ") should be less than line limit(" + mLen + ")");
- }
- final int target = trailing ? offset - 1 : offset;
+ public float measure(int offset, boolean trailing, FontMetricsInt fmi) {
+ int target = trailing ? offset - 1 : offset;
if (target < 0) {
return 0;
}
float h = 0;
- for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
- final int runStart = mDirections.getRunStart(runIndex);
- final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
- final boolean runIsRtl = mDirections.isRunRtl(runIndex);
- int segStart = runStart;
+ if (!mHasTabs) {
+ if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
+ return measureRun(0, offset, mLen, false, fmi);
+ }
+ if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
+ return measureRun(0, offset, mLen, true, fmi);
+ }
+ }
+
+ char[] chars = mChars;
+ int[] runs = mDirections.mDirections;
+ for (int i = 0; i < runs.length; i += 2) {
+ int runStart = runs[i];
+ int runLimit = runStart + (runs[i+1] & Layout.RUN_LENGTH_MASK);
+ if (runLimit > mLen) {
+ runLimit = mLen;
+ }
+ boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
+
+ int segstart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
- if (j == runLimit || charAt(j) == TAB_CHAR) {
- final boolean targetIsInThisSegment = target >= segStart && target < j;
- final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+ int codept = 0;
+ if (mHasTabs && j < runLimit) {
+ codept = chars[j];
+ if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
+ codept = Character.codePointAt(chars, j);
+ if (codept > 0xFFFF) {
+ ++j;
+ continue;
+ }
+ }
+ }
- if (targetIsInThisSegment && sameDirection) {
- return h + measureRun(segStart, offset, j, runIsRtl, fmi);
+ if (j == runLimit || codept == '\t') {
+ boolean inSegment = target >= segstart && target < j;
+
+ boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+ if (inSegment && advance) {
+ return h + measureRun(segstart, offset, j, runIsRtl, fmi);
}
- final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi);
- h += sameDirection ? segmentWidth : -segmentWidth;
+ float w = measureRun(segstart, j, j, runIsRtl, fmi);
+ h += advance ? w : -w;
- if (targetIsInThisSegment) {
- return h + measureRun(segStart, offset, j, runIsRtl, null);
+ if (inSegment) {
+ return h + measureRun(segstart, offset, j, runIsRtl, null);
}
- if (j != runLimit) { // charAt(j) == TAB_CHAR
+ if (codept == '\t') {
if (offset == j) {
return h;
}
@@ -386,7 +401,7 @@
}
}
- segStart = j + 1;
+ segstart = j + 1;
}
}
}
@@ -411,29 +426,62 @@
}
float h = 0;
- for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
- final int runStart = mDirections.getRunStart(runIndex);
- final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
- final boolean runIsRtl = mDirections.isRunRtl(runIndex);
- int segStart = runStart;
+ if (!mHasTabs) {
+ if (mDirections == Layout.DIRS_ALL_LEFT_TO_RIGHT) {
+ for (int offset = 0; offset <= mLen; ++offset) {
+ measurement[offset] = measureRun(0, offset, mLen, false, fmi);
+ }
+ return measurement;
+ }
+ if (mDirections == Layout.DIRS_ALL_RIGHT_TO_LEFT) {
+ for (int offset = 0; offset <= mLen; ++offset) {
+ measurement[offset] = measureRun(0, offset, mLen, true, fmi);
+ }
+ return measurement;
+ }
+ }
+
+ char[] chars = mChars;
+ int[] runs = mDirections.mDirections;
+ for (int i = 0; i < runs.length; i += 2) {
+ int runStart = runs[i];
+ int runLimit = runStart + (runs[i + 1] & Layout.RUN_LENGTH_MASK);
+ if (runLimit > mLen) {
+ runLimit = mLen;
+ }
+ boolean runIsRtl = (runs[i + 1] & Layout.RUN_RTL_FLAG) != 0;
+
+ int segstart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) {
- if (j == runLimit || charAt(j) == TAB_CHAR) {
- final float oldh = h;
- final boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
- final float w = measureRun(segStart, j, j, runIsRtl, fmi);
+ int codept = 0;
+ if (mHasTabs && j < runLimit) {
+ codept = chars[j];
+ if (codept >= 0xD800 && codept < 0xDC00 && j + 1 < runLimit) {
+ codept = Character.codePointAt(chars, j);
+ if (codept > 0xFFFF) {
+ ++j;
+ continue;
+ }
+ }
+ }
+
+ if (j == runLimit || codept == '\t') {
+ float oldh = h;
+ boolean advance = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
+ float w = measureRun(segstart, j, j, runIsRtl, fmi);
h += advance ? w : -w;
- final float baseh = advance ? oldh : h;
+ float baseh = advance ? oldh : h;
FontMetricsInt crtfmi = advance ? fmi : null;
- for (int offset = segStart; offset <= j && offset <= mLen; ++offset) {
- if (target[offset] >= segStart && target[offset] < j) {
+ for (int offset = segstart; offset <= j && offset <= mLen; ++offset) {
+ if (target[offset] >= segstart && target[offset] < j) {
measurement[offset] =
- baseh + measureRun(segStart, offset, j, runIsRtl, crtfmi);
+ baseh + measureRun(segstart, offset, j, runIsRtl, crtfmi);
}
}
- if (j != runLimit) { // charAt(j) == TAB_CHAR
+ if (codept == '\t') {
if (target[j] == j) {
measurement[j] = h;
}
@@ -443,7 +491,7 @@
}
}
- segStart = j + 1;
+ segstart = j + 1;
}
}
}
@@ -815,6 +863,7 @@
} else {
final int delta = mStart;
if (mComputed == null) {
+ // TODO: Enable measured getRunAdvance for ReplacementSpan and RTL text.
return wp.getRunAdvance(mText, delta + start, delta + end,
delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
} else {
diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java
index 043b31d..03d9955 100644
--- a/core/java/android/view/DisplayListCanvas.java
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -20,6 +20,7 @@
import android.graphics.BaseRecordingCanvas;
import android.graphics.CanvasProperty;
import android.graphics.Paint;
+import android.os.Build;
/**
* This class exists temporarily to workaround broken apps
@@ -36,13 +37,13 @@
}
/** @hide */
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public abstract void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top,
CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx,
CanvasProperty<Float> ry, CanvasProperty<Paint> paint);
/** @hide */
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
CanvasProperty<Float> radius, CanvasProperty<Paint> paint);
}
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index fa30221..4a5ccdf 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -218,11 +218,6 @@
layoutRight = end - paddingEnd;
end = layoutLeft = layoutRight - child.getMeasuredWidth();
}
- if (child == mAudiblyAlertedIcon) {
- int paddingEnd = mContentEndMargin;
- layoutRight = end - paddingEnd;
- end = layoutLeft = layoutRight - child.getMeasuredWidth();
- }
if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
int ltrLeft = layoutLeft;
layoutLeft = getWidth() - layoutRight;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c7f25d7..5f1336f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4732,6 +4732,16 @@
private TouchDelegate mTouchDelegate = null;
/**
+ * While touch exploration is in use, set to true when hovering across boundaries and
+ * inside the touch area of the delegate at receiving {@link MotionEvent#ACTION_HOVER_ENTER}
+ * or {@link MotionEvent#ACTION_HOVER_MOVE}. False when leaving boundaries or receiving a
+ * {@link MotionEvent#ACTION_HOVER_EXIT}.
+ * Note that children of view group are excluded in the touch area.
+ * @see #dispatchTouchExplorationHoverEvent
+ */
+ private boolean mHoveringTouchDelegate = false;
+
+ /**
* Solid color to use as a background when creating the drawing cache. Enables
* the cache to use 16 bit bitmaps instead of 32 bit.
*/
@@ -13965,6 +13975,96 @@
}
/**
+ * Dispatching hover events to {@link TouchDelegate} to improve accessibility.
+ * <p>
+ * This method is dispatching hover events to the delegate target to support explore by touch.
+ * Similar to {@link ViewGroup#dispatchTouchEvent}, this method send proper hover events to
+ * the delegate target according to the pointer and the touch area of the delegate while touch
+ * exploration enabled.
+ * </p>
+ *
+ * @param event The motion event dispatch to the delegate target.
+ * @return True if the event was handled, false otherwise.
+ *
+ * @see #onHoverEvent
+ */
+ private boolean dispatchTouchExplorationHoverEvent(MotionEvent event) {
+ final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+ if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
+ return false;
+ }
+
+ final boolean oldHoveringTouchDelegate = mHoveringTouchDelegate;
+ final int action = event.getActionMasked();
+ boolean pointInDelegateRegion = false;
+ boolean handled = false;
+
+ final AccessibilityNodeInfo.TouchDelegateInfo info = mTouchDelegate.getTouchDelegateInfo();
+ for (int i = 0; i < info.getRegionCount(); i++) {
+ Region r = info.getRegionAt(i);
+ if (r.contains((int) event.getX(), (int) event.getY())) {
+ pointInDelegateRegion = true;
+ }
+ }
+
+ // Explore by touch should dispatch events to children under the pointer first if any
+ // before dispatching to TouchDelegate. For non-hoverable views that do not consume
+ // hover events but receive accessibility focus, it should also not delegate to these
+ // views when hovered.
+ if (!oldHoveringTouchDelegate) {
+ if ((action == MotionEvent.ACTION_HOVER_ENTER
+ || action == MotionEvent.ACTION_HOVER_MOVE)
+ && !pointInHoveredChild(event)
+ && pointInDelegateRegion) {
+ mHoveringTouchDelegate = true;
+ }
+ } else {
+ if (action == MotionEvent.ACTION_HOVER_EXIT
+ || (action == MotionEvent.ACTION_HOVER_MOVE
+ && (pointInHoveredChild(event) || !pointInDelegateRegion))) {
+ mHoveringTouchDelegate = false;
+ }
+ }
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_MOVE:
+ if (oldHoveringTouchDelegate && mHoveringTouchDelegate) {
+ // Inside bounds, dispatch as is.
+ handled = mTouchDelegate.onTouchExplorationHoverEvent(event);
+ } else if (!oldHoveringTouchDelegate && mHoveringTouchDelegate) {
+ // Moving inbound, synthesize hover enter.
+ MotionEvent eventNoHistory = (event.getHistorySize() == 0)
+ ? event : MotionEvent.obtainNoHistory(event);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
+ handled = mTouchDelegate.onTouchExplorationHoverEvent(eventNoHistory);
+ eventNoHistory.setAction(action);
+ handled |= mTouchDelegate.onTouchExplorationHoverEvent(eventNoHistory);
+ } else if (oldHoveringTouchDelegate && !mHoveringTouchDelegate) {
+ // Moving outbound, synthesize hover exit.
+ final boolean hoverExitPending = event.isHoverExitPending();
+ event.setHoverExitPending(true);
+ mTouchDelegate.onTouchExplorationHoverEvent(event);
+ MotionEvent eventNoHistory = (event.getHistorySize() == 0)
+ ? event : MotionEvent.obtainNoHistory(event);
+ eventNoHistory.setHoverExitPending(hoverExitPending);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
+ mTouchDelegate.onTouchExplorationHoverEvent(eventNoHistory);
+ } // else: outside bounds, do nothing.
+ break;
+ case MotionEvent.ACTION_HOVER_ENTER:
+ if (!oldHoveringTouchDelegate && mHoveringTouchDelegate) {
+ handled = mTouchDelegate.onTouchExplorationHoverEvent(event);
+ }
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ if (oldHoveringTouchDelegate) {
+ mTouchDelegate.onTouchExplorationHoverEvent(event);
+ }
+ break;
+ }
+ return handled;
+ }
+
+ /**
* Implement this method to handle hover events.
* <p>
* This method is called whenever a pointer is hovering into, over, or out of the
@@ -14001,15 +14101,8 @@
* @see #onHoverChanged
*/
public boolean onHoverEvent(MotionEvent event) {
- // Explore by touch should dispatch events to children under pointer first if any before
- // dispatching to TouchDelegate. For children non-hoverable that will not consume events,
- // it should also not delegate when they got the pointer hovered.
- if (mTouchDelegate != null && !pointInHoveredChild(event)) {
- final AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
- if (manager.isEnabled() && manager.isTouchExplorationEnabled()
- && mTouchDelegate.onTouchExplorationHoverEvent(event)) {
- return true;
- }
+ if (mTouchDelegate != null && dispatchTouchExplorationHoverEvent(event)) {
+ return true;
}
// The root view may receive hover (or touch) events that are outside the bounds of
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 9f509b1..7756a19 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -17,6 +17,7 @@
package android.widget;
import android.annotation.FloatRange;
+import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -40,6 +41,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
+import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.PixelCopy;
@@ -55,11 +57,15 @@
import com.android.internal.R;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Android magnifier widget. Can be used by any view which is attached to a window.
*/
@UiThread
public final class Magnifier {
+ private static final String TAG = "Magnifier";
// Use this to specify that a previous configuration value does not exist.
private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
// The callbacks of the pixel copy requests will be invoked on
@@ -83,8 +89,8 @@
private int mSourceWidth;
// The height of the content that will be copied to the magnifier.
private int mSourceHeight;
- // Whether the zoom of the magnifier has changed since last content copy.
- private boolean mDirtyZoom;
+ // Whether the zoom of the magnifier or the view position have changed since last content copy.
+ private boolean mDirtyState;
// The elevation of the window containing the magnifier.
private final float mWindowElevation;
// The corner radius of the window containing the magnifier.
@@ -95,6 +101,14 @@
private final int mDefaultVerticalSourceToMagnifierOffset;
// Whether the magnifier will be clamped inside the main surface and not overlap system insets.
private final boolean mForcePositionWithinWindowSystemInsetsBounds;
+ // The behavior of the left bound of the rectangle where the content can be copied from.
+ private @SourceBound int mLeftContentBound;
+ // The behavior of the top bound of the rectangle where the content can be copied from.
+ private @SourceBound int mTopContentBound;
+ // The behavior of the right bound of the rectangle where the content can be copied from.
+ private @SourceBound int mRightContentBound;
+ // The behavior of the bottom bound of the rectangle where the content can be copied from.
+ private @SourceBound int mBottomContentBound;
// The parent surface for the magnifier surface.
private SurfaceInfo mParentSurface;
// The surface where the content will be copied from.
@@ -145,6 +159,10 @@
params.mVerticalDefaultSourceToMagnifierOffset;
mForcePositionWithinWindowSystemInsetsBounds =
params.mForcePositionWithinWindowSystemInsetsBounds;
+ mLeftContentBound = params.mLeftContentBound;
+ mTopContentBound = params.mTopContentBound;
+ mRightContentBound = params.mRightContentBound;
+ mBottomContentBound = params.mBottomContentBound;
// The view's surface coordinates will not be updated until the magnifier is first shown.
mViewCoordinatesInSurface = new int[2];
}
@@ -195,8 +213,6 @@
public void show(@FloatRange(from = 0) float sourceCenterX,
@FloatRange(from = 0) float sourceCenterY,
float magnifierCenterX, float magnifierCenterY) {
- sourceCenterX = Math.max(0, Math.min(sourceCenterX, mView.getWidth()));
- sourceCenterY = Math.max(0, Math.min(sourceCenterY, mView.getHeight()));
obtainSurfaces();
obtainContentCoordinates(sourceCenterX, sourceCenterY);
@@ -205,7 +221,7 @@
final int startX = mClampedCenterZoomCoords.x - mSourceWidth / 2;
final int startY = mClampedCenterZoomCoords.y - mSourceHeight / 2;
if (sourceCenterX != mPrevShowSourceCoords.x || sourceCenterY != mPrevShowSourceCoords.y
- || mDirtyZoom) {
+ || mDirtyState) {
if (mWindow == null) {
synchronized (mLock) {
mWindow = new InternalPopupWindow(mView.getContext(), mView.getDisplay(),
@@ -262,13 +278,13 @@
public void update() {
if (mWindow != null) {
obtainSurfaces();
- if (!mDirtyZoom) {
+ if (!mDirtyState) {
// Update the content shown in the magnifier.
performPixelCopy(mPrevStartCoordsInSurface.x, mPrevStartCoordsInSurface.y,
false /* update window position */);
} else {
- // If the zoom has changed, we cannot use the same top left coordinates
- // as before, so just #show again to have them recomputed.
+ // If for example the zoom has changed, we cannot use the same top left
+ // coordinates as before, so just #show again to have them recomputed.
show(mPrevShowSourceCoords.x, mPrevShowSourceCoords.y,
mPrevShowWindowCoords.x, mPrevShowWindowCoords.y);
}
@@ -315,6 +331,7 @@
/**
* Sets the zoom to be applied to the chosen content before being copied to the magnifier popup.
+ * The change will become effective at the next #show or #update call.
* @param zoom the zoom to be set
*/
public void setZoom(@FloatRange(from = 0f) float zoom) {
@@ -322,7 +339,7 @@
mZoom = zoom;
mSourceWidth = Math.round(mWindowWidth / mZoom);
mSourceHeight = Math.round(mWindowHeight / mZoom);
- mDirtyZoom = true;
+ mDirtyState = true;
}
/**
@@ -480,7 +497,14 @@
* magnifier. These are relative to the surface the content is copied from.
*/
private void obtainContentCoordinates(final float xPosInView, final float yPosInView) {
+ final int prevViewXInSurface = mViewCoordinatesInSurface[0];
+ final int prevViewYInSurface = mViewCoordinatesInSurface[1];
mView.getLocationInSurface(mViewCoordinatesInSurface);
+ if (mViewCoordinatesInSurface[0] != prevViewXInSurface
+ || mViewCoordinatesInSurface[1] != prevViewYInSurface) {
+ mDirtyState = true;
+ }
+
final int zoomCenterX;
final int zoomCenterY;
if (mView instanceof SurfaceView) {
@@ -492,8 +516,25 @@
zoomCenterY = Math.round(yPosInView + mViewCoordinatesInSurface[1]);
}
- // Clamp the x location to avoid magnifying content which does not belong
- // to the magnified view. This will not take into account overlapping views.
+ final Rect[] bounds = new Rect[3]; // [MAX_IN_SURFACE, MAX_IN_VIEW, MAX_VISIBLE]
+ // Obtain the surface bounds rectangle.
+ final Rect surfaceBounds = new Rect(0, 0,
+ mContentCopySurface.mWidth, mContentCopySurface.mHeight);
+ bounds[0] = surfaceBounds;
+ // Obtain the view bounds rectangle.
+ final Rect viewBounds;
+ if (mView instanceof SurfaceView) {
+ viewBounds = new Rect(0, 0, mContentCopySurface.mWidth, mContentCopySurface.mHeight);
+ } else {
+ viewBounds = new Rect(
+ mViewCoordinatesInSurface[0],
+ mViewCoordinatesInSurface[1],
+ mViewCoordinatesInSurface[0] + mView.getWidth(),
+ mViewCoordinatesInSurface[1] + mView.getHeight()
+ );
+ }
+ bounds[1] = viewBounds;
+ // Obtain the visible view region rectangle.
final Rect viewVisibleRegion = new Rect();
mView.getGlobalVisibleRect(viewVisibleRegion);
if (mView.getViewRootImpl() != null) {
@@ -505,9 +546,40 @@
// If we copy content from a SurfaceView, clamp coordinates relative to it.
viewVisibleRegion.offset(-mViewCoordinatesInSurface[0], -mViewCoordinatesInSurface[1]);
}
- mClampedCenterZoomCoords.x = Math.max(viewVisibleRegion.left + mSourceWidth / 2, Math.min(
- zoomCenterX, viewVisibleRegion.right - mSourceWidth / 2));
- mClampedCenterZoomCoords.y = zoomCenterY;
+ bounds[2] = viewVisibleRegion;
+
+ // Aggregate the above to obtain the bounds where the content copy will be restricted.
+ int resolvedLeft = Integer.MIN_VALUE;
+ for (int i = mLeftContentBound; i >= 0; --i) {
+ resolvedLeft = Math.max(resolvedLeft, bounds[i].left);
+ }
+ int resolvedTop = Integer.MIN_VALUE;
+ for (int i = mTopContentBound; i >= 0; --i) {
+ resolvedTop = Math.max(resolvedTop, bounds[i].top);
+ }
+ int resolvedRight = Integer.MAX_VALUE;
+ for (int i = mRightContentBound; i >= 0; --i) {
+ resolvedRight = Math.min(resolvedRight, bounds[i].right);
+ }
+ int resolvedBottom = Integer.MAX_VALUE;
+ for (int i = mBottomContentBound; i >= 0; --i) {
+ resolvedBottom = Math.min(resolvedBottom, bounds[i].bottom);
+ }
+ // Adjust <left-right> and <top-bottom> pairs of bounds to make sense.
+ resolvedLeft = Math.min(resolvedLeft, mContentCopySurface.mWidth - mSourceWidth);
+ resolvedTop = Math.min(resolvedTop, mContentCopySurface.mHeight - mSourceHeight);
+ if (resolvedLeft < 0 || resolvedTop < 0) {
+ Log.e(TAG, "Magnifier's content is copied from a surface smaller than"
+ + "the content requested size. This will probably lead to distorted content.");
+ }
+ resolvedRight = Math.max(resolvedRight, resolvedLeft + mSourceWidth);
+ resolvedBottom = Math.max(resolvedBottom, resolvedTop + mSourceHeight);
+
+ // Finally compute the coordinates of the source center.
+ mClampedCenterZoomCoords.x = Math.max(resolvedLeft + mSourceWidth / 2, Math.min(
+ zoomCenterX, resolvedRight - mSourceWidth / 2));
+ mClampedCenterZoomCoords.y = Math.max(resolvedTop + mSourceHeight / 2, Math.min(
+ zoomCenterY, resolvedBottom - mSourceHeight / 2));
}
/**
@@ -539,20 +611,16 @@
if (mContentCopySurface.mSurface == null || !mContentCopySurface.mSurface.isValid()) {
return;
}
- // Clamp copy coordinates inside the surface to avoid displaying distorted content.
- final int clampedStartXInSurface = Math.max(0,
- Math.min(startXInSurface, mContentCopySurface.mWidth - mSourceWidth));
- final int clampedStartYInSurface = Math.max(0,
- Math.min(startYInSurface, mContentCopySurface.mHeight - mSourceHeight));
+
// Clamp window coordinates inside the parent surface, to avoid displaying
// the magnifier out of screen or overlapping with system insets.
final Point windowCoords = getCurrentClampedWindowCoordinates();
// Perform the pixel copy.
- mPixelCopyRequestRect.set(clampedStartXInSurface,
- clampedStartYInSurface,
- clampedStartXInSurface + mSourceWidth,
- clampedStartYInSurface + mSourceHeight);
+ mPixelCopyRequestRect.set(startXInSurface,
+ startYInSurface,
+ startXInSurface + mSourceWidth,
+ startYInSurface + mSourceHeight);
final InternalPopupWindow currentWindowInstance = mWindow;
final Bitmap bitmap =
Bitmap.createBitmap(mSourceWidth, mSourceHeight, Bitmap.Config.ARGB_8888);
@@ -573,7 +641,7 @@
sPixelCopyHandlerThread.getThreadHandler());
mPrevStartCoordsInSurface.x = startXInSurface;
mPrevStartCoordsInSurface.y = startYInSurface;
- mDirtyZoom = false;
+ mDirtyState = false;
}
/**
@@ -912,6 +980,10 @@
private int mHorizontalDefaultSourceToMagnifierOffset;
private int mVerticalDefaultSourceToMagnifierOffset;
private boolean mForcePositionWithinWindowSystemInsetsBounds;
+ private @SourceBound int mLeftContentBound;
+ private @SourceBound int mTopContentBound;
+ private @SourceBound int mRightContentBound;
+ private @SourceBound int mBottomContentBound;
/**
* Construct a new builder for {@link Magnifier} objects.
@@ -937,6 +1009,10 @@
a.getDimensionPixelSize(R.styleable.Magnifier_magnifierVerticalOffset, 0);
a.recycle();
mForcePositionWithinWindowSystemInsetsBounds = true;
+ mLeftContentBound = SOURCE_BOUND_MAX_VISIBLE;
+ mTopContentBound = SOURCE_BOUND_MAX_IN_SURFACE;
+ mRightContentBound = SOURCE_BOUND_MAX_VISIBLE;
+ mBottomContentBound = SOURCE_BOUND_MAX_IN_SURFACE;
}
/**
@@ -1044,6 +1120,52 @@
}
/**
+ * Defines the bounds of the rectangle where the magnifier will be able to copy its content
+ * from. The content will always be copied from the {@link Surface} of the main application
+ * window unless the magnified view is a {@link SurfaceView}, in which case its backing
+ * surface will be used. Each bound can have a different behavior, with the options being:
+ * <ul>
+ * <li>{@link #SOURCE_BOUND_MAX_VISIBLE}, which extends the bound as much as possible
+ * while remaining in the visible region of the magnified view, as given by
+ * {@link android.view.View#getGlobalVisibleRect(Rect)}. For example, this will take into
+ * account the case when the view is contained in a scrollable container, and the
+ * magnifier will refuse to copy content outside of the visible view region</li>
+ * <li>{@link #SOURCE_BOUND_MAX_IN_VIEW}, which extends the bound as much as possible
+ * while remaining in the bounds of the view. Note that, although this option is
+ * used, the magnifier will always only display content visible on the screen: if the
+ * view lays outside the screen or is covered by a different view either partially or
+ * totally, the magnifier will not show any view region not visible on the screen.</li>
+ * <li>{@link #SOURCE_BOUND_MAX_IN_SURFACE}, which extends the bound as much
+ * as possible while remaining inside the surface the content is copied from.</li>
+ * </ul>
+ * Note that if either of the first three options is used, the bound will be compared to
+ * the bound of the surface (i.e. as if {@link #SOURCE_BOUND_MAX_IN_SURFACE} was used),
+ * and the more restrictive one will be chosen. In other words, no attempt to copy content
+ * from outside the surface will be permitted. If two opposite bounds are not well-behaved
+ * (i.e. left + sourceWidth > right or top + sourceHeight > bottom), the left and top
+ * bounds will have priority and the others will be extended accordingly. If the pairs
+ * obtained this way still remain out of bounds, the smallest possible offset will be added
+ * to the pairs to bring them inside the surface bounds. If this is impossible
+ * (i.e. the surface is too small for the size of the content we try to copy on either
+ * dimension), an error will be logged and the magnifier content will look distorted.
+ * The default values assumed by the builder for the source bounds are
+ * left: {@link #SOURCE_BOUND_MAX_VISIBLE}, top: {@link #SOURCE_BOUND_MAX_IN_SURFACE},
+ * right: {@link #SOURCE_BOUND_MAX_VISIBLE}, bottom: {@link #SOURCE_BOUND_MAX_IN_SURFACE}.
+ * @param left the left bound for content copy
+ * @param top the top bound for content copy
+ * @param right the right bound for content copy
+ * @param bottom the bottom bound for content copy
+ */
+ public Builder setSourceBounds(@SourceBound int left, @SourceBound int top,
+ @SourceBound int right, @SourceBound int bottom) {
+ mLeftContentBound = left;
+ mTopContentBound = top;
+ mRightContentBound = right;
+ mBottomContentBound = bottom;
+ return this;
+ }
+
+ /**
* Builds a {@link Magnifier} instance based on the configuration of this {@link Builder}.
*/
public @NonNull Magnifier build() {
@@ -1051,6 +1173,38 @@
}
}
+ /**
+ * A source bound that will extend as much as possible, while remaining within the surface
+ * the content is copied from.
+ */
+
+ public static final int SOURCE_BOUND_MAX_IN_SURFACE = 0;
+ /**
+ * A source bound that will extend as much as possible, while remaining within the
+ * magnified view.
+ */
+
+ public static final int SOURCE_BOUND_MAX_IN_VIEW = 1;
+
+ /**
+ * A source bound that will extend as much as possible, while remaining within the
+ * visible region of the magnified view, as determined by
+ * {@link View#getGlobalVisibleRect(Rect)}.
+ */
+ public static final int SOURCE_BOUND_MAX_VISIBLE = 2;
+
+
+ /**
+ * Used to describe the {@link Surface} rectangle where the magnifier's content is allowed
+ * to be copied from. For more details, see method
+ * {@link Magnifier.Builder#setSourceBounds(int, int, int, int)}
+ *
+ * @hide
+ */
+ @IntDef({SOURCE_BOUND_MAX_IN_SURFACE, SOURCE_BOUND_MAX_IN_VIEW, SOURCE_BOUND_MAX_VISIBLE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SourceBound {}
+
// The rest of the file consists of test APIs.
/**
diff --git a/core/proto/android/server/location/enums.proto b/core/proto/android/server/location/enums.proto
new file mode 100644
index 0000000..b6dc589
--- /dev/null
+++ b/core/proto/android/server/location/enums.proto
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package android.server.location;
+
+option java_outer_classname = "ServerLocationProtoEnums";
+option java_multiple_files = true;
+
+// GPS Signal Quality levels,
+// primarily used by location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+enum GpsSignalQualityEnum {
+ GPS_SIGNAL_QUALITY_UNKNOWN = -1;
+ GPS_SIGNAL_QUALITY_POOR = 0;
+ GPS_SIGNAL_QUALITY_GOOD = 1;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 62896be..ecf8137 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -642,7 +642,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.READ_CONTACTS"
- android:permissionGroup="android.permission-group.CONTACTS"
android:label="@string/permlab_readContacts"
android:description="@string/permdesc_readContacts"
android:protectionLevel="dangerous" />
@@ -651,7 +650,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.WRITE_CONTACTS"
- android:permissionGroup="android.permission-group.CONTACTS"
android:label="@string/permlab_writeContacts"
android:description="@string/permdesc_writeContacts"
android:protectionLevel="dangerous" />
@@ -673,7 +671,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.READ_CALENDAR"
- android:permissionGroup="android.permission-group.CALENDAR"
android:label="@string/permlab_readCalendar"
android:description="@string/permdesc_readCalendar"
android:protectionLevel="dangerous" />
@@ -682,7 +679,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.WRITE_CALENDAR"
- android:permissionGroup="android.permission-group.CALENDAR"
android:label="@string/permlab_writeCalendar"
android:description="@string/permdesc_writeCalendar"
android:protectionLevel="dangerous" />
@@ -704,7 +700,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.SEND_SMS"
- android:permissionGroup="android.permission-group.SMS"
android:label="@string/permlab_sendSms"
android:description="@string/permdesc_sendSms"
android:permissionFlags="costsMoney"
@@ -714,7 +709,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.RECEIVE_SMS"
- android:permissionGroup="android.permission-group.SMS"
android:label="@string/permlab_receiveSms"
android:description="@string/permdesc_receiveSms"
android:protectionLevel="dangerous"/>
@@ -723,7 +717,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.READ_SMS"
- android:permissionGroup="android.permission-group.SMS"
android:label="@string/permlab_readSms"
android:description="@string/permdesc_readSms"
android:protectionLevel="dangerous" />
@@ -732,7 +725,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.RECEIVE_WAP_PUSH"
- android:permissionGroup="android.permission-group.SMS"
android:label="@string/permlab_receiveWapPush"
android:description="@string/permdesc_receiveWapPush"
android:protectionLevel="dangerous" />
@@ -741,7 +733,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.RECEIVE_MMS"
- android:permissionGroup="android.permission-group.SMS"
android:label="@string/permlab_receiveMms"
android:description="@string/permdesc_receiveMms"
android:protectionLevel="dangerous" />
@@ -759,7 +750,6 @@
<p>Protection level: dangerous
@hide Pending API council approval -->
<permission android:name="android.permission.READ_CELL_BROADCASTS"
- android:permissionGroup="android.permission-group.SMS"
android:label="@string/permlab_readCellBroadcasts"
android:description="@string/permdesc_readCellBroadcasts"
android:protectionLevel="dangerous" />
@@ -801,7 +791,6 @@
@deprecated replaced by new strongly-typed permission groups in Q.
-->
<permission android:name="android.permission.READ_EXTERNAL_STORAGE"
- android:permissionGroup="android.permission-group.STORAGE"
android:label="@string/permlab_sdcardRead"
android:description="@string/permdesc_sdcardRead"
android:protectionLevel="normal" />
@@ -822,7 +811,6 @@
@deprecated replaced by new strongly-typed permission groups in Q.
-->
<permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
- android:permissionGroup="android.permission-group.STORAGE"
android:label="@string/permlab_sdcardWrite"
android:description="@string/permdesc_sdcardWrite"
android:protectionLevel="normal" />
@@ -838,14 +826,12 @@
<!-- Allows an application to read the user's shared audio collection. -->
<permission android:name="android.permission.READ_MEDIA_AUDIO"
- android:permissionGroup="android.permission-group.MEDIA_AURAL"
android:label="@string/permlab_audioRead"
android:description="@string/permdesc_audioRead"
android:protectionLevel="dangerous" />
<!-- Allows an application to modify the user's shared audio collection. -->
<permission android:name="android.permission.WRITE_MEDIA_AUDIO"
- android:permissionGroup="android.permission-group.MEDIA_AURAL"
android:label="@string/permlab_audioWrite"
android:description="@string/permdesc_audioWrite"
android:protectionLevel="dangerous" />
@@ -861,28 +847,24 @@
<!-- Allows an application to read the user's shared images collection. -->
<permission android:name="android.permission.READ_MEDIA_IMAGES"
- android:permissionGroup="android.permission-group.MEDIA_VISUAL"
android:label="@string/permlab_imagesRead"
android:description="@string/permdesc_imagesRead"
android:protectionLevel="dangerous" />
<!-- Allows an application to modify the user's shared images collection. -->
<permission android:name="android.permission.WRITE_MEDIA_IMAGES"
- android:permissionGroup="android.permission-group.MEDIA_VISUAL"
android:label="@string/permlab_imagesWrite"
android:description="@string/permdesc_imagesWrite"
android:protectionLevel="dangerous" />
<!-- Allows an application to read the user's shared video collection. -->
<permission android:name="android.permission.READ_MEDIA_VIDEO"
- android:permissionGroup="android.permission-group.MEDIA_VISUAL"
android:label="@string/permlab_videoRead"
android:description="@string/permdesc_videoRead"
android:protectionLevel="dangerous" />
<!-- Allows an application to modify the user's shared video collection. -->
<permission android:name="android.permission.WRITE_MEDIA_VIDEO"
- android:permissionGroup="android.permission-group.MEDIA_VISUAL"
android:label="@string/permlab_videoWrite"
android:description="@string/permdesc_videoWrite"
android:protectionLevel="dangerous" />
@@ -890,7 +872,6 @@
<!-- Allows an application to access any geographic locations persisted in the
user's shared collection. -->
<permission android:name="android.permission.ACCESS_MEDIA_LOCATION"
- android:permissionGroup="android.permission-group.MEDIA_VISUAL"
android:label="@string/permlab_mediaLocation"
android:description="@string/permdesc_mediaLocation"
android:protectionLevel="dangerous" />
@@ -921,7 +902,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACCESS_FINE_LOCATION"
- android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/permlab_accessFineLocation"
android:description="@string/permdesc_accessFineLocation"
android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION"
@@ -932,7 +912,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACCESS_COARSE_LOCATION"
- android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/permlab_accessCoarseLocation"
android:description="@string/permdesc_accessCoarseLocation"
android:backgroundPermission="android.permission.ACCESS_BACKGROUND_LOCATION"
@@ -945,7 +924,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"
- android:permissionGroup="android.permission-group.LOCATION"
android:label="@string/permlab_accessBackgroundLocation"
android:description="@string/permdesc_accessBackgroundLocation"
android:protectionLevel="dangerous|instant" />
@@ -986,7 +964,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.READ_CALL_LOG"
- android:permissionGroup="android.permission-group.CALL_LOG"
android:label="@string/permlab_readCallLog"
android:description="@string/permdesc_readCallLog"
android:protectionLevel="dangerous" />
@@ -1005,7 +982,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.WRITE_CALL_LOG"
- android:permissionGroup="android.permission-group.CALL_LOG"
android:label="@string/permlab_writeCallLog"
android:description="@string/permdesc_writeCallLog"
android:protectionLevel="dangerous" />
@@ -1016,7 +992,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.PROCESS_OUTGOING_CALLS"
- android:permissionGroup="android.permission-group.CALL_LOG"
android:label="@string/permlab_processOutgoingCalls"
android:description="@string/permdesc_processOutgoingCalls"
android:protectionLevel="dangerous" />
@@ -1048,7 +1023,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.READ_PHONE_STATE"
- android:permissionGroup="android.permission-group.PHONE"
android:label="@string/permlab_readPhoneState"
android:description="@string/permdesc_readPhoneState"
android:protectionLevel="dangerous" />
@@ -1057,7 +1031,6 @@
granted by {@link #READ_PHONE_STATE} but is exposed to instant applications.
<p>Protection level: dangerous-->
<permission android:name="android.permission.READ_PHONE_NUMBERS"
- android:permissionGroup="android.permission-group.PHONE"
android:label="@string/permlab_readPhoneNumbers"
android:description="@string/permdesc_readPhoneNumbers"
android:protectionLevel="dangerous|instant" />
@@ -1067,7 +1040,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.CALL_PHONE"
- android:permissionGroup="android.permission-group.PHONE"
android:permissionFlags="costsMoney"
android:label="@string/permlab_callPhone"
android:description="@string/permdesc_callPhone"
@@ -1077,7 +1049,6 @@
<p>Protection level: dangerous
-->
<permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL"
- android:permissionGroup="android.permission-group.PHONE"
android:label="@string/permlab_addVoicemail"
android:description="@string/permdesc_addVoicemail"
android:protectionLevel="dangerous" />
@@ -1086,7 +1057,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.USE_SIP"
- android:permissionGroup="android.permission-group.PHONE"
android:description="@string/permdesc_use_sip"
android:label="@string/permlab_use_sip"
android:protectionLevel="dangerous"/>
@@ -1095,7 +1065,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ANSWER_PHONE_CALLS"
- android:permissionGroup="android.permission-group.PHONE"
android:label="@string/permlab_answerPhoneCalls"
android:description="@string/permdesc_answerPhoneCalls"
android:protectionLevel="dangerous|runtime" />
@@ -1123,7 +1092,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACCEPT_HANDOVER"
- android:permissionGroup="android.permission-group.PHONE"
android.label="@string/permlab_acceptHandover"
android:description="@string/permdesc_acceptHandovers"
android:protectionLevel="dangerous" />
@@ -1147,7 +1115,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.RECORD_AUDIO"
- android:permissionGroup="android.permission-group.MICROPHONE"
android:label="@string/permlab_recordAudio"
android:description="@string/permdesc_recordAudio"
android:protectionLevel="dangerous|instant"/>
@@ -1169,7 +1136,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.ACTIVITY_RECOGNITION"
- android:permissionGroup="android.permission-group.ACTIVITY_RECOGNITION"
android:label="@string/permlab_activityRecognition"
android:description="@string/permdesc_activityRecognition"
android:protectionLevel="dangerous|instant" />
@@ -1218,7 +1184,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.CAMERA"
- android:permissionGroup="android.permission-group.CAMERA"
android:label="@string/permlab_camera"
android:description="@string/permdesc_camera"
android:protectionLevel="dangerous|instant" />
@@ -1242,7 +1207,6 @@
measure what is happening inside his/her body, such as heart rate.
<p>Protection level: dangerous -->
<permission android:name="android.permission.BODY_SENSORS"
- android:permissionGroup="android.permission-group.SENSORS"
android:label="@string/permlab_bodySensors"
android:description="@string/permdesc_bodySensors"
android:protectionLevel="dangerous" />
@@ -1721,7 +1685,6 @@
<p>Protection level: dangerous
-->
<permission android:name="android.permission.GET_ACCOUNTS"
- android:permissionGroup="android.permission-group.CONTACTS"
android:protectionLevel="dangerous"
android:description="@string/permdesc_getAccounts"
android:label="@string/permlab_getAccounts" />
diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml
index 4ee9731..433ae39 100644
--- a/core/res/res/layout/notification_template_header.xml
+++ b/core/res/res/layout/notification_template_header.xml
@@ -116,17 +116,6 @@
android:contentDescription="@string/expand_button_content_description_collapsed"
/>
<ImageView
- android:id="@+id/profile_badge"
- android:layout_width="@dimen/notification_badge_size"
- android:layout_height="@dimen/notification_badge_size"
- android:layout_gravity="center"
- android:layout_marginStart="4dp"
- android:paddingTop="1dp"
- android:scaleType="fitCenter"
- android:visibility="gone"
- android:contentDescription="@string/notification_work_profile_content_description"
- />
- <ImageView
android:id="@+id/alerted_icon"
android:layout_width="@dimen/notification_alerted_size"
android:layout_height="@dimen/notification_alerted_size"
@@ -138,6 +127,17 @@
android:contentDescription="@string/notification_alerted_content_description"
android:src="@drawable/ic_notifications_alerted"
android:tint="@color/notification_secondary_text_color_light"
+ />
+ <ImageView
+ android:id="@+id/profile_badge"
+ android:layout_width="@dimen/notification_badge_size"
+ android:layout_height="@dimen/notification_badge_size"
+ android:layout_gravity="center"
+ android:layout_marginStart="4dp"
+ android:paddingTop="1dp"
+ android:scaleType="fitCenter"
+ android:visibility="gone"
+ android:contentDescription="@string/notification_work_profile_content_description"
/>
<LinearLayout
android:id="@+id/app_ops"
diff --git a/core/res/res/values-night/themes_device_defaults.xml b/core/res/res/values-night/themes_device_defaults.xml
index 931674a..84c6446 100644
--- a/core/res/res/values-night/themes_device_defaults.xml
+++ b/core/res/res/values-night/themes_device_defaults.xml
@@ -52,6 +52,9 @@
<!-- DeviceDefault theme for a window that should look like the Settings app. -->
<style name="Theme.DeviceDefault.Settings" parent="Theme.DeviceDefault">
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
+ <item name="colorBackground">@color/primary_dark_device_default_settings</item>
+
+ <item name="listDivider">@color/list_divider_color_dark</item>
</style>
<!-- Theme for the dialog shown when an app crashes or ANRs. -->
diff --git a/core/res/res/values/colors_car.xml b/core/res/res/values/colors_car.xml
index 32671ac8..ea7c009 100644
--- a/core/res/res/values/colors_car.xml
+++ b/core/res/res/values/colors_car.xml
@@ -284,4 +284,8 @@
<color name="car_red_500a">#ffd50000</color>
<color name="car_red_a700">#ffd50000</color>
+
+ <color name="car_keyboard_divider_line">#38ffffff</color>
+ <color name="car_keyboard_text_primary_color">@color/car_grey_50</color>
+ <color name="car_keyboard_text_secondary_color">#8af8f9fa</color>
</resources>
diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml
index 0fe80a1..ded916f 100644
--- a/core/res/res/values/colors_device_defaults.xml
+++ b/core/res/res/values/colors_device_defaults.xml
@@ -42,4 +42,7 @@
<!-- Error color -->
<color name="error_color_device_default_dark">@color/error_color_material_dark</color>
<color name="error_color_device_default_light">@color/error_color_material_light</color>
+
+ <color name="list_divider_color_light">#64000000</color>
+ <color name="list_divider_color_dark">#85ffffff</color>
</resources>
diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml
index c1ca33e..5014a29 100644
--- a/core/res/res/values/dimens_car.xml
+++ b/core/res/res/values/dimens_car.xml
@@ -34,7 +34,6 @@
<!-- The diff between keyline 1 and keyline 3. -->
<dimen name="car_keyline_1_keyline_3_diff">88dp</dimen>
<dimen name="car_dialog_action_bar_height">@dimen/car_card_action_bar_height</dimen>
- <dimen name="car_primary_icon_size">44dp</dimen>
<!-- Text size for car -->
<dimen name="car_title_size">32sp</dimen>
@@ -56,16 +55,19 @@
<!-- Common icon size for car app -->
<dimen name="car_icon_size">56dp</dimen>
+ <dimen name="car_primary_icon_size">44dp</dimen>
+ <dimen name="car_secondary_icon_size">36dp</dimen>
- <dimen name="car_card_header_height">96dp</dimen>
- <dimen name="car_card_action_bar_height">96dp</dimen>
+ <dimen name="car_card_header_height">76dp</dimen>
+ <dimen name="car_card_action_bar_height">76dp</dimen>
<!-- Paddings -->
- <dimen name="car_padding_1">4dp</dimen>
- <dimen name="car_padding_2">10dp</dimen>
- <dimen name="car_padding_3">16dp</dimen>
- <dimen name="car_padding_4">28dp</dimen>
- <dimen name="car_padding_5">32dp</dimen>
+ <dimen name="car_padding_0">4dp</dimen>
+ <dimen name="car_padding_1">8dp</dimen>
+ <dimen name="car_padding_2">16dp</dimen>
+ <dimen name="car_padding_3">28dp</dimen>
+ <dimen name="car_padding_4">32dp</dimen>
+ <dimen name="car_padding_5">64dp</dimen>
<!-- Radius -->
<dimen name="car_radius_1">4dp</dimen>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 3385527..fa009bd 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1475,6 +1475,8 @@
<!-- Toolbar attributes -->
<item name="toolbarStyle">@style/Widget.DeviceDefault.Toolbar</item>
+
+ <item name="listDivider">@color/list_divider_color_light</item>
</style>
<!-- @hide DeviceDefault theme for a window that should use Settings theme colors
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 835b735..fec800d 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -23,6 +23,7 @@
import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
import android.os.IBinder;
@@ -1254,7 +1255,7 @@
return new UserNotAuthenticatedException();
}
- long fingerprintOnlySid = getFingerprintOnlySid();
+ final long fingerprintOnlySid = getFingerprintOnlySid();
if ((fingerprintOnlySid != 0)
&& (keySids.contains(KeymasterArguments.toUint64(fingerprintOnlySid)))) {
// One of the key's SIDs is the current fingerprint SID -- user can be
@@ -1262,6 +1263,14 @@
return new UserNotAuthenticatedException();
}
+ final long faceOnlySid = getFaceOnlySid();
+ if ((faceOnlySid != 0)
+ && (keySids.contains(KeymasterArguments.toUint64(faceOnlySid)))) {
+ // One of the key's SIDs is the current face SID -- user can be
+ // authenticated against that SID.
+ return new UserNotAuthenticatedException();
+ }
+
// None of the key's SIDs can ever be authenticated
return new KeyPermanentlyInvalidatedException();
}
@@ -1272,6 +1281,21 @@
}
}
+ private long getFaceOnlySid() {
+ final PackageManager packageManager = mContext.getPackageManager();
+ if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FACE)) {
+ return 0;
+ }
+ FaceManager faceManager = mContext.getSystemService(FaceManager.class);
+ if (faceManager == null) {
+ return 0;
+ }
+
+ // TODO: Restore USE_BIOMETRIC or USE_BIOMETRIC_INTERNAL permission check in
+ // FaceManager.getAuthenticatorId once the ID is no longer needed here.
+ return faceManager.getAuthenticatorId();
+ }
+
private long getFingerprintOnlySid() {
final PackageManager packageManager = mContext.getPackageManager();
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 7bbc099..a2d2355 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -182,8 +182,8 @@
KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED);
boolean invalidatedByBiometricEnrollment = false;
- if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_FINGERPRINT
- || keymasterHwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_FINGERPRINT) {
+ if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC
+ || keymasterHwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_BIOMETRIC) {
// Fingerprint-only key; will be invalidated if the root SID isn't in the SID list.
invalidatedByBiometricEnrollment = keymasterSecureUserIds != null
&& !keymasterSecureUserIds.isEmpty()
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index f829bb7..52896b5 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -16,7 +16,7 @@
package android.security.keystore;
-import android.app.ActivityManager;
+import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.security.GateKeeper;
import android.security.KeyStore;
@@ -24,6 +24,8 @@
import android.security.keymaster.KeymasterDefs;
import java.security.ProviderException;
+import java.util.ArrayList;
+import java.util.List;
/**
* @hide
@@ -121,35 +123,44 @@
if (spec.getUserAuthenticationValidityDurationSeconds() == -1) {
// Every use of this key needs to be authorized by the user. This currently means
- // fingerprint-only auth.
+ // fingerprint or face auth.
FingerprintManager fingerprintManager =
KeyStore.getApplicationContext().getSystemService(FingerprintManager.class);
+ FaceManager faceManager =
+ KeyStore.getApplicationContext().getSystemService(FaceManager.class);
// TODO: Restore USE_FINGERPRINT permission check in
// FingerprintManager.getAuthenticatorId once the ID is no longer needed here.
- long fingerprintOnlySid =
+ final long fingerprintOnlySid =
(fingerprintManager != null) ? fingerprintManager.getAuthenticatorId() : 0;
- if (fingerprintOnlySid == 0) {
+ final long faceOnlySid =
+ (faceManager != null) ? faceManager.getAuthenticatorId() : 0;
+
+ if (fingerprintOnlySid == 0 && faceOnlySid == 0) {
throw new IllegalStateException(
- "At least one fingerprint must be enrolled to create keys requiring user"
+ "At least one biometric must be enrolled to create keys requiring user"
+ " authentication for every use");
}
- long sid;
+ List<Long> sids = new ArrayList<>();
if (spec.getBoundToSpecificSecureUserId() != GateKeeper.INVALID_SECURE_USER_ID) {
- sid = spec.getBoundToSpecificSecureUserId();
+ sids.add(spec.getBoundToSpecificSecureUserId());
} else if (spec.isInvalidatedByBiometricEnrollment()) {
- // The fingerprint-only SID will change on fingerprint enrollment or removal of all,
- // enrolled fingerprints, invalidating the key.
- sid = fingerprintOnlySid;
+ // The biometric-only SIDs will change on biometric enrollment or removal of all
+ // enrolled templates, invalidating the key.
+ sids.add(fingerprintOnlySid);
+ sids.add(faceOnlySid);
} else {
// The root SID will *not* change on fingerprint enrollment, or removal of all
// enrolled fingerprints, allowing the key to remain valid.
- sid = getRootSid();
+ sids.add(getRootSid());
}
- args.addUnsignedLong(
- KeymasterDefs.KM_TAG_USER_SECURE_ID, KeymasterArguments.toUint64(sid));
- args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_FINGERPRINT);
+ for (int i = 0; i < sids.size(); i++) {
+ args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
+ KeymasterArguments.toUint64(sids.get(i)));
+ }
+ args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_BIOMETRIC);
+
if (spec.isUserAuthenticationValidWhileOnBody()) {
throw new ProviderException("Key validity extension while device is on-body is not "
+ "supported for keys requiring fingerprint authentication");
@@ -166,7 +177,7 @@
args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
KeymasterArguments.toUint64(sid));
args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
- KeymasterDefs.HW_AUTH_PASSWORD | KeymasterDefs.HW_AUTH_FINGERPRINT);
+ KeymasterDefs.HW_AUTH_PASSWORD | KeymasterDefs.HW_AUTH_BIOMETRIC);
args.addUnsignedInt(KeymasterDefs.KM_TAG_AUTH_TIMEOUT,
spec.getUserAuthenticationValidityDurationSeconds());
if (spec.isUserAuthenticationValidWhileOnBody()) {
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index d401b38..6ae5999 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -164,7 +164,11 @@
if (surface) {
mRenderThread.requireGlContext();
- mEglSurface = mEglManager.createSurface(surface, colorMode);
+ auto newSurface = mEglManager.createSurface(surface, colorMode);
+ if (!newSurface) {
+ return false;
+ }
+ mEglSurface = newSurface.unwrap();
}
if (colorMode == ColorMode::SRGB) {
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index d4ffddd..65ced6a 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -261,7 +261,7 @@
}
}
-EGLSurface EglManager::createSurface(EGLNativeWindowType window, ColorMode colorMode) {
+Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window, ColorMode colorMode) {
LOG_ALWAYS_FATAL_IF(!hasEglContext(), "Not initialized");
bool wideColorGamut = colorMode == ColorMode::WideColorGamut && EglExtensions.glColorSpace &&
@@ -311,9 +311,9 @@
EGLSurface surface = eglCreateWindowSurface(
mEglDisplay, wideColorGamut ? mEglConfigWideGamut : mEglConfig, window, attribs);
- LOG_ALWAYS_FATAL_IF(surface == EGL_NO_SURFACE,
- "Failed to create EGLSurface for window %p, eglErr = %s", (void*)window,
- eglErrorString());
+ if (surface == EGL_NO_SURFACE) {
+ return Error<EGLint> { eglGetError() };
+ }
if (mSwapBehavior != SwapBehavior::Preserved) {
LOG_ALWAYS_FATAL_IF(eglSurfaceAttrib(mEglDisplay, surface, EGL_SWAP_BEHAVIOR,
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index 55c81d4..2a44f7e 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -25,6 +25,7 @@
#include <ui/GraphicBuffer.h>
#include <utils/StrongPointer.h>
#include "IRenderPipeline.h"
+#include "utils/Result.h"
namespace android {
namespace uirenderer {
@@ -47,7 +48,7 @@
bool hasEglContext();
- EGLSurface createSurface(EGLNativeWindowType window, ColorMode colorMode);
+ Result<EGLSurface, EGLint> createSurface(EGLNativeWindowType window, ColorMode colorMode);
void destroySurface(EGLSurface surface);
void destroy();
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 0795208..a6073eb 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -321,7 +321,7 @@
// Check that the VD is in the dislay list, and the layer update queue contains the correct
// damage rect.
EXPECT_TRUE(rootNode->getDisplayList()->hasVectorDrawables());
- EXPECT_FALSE(info.layerUpdateQueue->entries().empty());
+ ASSERT_FALSE(info.layerUpdateQueue->entries().empty());
EXPECT_EQ(rootNode.get(), info.layerUpdateQueue->entries().at(0).renderNode.get());
EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage);
canvasContext->destroy();
diff --git a/libs/hwui/utils/Result.h b/libs/hwui/utils/Result.h
new file mode 100644
index 0000000..7f33f2e
--- /dev/null
+++ b/libs/hwui/utils/Result.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <variant>
+#include <log/log.h>
+
+namespace android::uirenderer {
+
+template <typename E>
+struct Error {
+ E error;
+};
+
+template <typename R, typename E>
+class Result {
+public:
+ Result(const R& r) : result(std::forward<R>(r)) {}
+ Result(R&& r) : result(std::forward<R>(r)) {}
+ Result(Error<E>&& error) : result(std::forward<Error<E>>(error)) {}
+
+ operator bool() const {
+ return result.index() == 0;
+ }
+
+ R unwrap() const {
+ LOG_ALWAYS_FATAL_IF(result.index() == 1, "unwrap called on error value!");
+ return std::get<R>(result);
+ }
+
+ E error() const {
+ LOG_ALWAYS_FATAL_IF(result.index() == 0, "No error to get from Result");
+ return std::get<Error<E>>(result).error;
+ }
+
+private:
+ std::variant<R, Error<E>> result;
+};
+
+}; // namespace android::uirenderer
diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
index 8a02a82..057a4ae 100644
--- a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
@@ -20,9 +20,12 @@
import android.os.connectivity.GpsBatteryStats;
import android.os.SystemProperties;
+import android.server.location.ServerLocationProtoEnums;
+
import android.text.format.DateUtils;
import android.util.Base64;
import android.util.Log;
+import android.util.StatsLog;
import android.util.TimeUtils;
import java.util.Arrays;
@@ -39,11 +42,17 @@
private static final String TAG = GnssMetrics.class.getSimpleName();
+ /* Constant which indicates GPS signal quality is as yet unknown */
+ public static final int GPS_SIGNAL_QUALITY_UNKNOWN =
+ ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_UNKNOWN; // -1
+
/* Constant which indicates GPS signal quality is poor */
- public static final int GPS_SIGNAL_QUALITY_POOR = 0;
+ public static final int GPS_SIGNAL_QUALITY_POOR =
+ ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_POOR; // 0
/* Constant which indicates GPS signal quality is good */
- public static final int GPS_SIGNAL_QUALITY_GOOD = 1;
+ public static final int GPS_SIGNAL_QUALITY_GOOD =
+ ServerLocationProtoEnums.GPS_SIGNAL_QUALITY_GOOD; // 1
/* Number of GPS signal quality levels */
public static final int NUM_GPS_SIGNAL_QUALITY_LEVELS = GPS_SIGNAL_QUALITY_GOOD + 1;
@@ -329,11 +338,15 @@
/* Last reported Top Four Average CN0 */
private double mLastAverageCn0;
+ /* Last reported signal quality bin (based on Top Four Average CN0) */
+ private int mLastSignalLevel;
+
public GnssPowerMetrics(IBatteryStats stats) {
mBatteryStats = stats;
// Used to initialize the variable to a very small value (unachievable in practice) so that
// the first CNO report will trigger an update to BatteryStats
mLastAverageCn0 = -100.0;
+ mLastSignalLevel = GPS_SIGNAL_QUALITY_UNKNOWN;
}
/**
@@ -384,8 +397,13 @@
if (Math.abs(avgCn0 - mLastAverageCn0) < REPORTING_THRESHOLD_DB_HZ) {
return;
}
+ int signalLevel = getSignalLevel(avgCn0);
+ if (signalLevel != mLastSignalLevel) {
+ StatsLog.write(StatsLog.GPS_SIGNAL_QUALITY_CHANGED, signalLevel);
+ mLastSignalLevel = signalLevel;
+ }
try {
- mBatteryStats.noteGpsSignalQuality(getSignalLevel(avgCn0));
+ mBatteryStats.noteGpsSignalQuality(signalLevel);
mLastAverageCn0 = avgCn0;
} catch (Exception e) {
Log.w(TAG, "Exception", e);
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 4919eeb..c203fa9 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -272,10 +272,12 @@
public static final class CasInfo {
private final int mSystemId;
private final MediaCas.Session mSession;
+ private final byte[] mPrivateData;
- CasInfo(int systemId, @Nullable MediaCas.Session session) {
+ CasInfo(int systemId, @Nullable MediaCas.Session session, @Nullable byte[] privateData) {
mSystemId = systemId;
mSession = session;
+ mPrivateData = privateData;
}
/**
@@ -288,10 +290,30 @@
}
/**
+ * Retrieves the private data in the CA_Descriptor associated with a track.
+ * Some CAS systems may need this to initialize the CAS plugin object. This
+ * private data can only be retrieved before a valid {@link MediaCas} object
+ * is set on the extractor.
+ * <p>
+ * @see MediaExtractor#setMediaCas
+ * <p>
+ * @return a byte array containing the private data. A null return value
+ * indicates that the private data is unavailable. An empty array,
+ * on the other hand, indicates that the private data is empty
+ * (zero in length).
+ */
+ @Nullable
+ public byte[] getPrivateData() {
+ return mPrivateData;
+ }
+
+ /**
* Retrieves the {@link MediaCas.Session} associated with a track. The
* session is needed to initialize a descrambler in order to decode the
- * scrambled track.
+ * scrambled track. The session object can only be retrieved after a valid
+ * {@link MediaCas} object is set on the extractor.
* <p>
+ * @see MediaExtractor#setMediaCas
* @see MediaDescrambler#setMediaCasSession
* <p>
* @return a {@link MediaCas.Session} object associated with a track.
@@ -321,6 +343,13 @@
if (formatMap.containsKey(MediaFormat.KEY_CA_SYSTEM_ID)) {
int systemId = ((Integer)formatMap.get(MediaFormat.KEY_CA_SYSTEM_ID)).intValue();
MediaCas.Session session = null;
+ byte[] privateData = null;
+ if (formatMap.containsKey(MediaFormat.KEY_CA_PRIVATE_DATA)) {
+ ByteBuffer buf = (ByteBuffer) formatMap.get(MediaFormat.KEY_CA_PRIVATE_DATA);
+ buf.rewind();
+ privateData = new byte[buf.remaining()];
+ buf.get(privateData);
+ }
if (mMediaCas != null && formatMap.containsKey(MediaFormat.KEY_CA_SESSION_ID)) {
ByteBuffer buf = (ByteBuffer) formatMap.get(MediaFormat.KEY_CA_SESSION_ID);
buf.rewind();
@@ -328,7 +357,7 @@
buf.get(sessionId);
session = mMediaCas.createFromSessionId(toByteArray(sessionId));
}
- return new CasInfo(systemId, session);
+ return new CasInfo(systemId, session, privateData);
}
return null;
}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index d10cbbc..5dee16e 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -919,7 +919,7 @@
* a media track.
* <p>
* This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
- * access system.
+ * access system, regardless of the presence of a valid {@link MediaCas} object.
* <p>
* The associated value is an integer.
* @hide
@@ -930,13 +930,25 @@
* A key describing the {@link MediaCas.Session} object associated with a media track.
* <p>
* This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
- * access system.
+ * access system, after it receives a valid {@link MediaCas} object.
* <p>
* The associated value is a ByteBuffer.
* @hide
*/
public static final String KEY_CA_SESSION_ID = "ca-session-id";
+
+ /**
+ * A key describing the private data in the CA_descriptor associated with a media track.
+ * <p>
+ * This key is set by {@link MediaExtractor} if the track is scrambled with a conditional
+ * access system, before it receives a valid {@link MediaCas} object.
+ * <p>
+ * The associated value is a ByteBuffer.
+ * @hide
+ */
+ public static final String KEY_CA_PRIVATE_DATA = "ca-private-data";
+
/* package private */ MediaFormat(Map<String, Object> map) {
mMap = map;
}
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 9426148..942eafd 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -37,6 +37,7 @@
ldflags: ["-Wl,--hash-style=both"],
},
},
+ version_script: "libjnigraphics.map.txt",
}
// The headers module is in frameworks/native/Android.bp.
diff --git a/packages/SettingsLib/SettingsSpinner/res/values/styles.xml b/packages/SettingsLib/SettingsSpinner/res/values/styles.xml
index 8447b08..8af20e2 100644
--- a/packages/SettingsLib/SettingsSpinner/res/values/styles.xml
+++ b/packages/SettingsLib/SettingsSpinner/res/values/styles.xml
@@ -17,7 +17,7 @@
<resources>
<style name="SettingsSpinnerTitleBar">
- <item name="android:textAppearance">?android:attr/textAppearance</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceButton</item>
<item name="android:paddingStart">16dp</item>
<item name="android:paddingEnd">36dp</item>
<item name="android:paddingTop">8dp</item>
diff --git a/packages/SettingsLib/res/drawable/list_divider_dark.xml b/packages/SettingsLib/res/drawable/list_divider_dark.xml
deleted file mode 100644
index 5773d9e..0000000
--- a/packages/SettingsLib/res/drawable/list_divider_dark.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- -->
-
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="#64000000" />
- <size
- android:height="1dp"
- android:width="1dp" />
-</shape>
diff --git a/packages/SettingsLib/res/layout/preference_two_target_divider.xml b/packages/SettingsLib/res/layout/preference_two_target_divider.xml
index 60efed4..b81dd83 100644
--- a/packages/SettingsLib/res/layout/preference_two_target_divider.xml
+++ b/packages/SettingsLib/res/layout/preference_two_target_divider.xml
@@ -27,5 +27,5 @@
<View
android:layout_width="1dp"
android:layout_height="match_parent"
- android:background="@drawable/list_divider_dark" />
+ android:background="?android:attr/listDivider" />
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/colors.xml b/packages/SettingsLib/res/values/colors.xml
index 02b7ea6..66bbb3a 100644
--- a/packages/SettingsLib/res/values/colors.xml
+++ b/packages/SettingsLib/res/values/colors.xml
@@ -18,4 +18,5 @@
<color name="disabled_text_color">#66000000</color> <!-- 38% black -->
<color name="usage_graph_dots">@*android:color/tertiary_device_default_settings</color>
+ <color name="list_divider_color">#64000000</color>
</resources>
diff --git a/packages/SystemUI/res/drawable/ic_notification_block.xml b/packages/SystemUI/res/drawable/ic_notification_block.xml
new file mode 100644
index 0000000..572e97b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_notification_block.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12.0,2.0C6.48,2.0 2.0,6.48 2.0,12.0s4.48,10.0 10.0,10.0 10.0,-4.48 10.0,-10.0S17.52,2.0 12.0,2.0zM4.0,12.0c0.0,-4.42 3.58,-8.0 8.0,-8.0 1.85,0.0 3.5,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4.0,13.85 4.0,12.0zm8.0,8.0c-1.85,0.0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20.0,10.15 20.0,12.0c0.0,4.42 -3.58,8.0 -8.0,8.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_notifications_alert.xml b/packages/SystemUI/res/drawable/ic_notifications_alert.xml
new file mode 100644
index 0000000..eb7b8ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_notifications_alert.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M7.58 4.08L6.15 2.65C3.75 4.48 2.17 7.3 2.03 10.5h2c.15-2.65 1.51-4.97 3.55-6.42zm12.39 6.42h2c-.15-3.2-1.73-6.02-4.12-7.85l-1.42 1.43c2.02 1.45 3.39 3.77 3.54 6.42zM18 11c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2v-5zm-6 11c.14 0 .27-.01.4-.04.65-.14 1.18-.58 1.44-1.18.1-.24.15-.5.15-.78h-4c.01 1.1.9 2 2.01 2z"
+ android:fillColor="#FF000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_notifications_silence.xml b/packages/SystemUI/res/drawable/ic_notifications_silence.xml
new file mode 100644
index 0000000..ff136eb
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_notifications_silence.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M0 0h24v24H0z"
+ />
+ <path
+ android:pathData="M20 18.69L7.84 6.14 5.27 3.49 4 4.76l2.8 2.8v.01c-.52.99-.8 2.16-.8 3.42v5l-2 2v1h13.73l2 2L21 19.72l-1-1.03zM12 22c1.11 0 2-.89 2-2h-4c0 1.11.89 2 2 2zm6-7.32V11c0-3.08-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68c-.15.03-.29.08-.42.12-.1.03-.2.07-.3.11h-.01c-.01 0-.01 0-.02.01-.23.09-.46.2-.68.31 0 0-.01 0-.01.01L18 14.68z"
+ android:fillColor="#FF000000"
+ />
+</vector>
diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
index 8247c27..36d0659 100644
--- a/packages/SystemUI/res/drawable/privacy_chip_bg.xml
+++ b/packages/SystemUI/res/drawable/privacy_chip_bg.xml
@@ -16,7 +16,7 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="#bbbbbb" />
+ <solid android:color="#4a4a4a" />
<padding android:padding="@dimen/ongoing_appops_chip_bg_padding" />
<corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
index ddefb6a..cbdd51b 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml
@@ -18,11 +18,14 @@
<com.android.systemui.privacy.OngoingPrivacyChip
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/privacy_chip"
- android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_margin="@dimen/ongoing_appops_chip_margin"
+ android:layout_width="wrap_content"
+ android:layout_marginLeft="@dimen/ongoing_appops_chip_margin"
+ android:layout_marginRight="@dimen/ongoing_appops_chip_margin"
+ android:layout_marginTop="@dimen/ongoing_appops_top_chip_margin"
+ android:layout_marginBottom="@dimen/ongoing_appops_top_chip_margin"
android:gravity="center_vertical|center_horizontal"
- android:layout_gravity="center_vertical|end"
+ android:layout_gravity="center_vertical|start"
android:orientation="horizontal"
android:paddingStart="@dimen/ongoing_appops_chip_side_padding"
android:paddingEnd="@dimen/ongoing_appops_chip_side_padding"
@@ -38,12 +41,17 @@
/>
<TextView
- android:id="@+id/app_name"
+ android:id="@+id/text_container"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:singleLine="true"
android:ellipsize="end"
+ android:lines="1"
android:layout_gravity="center_vertical|end"
android:gravity="center_vertical"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ android:textColor="@color/status_bar_clock_color"
+ android:layout_marginStart="@dimen/ongoing_appops_chip_icon_margin"
+ android:layout_marginEnd="@dimen/ongoing_appops_chip_icon_margin"
/>
</com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml b/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml
index b5e24a0..2f7d486 100644
--- a/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml
+++ b/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml
@@ -29,22 +29,30 @@
android:orientation="vertical"
android:padding="@dimen/ongoing_appops_dialog_content_padding">
- <LinearLayout
- android:id="@+id/icons_container"
+ <TextView
+ android:id="@+id/title"
android:layout_width="match_parent"
- android:layout_height="@dimen/ongoing_appops_dialog_icon_height"
- android:orientation="horizontal"
+ android:layout_height="wrap_content"
android:gravity="center"
- android:importantForAccessibility="no"
+ android:textDirection="locale"
+ android:textAppearance="@style/TextAppearance.AppOpsDialog.Title"
+ android:textColor="@*android:color/text_color_primary"
+ android:paddingStart="@dimen/ongoing_appops_dialog_title_padding"
+ android:paddingEnd="@dimen/ongoing_appops_dialog_title_padding"
+ android:paddingBottom="@dimen/ongoing_appops_dialog_sep"
/>
<LinearLayout
- android:id="@+id/text_container"
+ android:id="@+id/items_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="start"
/>
+
+ <include android:id="@+id/overflow" layout="@layout/ongoing_privacy_dialog_item"
+ android:visibility="gone" />
+
</LinearLayout>
</ScrollView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml b/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml
new file mode 100644
index 0000000..f05f7ba
--- /dev/null
+++ b/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fillViewport="true"
+ android:orientation="horizontal"
+ android:layout_marginTop="@dimen/ongoing_appops_dialog_text_margin"
+ android:focusable="true" >
+
+ <ImageView
+ android:id="@+id/app_icon"
+ android:layout_height="@dimen/ongoing_appops_dialog_icon_height"
+ android:layout_width="@dimen/ongoing_appops_dialog_icon_height"
+ />
+
+ <TextView
+ android:id="@+id/app_name"
+ android:layout_height="@dimen/ongoing_appops_dialog_icon_height"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:gravity="bottom|start"
+ android:textDirection="locale"
+ android:textAppearance="@style/TextAppearance.AppOpsDialog.Item"
+ android:textColor="@*android:color/text_color_primary"
+ android:paddingStart="@dimen/ongoing_appops_dialog_text_padding"
+ android:paddingEnd="@dimen/ongoing_appops_dialog_text_padding"
+
+ />
+
+ <LinearLayout
+ android:id="@+id/icons"
+ android:layout_height="@dimen/ongoing_appops_dialog_icon_height"
+ android:layout_width="wrap_content"
+ android:gravity="end"
+ android:visibility="gone"
+ />
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
index e7f2c51..22b8d2f 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml
@@ -59,7 +59,7 @@
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal"
- android:gravity="center_vertical|end">
+ android:gravity="center_vertical|end" >
<include layout="@layout/ongoing_privacy_chip" />
@@ -67,6 +67,7 @@
android:id="@+id/battery"
android:layout_height="match_parent"
android:layout_width="wrap_content"
- android:gravity="center_vertical|end" />
+ android:gravity="center_vertical|end"
+ android:layout_gravity="center_vertical|end" />
</LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index bb0c6f6..df858f0 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -34,4 +34,5 @@
<bool name="quick_settings_wide">true</bool>
<dimen name="qs_detail_margin_top">0dp</dimen>
<dimen name="qs_paged_tile_layout_padding_bottom">0dp</dimen>
+ <dimen name="ongoing_appops_top_chip_margin">2dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d4e6987..97f5f86 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -940,18 +940,34 @@
that just start below the notch. -->
<dimen name="display_cutout_touchable_region_size">12dp</dimen>
+ <!-- Padding below Ongoing App Ops dialog title -->
+ <dimen name="ongoing_appops_dialog_sep">16dp</dimen>
+ <!--Padding around text items in Ongoing App Ops dialog -->
+ <dimen name="ongoing_appops_dialog_text_padding">16dp</dimen>
<!-- Height of icons in Ongoing App Ops dialog. Both App Op icon and application icon -->
- <dimen name="ongoing_appops_dialog_icon_height">48dp</dimen>
+ <dimen name="ongoing_appops_dialog_icon_height">28dp</dimen>
<!-- Margin between text lines in Ongoing App Ops dialog -->
<dimen name="ongoing_appops_dialog_text_margin">15dp</dimen>
+ <!-- Side padding of title in Ongoing App Ops dialog -->
+ <dimen name="ongoing_appops_dialog_title_padding">10dp</dimen>
<!-- Padding around Ongoing App Ops dialog content -->
<dimen name="ongoing_appops_dialog_content_padding">24dp</dimen>
- <!-- Margins around the Ongoing App Ops chip. In landscape, the side margins are 0 -->
+ <!-- Side margins around the Ongoing App Ops chip-->
<dimen name="ongoing_appops_chip_margin">12dp</dimen>
+ <!-- Top and bottom margins around the Ongoing App Ops chip -->
+ <dimen name="ongoing_appops_top_chip_margin">12dp</dimen>
<!-- Start and End padding for Ongoing App Ops chip -->
<dimen name="ongoing_appops_chip_side_padding">6dp</dimen>
<!-- Padding between background of Ongoing App Ops chip and content -->
- <dimen name="ongoing_appops_chip_bg_padding">4dp</dimen>
+ <dimen name="ongoing_appops_chip_bg_padding">0dp</dimen>
+ <!-- Margin between icons of Ongoing App Ops chip -->
+ <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
+ <!-- Icon size of Ongoing App Ops chip -->
+ <dimen name="ongoing_appops_chip_icon_size">18dp</dimen>
<!-- Radius of Ongoing App Ops chip corners -->
<dimen name="ongoing_appops_chip_bg_corner_radius">12dp</dimen>
+ <!-- Text size for Ongoing App Ops dialog title -->
+ <dimen name="ongoing_appops_dialog_title_size">24sp</dimen>
+ <!-- Text size for Ongoing App Ops dialog items -->
+ <dimen name="ongoing_appops_dialog_item_size">20sp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 50454fc..4a0bc9b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2250,39 +2250,48 @@
app for debugging. Will not be seen by users. [CHAR LIMIT=20] -->
<string name="heap_dump_tile_name">Dump SysUI Heap</string>
+ <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=10] -->
+ <string name="ongoing_privacy_chip_multiple_apps"><xliff:g id="num_apps" example="3">%d</xliff:g> apps</string>
+
<!-- Content description for ongoing privacy chip. Use with a single app [CHAR LIMIT=NONE]-->
<string name="ongoing_privacy_chip_content_single_app"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="types_list" example="camera, location">%2$s</xliff:g>.</string>
<!-- Content description for ongoing privacy chip. Use with multiple apps [CHAR LIMIT=NONE]-->
<string name="ongoing_privacy_chip_content_multiple_apps">Applications are using your <xliff:g id="types_list" example="camera, location">%s</xliff:g>.</string>
- <!-- Action on Ongoing Privacy Dialog to open application [CHAR LIMIT=10]-->
- <string name="ongoing_privacy_dialog_open_app">Open app</string>
+ <!-- Content description for ongoing privacy chip. Use with multiple apps using same app op[CHAR LIMIT=NONE]-->
+ <string name="ongoing_privacy_chip_content_multiple_apps_single_op"><xliff:g id="num_apps" example="3">%1$d</xliff:g> applications are using your <xliff:g id="type" example="camera">%2$s</xliff:g>.</string>
<!-- Action on Ongoing Privacy Dialog to dismiss [CHAR LIMIT=10]-->
<string name="ongoing_privacy_dialog_cancel">Cancel</string>
- <!-- Action on Ongoing Privacy Dialog to dismiss [CHAR LIMIT=10]-->
- <string name="ongoing_privacy_dialog_okay">Okay</string>
+ <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=15]-->
+ <string name="ongoing_privacy_dialog_open_settings">View details</string>
- <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=10]-->
- <string name="ongoing_privacy_dialog_open_settings">Settings</string>
+ <!-- Text for item in Ongoing Privacy Dialog title when only one app is using app ops [CHAR LIMIT=NONE] -->
+ <string name="ongoing_privacy_dialog_single_app_title">App using your <xliff:g id="types_list" example="camera( and location)">%s</xliff:g></string>
- <!-- Text for item in Ongoing Privacy Dialog when only one app is using a particular type of app op [CHAR LIMIT=NONE] -->
- <string name="ongoing_privacy_dialog_app_item"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="type" example="camera">%2$s</xliff:g> for the last <xliff:g id="time" example="3">%3$d</xliff:g> min</string>
+ <!-- Text for item in Ongoing Privacy Dialog title when multiple apps is using app ops [CHAR LIMIT=NONE] -->
+ <string name="ongoing_privacy_dialog_multiple_apps_title">Apps using your <xliff:g id="types_list" example="camera( and location)">%s</xliff:g></string>
- <!-- Text for item in Ongoing Privacy Dialog when only multiple apps are using a particular type of app op [CHAR LIMIT=NONE] -->
- <string name="ongoing_privacy_dialog_apps_item"><xliff:g id="apps" example="Camera, Phone">%1$s</xliff:g> are using your <xliff:g id="type" example="camera">%2$s</xliff:g></string>
+ <!-- Separator for types. Include spaces before and after if needed [CHAR LIMIT=10] -->
+ <string name="ongoing_privacy_dialog_separator">,\u0020</string>
- <!-- Text for Ongoing Privacy Dialog when a single app is using app ops [CHAR LIMIT=NONE] -->
- <string name="ongoing_privacy_dialog_single_app"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="types_list" example="camera, location">%2$s</xliff:g></string>
+ <!-- Separator for types, before last type. Include spaces before and after if needed [CHAR LIMIT=10] -->
+ <string name="ongoing_privacy_dialog_last_separator">\u0020and\u0020</string>
- <!-- Text for camera app op [CHAR LIMIT=12]-->
+ <!-- Text for camera app op [CHAR LIMIT=20]-->
<string name="privacy_type_camera">camera</string>
- <!-- Text for location app op [CHAR LIMIT=12]-->
+ <!-- Text for location app op [CHAR LIMIT=20]-->
<string name="privacy_type_location">location</string>
- <!-- Text for microphone app op [CHAR LIMIT=12]-->
+ <!-- Text for microphone app op [CHAR LIMIT=20]-->
<string name="privacy_type_microphone">microphone</string>
+
+ <!-- Text for indicating extra apps using app ops [CHAR LIMIT=NONE] -->
+ <plurals name="ongoing_privacy_dialog_overflow_text">
+ <item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> other app</item>
+ <item quantity="other"><xliff:g id="num_apps" example="3">%d</xliff:g> other app</item>
+ </plurals>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 95fd86f..e9aa1b6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -253,6 +253,18 @@
<item name="android:textSize">@dimen/qs_carrier_info_text_size</item>
</style>
+ <style name="TextAppearance.AppOpsDialog" />
+
+ <style name="TextAppearance.AppOpsDialog.Title">
+ <item name="android:textSize">@dimen/ongoing_appops_dialog_title_size</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ </style>
+
+ <style name="TextAppearance.AppOpsDialog.Item">
+ <item name="android:textSize">@dimen/ongoing_appops_dialog_item_size</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ </style>
+
<style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index e3584cf..3666400 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -88,8 +88,6 @@
private Runnable mWatchLongPress;
private final long mLongPressTimeout;
- protected boolean mSwipingInProgress;
-
final private int[] mTmpPos = new int[2];
private final int mFalsingThreshold;
private boolean mTouchAboveFalsingThreshold;
@@ -130,10 +128,6 @@
mDisableHwLayers = disableHwLayers;
}
- public boolean isSwipingInProgress() {
- return mSwipingInProgress;
- }
-
private float getPos(MotionEvent ev) {
return mSwipeDirection == X ? ev.getX() : ev.getY();
}
@@ -325,7 +319,6 @@
if (Math.abs(delta) > mPagingTouchSlop
&& Math.abs(delta) > Math.abs(deltaPerpendicular)) {
if (mCallback.canChildBeDragged(mCurrView)) {
- mSwipingInProgress = true;
mCallback.onBeginDrag(mCurrView);
mDragging = true;
mInitialTouchPos = getPos(ev);
@@ -445,7 +438,6 @@
wasRemoved = row.isRemoved();
}
if (!mCancelled || wasRemoved) {
- mSwipingInProgress = false;
mCallback.onChildDismissed(animView);
}
if (endAction != null) {
@@ -637,7 +629,6 @@
!swipedFastEnough() /* useAccelerateInterpolator */);
} else {
// snappity
- mSwipingInProgress = false;
mCallback.onDragCancelled(mCurrView);
snapChild(mCurrView, 0 /* leftTarget */, velocity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index fc1baef..d3715d0 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -15,7 +15,6 @@
package com.android.systemui.privacy
import android.content.Context
-import android.graphics.Color
import android.util.AttributeSet
import android.view.ViewGroup
import android.widget.ImageView
@@ -30,7 +29,13 @@
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttrs, defStyleRes) {
- private lateinit var appName: TextView
+ private val iconMargin =
+ context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
+ private val iconSize =
+ context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
+ val iconColor = context.resources.getColor(
+ R.color.status_bar_clock_color, context.theme)
+ private lateinit var text: TextView
private lateinit var iconsContainer: LinearLayout
var builder = PrivacyDialogBuilder(context, emptyList<PrivacyItem>())
var privacyList = emptyList<PrivacyItem>()
@@ -43,7 +48,7 @@
override fun onFinishInflate() {
super.onFinishInflate()
- appName = findViewById(R.id.app_name)
+ text = findViewById(R.id.text_container)
iconsContainer = findViewById(R.id.icons_container)
}
@@ -53,39 +58,52 @@
iconsContainer.removeAllViews()
dialogBuilder.generateIcons().forEach {
it.mutate()
- it.setTint(Color.WHITE)
- iconsContainer.addView(ImageView(context).apply {
+ it.setTint(iconColor)
+ val image = ImageView(context).apply {
setImageDrawable(it)
- maxHeight = this@OngoingPrivacyChip.height
- })
+ scaleType = ImageView.ScaleType.CENTER_INSIDE
+ }
+ iconsContainer.addView(image, iconSize, iconSize)
+ val lp = image.layoutParams as MarginLayoutParams
+ lp.marginStart = iconMargin
+ image.layoutParams = lp
}
}
- if (privacyList.isEmpty()) {
- return
- } else {
+ if (!privacyList.isEmpty()) {
generateContentDescription()
setIcons(builder, iconsContainer)
- appName.visibility = GONE
- builder.app?.let {
- appName.apply {
- setText(it.applicationName)
- setTextColor(Color.WHITE)
- visibility = VISIBLE
+ text.visibility = if (builder.types.size == 1) VISIBLE else GONE
+ if (builder.types.size == 1) {
+ if (builder.app != null) {
+ text.setText(builder.app?.applicationName)
+ } else {
+ text.text = context.getString(R.string.ongoing_privacy_chip_multiple_apps,
+ builder.appsAndTypes.size)
}
}
+ } else {
+ text.visibility = GONE
+ iconsContainer.removeAllViews()
}
requestLayout()
}
private fun generateContentDescription() {
- val typesText = builder.generateTypesText()
- if (builder.app != null) {
- contentDescription = context.getString(R.string.ongoing_privacy_chip_content_single_app,
- builder.app?.applicationName, typesText)
- } else {
+ val typesText = builder.joinTypes()
+ if (builder.types.size > 1) {
contentDescription = context.getString(
R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
+ } else {
+ if (builder.app != null) {
+ contentDescription =
+ context.getString(R.string.ongoing_privacy_chip_content_single_app,
+ builder.app?.applicationName, typesText)
+ } else {
+ contentDescription = context.getString(
+ R.string.ongoing_privacy_chip_content_multiple_apps_single_op,
+ builder.appsAndTypes.size, typesText)
+ }
}
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
index 1d0e16e..f6a95af 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt
@@ -18,10 +18,10 @@
import android.app.Dialog
import android.content.Context
import android.content.DialogInterface
-import android.graphics.drawable.Drawable
+import android.content.Intent
+import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.View
-import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
@@ -34,29 +34,25 @@
val dialogBuilder: PrivacyDialogBuilder
) {
- val iconHeight = context.resources.getDimensionPixelSize(
+ val iconSize = context.resources.getDimensionPixelSize(
R.dimen.ongoing_appops_dialog_icon_height)
- val textMargin = context.resources.getDimensionPixelSize(
- R.dimen.ongoing_appops_dialog_text_margin)
val iconColor = context.resources.getColor(
com.android.internal.R.color.text_color_primary, context.theme)
+ companion object {
+ private const val MAX_ITEMS = 10
+ }
fun createDialog(): Dialog {
- val builder = AlertDialog.Builder(context)
- .setNeutralButton(R.string.ongoing_privacy_dialog_open_settings, null)
- if (dialogBuilder.app != null) {
- builder.setPositiveButton(R.string.ongoing_privacy_dialog_open_app,
+ val builder = AlertDialog.Builder(context).apply {
+ setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null)
+ setPositiveButton(R.string.ongoing_privacy_dialog_open_settings,
object : DialogInterface.OnClickListener {
- val intent = context.packageManager
- .getLaunchIntentForPackage(dialogBuilder.app.packageName)
+ val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE)
override fun onClick(dialog: DialogInterface?, which: Int) {
Dependency.get(ActivityStarter::class.java).startActivity(intent, false)
}
})
- builder.setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null)
- } else {
- builder.setPositiveButton(R.string.ongoing_privacy_dialog_okay, null)
}
builder.setView(getContentView())
return builder.create()
@@ -66,44 +62,67 @@
val layoutInflater = LayoutInflater.from(context)
val contentView = layoutInflater.inflate(R.layout.ongoing_privacy_dialog_content, null)
- val iconsContainer = contentView.findViewById(R.id.icons_container) as LinearLayout
- val textContainer = contentView.findViewById(R.id.text_container) as LinearLayout
+ val title = contentView.findViewById(R.id.title) as TextView
+ val appsList = contentView.findViewById(R.id.items_container) as LinearLayout
- addIcons(dialogBuilder, iconsContainer)
- val lm = ViewGroup.MarginLayoutParams(
- ViewGroup.MarginLayoutParams.WRAP_CONTENT,
- ViewGroup.MarginLayoutParams.WRAP_CONTENT)
- lm.topMargin = textMargin
- val now = System.currentTimeMillis()
- dialogBuilder.generateText(now).forEach {
- val text = layoutInflater.inflate(R.layout.ongoing_privacy_text_item, null) as TextView
- text.setText(it)
- textContainer.addView(text, lm)
+ title.setText(dialogBuilder.getDialogTitle())
+
+ val numItems = dialogBuilder.appsAndTypes.size
+ for (i in 0..(numItems - 1)) {
+ if (i >= MAX_ITEMS) break
+ val item = dialogBuilder.appsAndTypes[i]
+ addAppItem(appsList, item.first, item.second, dialogBuilder.types.size > 1)
}
+
+ if (numItems > MAX_ITEMS) {
+ val overflow = contentView.findViewById(R.id.overflow) as LinearLayout
+ overflow.visibility = View.VISIBLE
+ val overflowText = overflow.findViewById(R.id.app_name) as TextView
+ overflowText.text = context.resources.getQuantityString(
+ R.plurals.ongoing_privacy_dialog_overflow_text,
+ numItems - MAX_ITEMS,
+ numItems - MAX_ITEMS
+ )
+ val overflowPlus = overflow.findViewById(R.id.app_icon) as ImageView
+ overflowPlus.apply {
+ imageTintList = ColorStateList.valueOf(iconColor)
+ setImageDrawable(context.getDrawable(R.drawable.plus))
+ }
+ }
+
return contentView
}
- private fun addIcons(dialogBuilder: PrivacyDialogBuilder, iconsContainer: LinearLayout) {
+ private fun addAppItem(
+ itemList: LinearLayout,
+ app: PrivacyApplication,
+ types: List<PrivacyType>,
+ showIcons: Boolean = true
+ ) {
+ val layoutInflater = LayoutInflater.from(context)
+ val item = layoutInflater.inflate(R.layout.ongoing_privacy_dialog_item, itemList, false)
+ val appIcon = item.findViewById(R.id.app_icon) as ImageView
+ val appName = item.findViewById(R.id.app_name) as TextView
+ val icons = item.findViewById(R.id.icons) as LinearLayout
- fun LinearLayout.addIcon(icon: Drawable) {
- val image = ImageView(context).apply {
- setImageDrawable(icon.apply {
- setBounds(0, 0, iconHeight, iconHeight)
- maxHeight = this@addIcon.height
- })
- adjustViewBounds = true
+ app.icon?.let {
+ appIcon.setImageDrawable(it)
+ }
+
+ appName.text = app.applicationName
+ if (showIcons) {
+ dialogBuilder.generateIconsForApp(types).forEach {
+ it.setBounds(0, 0, iconSize, iconSize)
+ val image = ImageView(context).apply {
+ imageTintList = ColorStateList.valueOf(iconColor)
+ setImageDrawable(it)
+ }
+ icons.addView(image, iconSize, LinearLayout.LayoutParams.WRAP_CONTENT)
}
- addView(image, LinearLayout.LayoutParams.WRAP_CONTENT,
- LinearLayout.LayoutParams.MATCH_PARENT)
+ icons.visibility = View.VISIBLE
+ } else {
+ icons.visibility = View.GONE
}
-
- dialogBuilder.generateIcons().forEach {
- it.mutate()
- it.setTint(iconColor)
- iconsContainer.addIcon(it)
- }
- dialogBuilder.app.let {
- it?.icon?.let { iconsContainer.addIcon(it) }
- }
+ itemList.addView(item)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt
index 5ce4ee7..519df19 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt
@@ -15,59 +15,53 @@
package com.android.systemui.privacy
import android.content.Context
+import android.graphics.drawable.Drawable
import com.android.systemui.R
-import java.lang.Math.max
class PrivacyDialogBuilder(val context: Context, itemsList: List<PrivacyItem>) {
- companion object {
- val MILLIS_IN_MINUTE: Long = 1000 * 60
- }
- private val itemsByType: Map<PrivacyType, List<PrivacyItem>>
+ val appsAndTypes: List<Pair<PrivacyApplication, List<PrivacyType>>>
+ val types: List<PrivacyType>
val app: PrivacyApplication?
+ private val separator = context.getString(R.string.ongoing_privacy_dialog_separator)
+ private val lastSeparator = context.getString(R.string.ongoing_privacy_dialog_last_separator)
init {
- itemsByType = itemsList.groupBy { it.privacyType }
- val apps = itemsList.map { it.application }.distinct()
- val singleApp = apps.size == 1
- app = if (singleApp) apps.get(0) else null
+ appsAndTypes = itemsList.groupBy({ it.application }, { it.privacyType })
+ .toList()
+ .sortedWith(compareBy({ -it.second.size }, { it.first }))
+ types = itemsList.map { it.privacyType }.distinct().sorted()
+ val singleApp = appsAndTypes.size == 1
+ app = if (singleApp) appsAndTypes[0].first else null
}
- private fun buildTextForItem(type: PrivacyType, now: Long): String {
- val items = itemsByType.getOrDefault(type, emptyList<PrivacyItem>())
- return when (items.size) {
- 0 -> throw IllegalStateException("List cannot be empty")
- 1 -> {
- val item = items.get(0)
- val minutesUsed = max(((now - item.timeStarted) / MILLIS_IN_MINUTE).toInt(), 1)
- context.getString(R.string.ongoing_privacy_dialog_app_item,
- item.application.applicationName, type.getName(context), minutesUsed)
- }
- else -> {
- val apps = items.map { it.application.applicationName }.joinToString()
- context.getString(R.string.ongoing_privacy_dialog_apps_item,
- apps, type.getName(context))
- }
+ fun generateIconsForApp(types: List<PrivacyType>): List<Drawable> {
+ return types.sorted().map { it.getIcon(context) }
+ }
+
+ fun generateIcons() = types.map { it.getIcon(context) }
+
+ private fun <T> List<T>.joinWithAnd(): StringBuilder {
+ return subList(0, size - 1).joinTo(StringBuilder(), separator = separator).apply {
+ append(lastSeparator)
+ append(this@joinWithAnd.last())
}
}
- private fun buildTextForApp(types: Set<PrivacyType>): List<String> {
- app?.let {
- val typesText = types.map { it.getName(context) }.sorted().joinToString()
- return listOf(context.getString(R.string.ongoing_privacy_dialog_single_app,
- it.applicationName, typesText))
- } ?: throw IllegalStateException("There has to be a single app")
+ fun joinTypes(): String {
+ return when (types.size) {
+ 0 -> ""
+ 1 -> types[0].getName(context)
+ else -> types.map { it.getName(context) }.joinWithAnd().toString()
+ }
}
- fun generateText(now: Long): List<String> {
- if (app == null || itemsByType.keys.size == 1) {
- return itemsByType.keys.map { buildTextForItem(it, now) }
+ fun getDialogTitle(): String {
+ if (app != null) {
+ return context.getString(R.string.ongoing_privacy_dialog_single_app_title, joinTypes())
} else {
- return buildTextForApp(itemsByType.keys)
+ return context.getString(R.string.ongoing_privacy_dialog_multiple_apps_title,
+ joinTypes())
}
}
-
- fun generateTypesText() = itemsByType.keys.map { it.getName(context) }.sorted().joinToString()
-
- fun generateIcons() = itemsByType.keys.map { it.getIcon(context) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
index 3d9aa0f..85e99f0 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -29,16 +29,21 @@
fun getName(context: Context) = context.resources.getString(nameId)
- fun getIcon(context: Context) = context.resources.getDrawable(iconId, null)
+ fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme)
}
data class PrivacyItem(
val privacyType: PrivacyType,
- val application: PrivacyApplication,
- val timeStarted: Long
+ val application: PrivacyApplication
)
-data class PrivacyApplication(val packageName: String, val context: Context) {
+data class PrivacyApplication(val packageName: String, val context: Context)
+ : Comparable<PrivacyApplication> {
+
+ override fun compareTo(other: PrivacyApplication): Int {
+ return applicationName.compareTo(other.applicationName)
+ }
+
var icon: Drawable? = null
var applicationName: String
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 5141e50..3fa3e8e 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -95,7 +95,7 @@
else -> return null
}
val app = PrivacyApplication(appOpItem.packageName, context)
- return PrivacyItem(type, app, appOpItem.timeStarted)
+ return PrivacyItem(type, app)
}
// Used by containing class to get notified of changes
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index e3f85d9..427f638 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -325,15 +325,10 @@
newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE;
mBatteryMeterView.useWallpaperTextColor(shouldUseWallpaperTextColor);
mClockView.useWallpaperTextColor(shouldUseWallpaperTextColor);
-
- MarginLayoutParams lm = (MarginLayoutParams) mPrivacyChip.getLayoutParams();
- int sideMargins = lm.leftMargin;
- int topBottomMargins = (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
- ? 0 : sideMargins;
- lm.setMargins(sideMargins, topBottomMargins, sideMargins, topBottomMargins);
- mPrivacyChip.setLayoutParams(lm);
}
+
+
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
super.onRtlPropertiesChanged(layoutDirection);
@@ -378,6 +373,15 @@
setLayoutParams(lp);
+ if (mPrivacyChip != null) {
+ MarginLayoutParams lm = (MarginLayoutParams) mPrivacyChip.getLayoutParams();
+ int sideMargins = lm.leftMargin;
+ int topBottomMargins = resources.getDimensionPixelSize(
+ R.dimen.ongoing_appops_top_chip_margin);
+ lm.setMargins(sideMargins, topBottomMargins, sideMargins, topBottomMargins);
+ mPrivacyChip.setLayoutParams(lm);
+ }
+
updateStatusIconAlphaAnimator();
updateHeaderTextContainerAlphaAnimator();
}
@@ -729,7 +733,8 @@
public void setMargins(int sideMargins) {
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
- if (v == mSystemIconsView || v == mQuickQsStatusIcons || v == mHeaderQsPanel) {
+ if (v == mSystemIconsView || v == mQuickQsStatusIcons || v == mHeaderQsPanel
+ || v == mPrivacyChip) {
continue;
}
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) v.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
index f42c6ef..f23ae3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -53,7 +53,8 @@
public static NotificationUiAdjustment extractFromNotificationEntry(
NotificationData.Entry entry) {
- return new NotificationUiAdjustment(entry.key, entry.smartActions, entry.smartReplies);
+ return new NotificationUiAdjustment(
+ entry.key, entry.systemGeneratedSmartActions, entry.smartReplies);
}
public static boolean needReinflate(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
index 4e712a5..da6d977 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationData.java
@@ -114,8 +114,9 @@
public CharSequence remoteInputText;
public List<SnoozeCriterion> snoozeCriteria;
public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
+ /** Smart Actions provided by the NotificationAssistantService. */
@NonNull
- public List<Notification.Action> smartActions = Collections.emptyList();
+ public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
public CharSequence[] smartReplies = new CharSequence[0];
private int mCachedContrastColor = COLOR_INVALID;
@@ -171,7 +172,7 @@
importance = ranking.getImportance();
snoozeCriteria = ranking.getSnoozeCriteria();
userSentiment = ranking.getUserSentiment();
- smartActions = ranking.getSmartActions() == null
+ systemGeneratedSmartActions = ranking.getSmartActions() == null
? Collections.emptyList() : ranking.getSmartActions();
smartReplies = ranking.getSmartReplies() == null
? new CharSequence[0]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 3bea7db..274d4b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -702,7 +702,6 @@
&& !mPresenter.isPresenterFullyCollapsed();
row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
- row.setSmartActions(entry.smartActions);
row.setEntry(entry);
row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, shouldHeadsUp(entry));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5166e06..c7876cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -39,7 +39,6 @@
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Notification;
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -1567,10 +1566,6 @@
mNotificationInflater.setUsesIncreasedHeight(use);
}
- public void setSmartActions(List<Notification.Action> smartActions) {
- mNotificationInflater.setSmartActions(smartActions);
- }
-
public void setUseIncreasedHeadsUpHeight(boolean use) {
mUseIncreasedHeadsUpHeight = use;
mNotificationInflater.setUsesIncreasedHeadsUpHeight(use);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
index 38d6b35..e1c2f73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java
@@ -43,10 +43,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
@@ -131,7 +128,6 @@
private boolean mIsChildInGroup;
private InflationCallback mCallback;
private boolean mRedactAmbient;
- private List<Notification.Action> mSmartActions;
private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();
public NotificationInflater(ExpandableNotificationRow row) {
@@ -161,10 +157,6 @@
mUsesIncreasedHeight = usesIncreasedHeight;
}
- public void setSmartActions(List<Notification.Action> smartActions) {
- mSmartActions = smartActions;
- }
-
public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
}
@@ -258,8 +250,7 @@
StatusBarNotification sbn = mRow.getEntry().notification;
AsyncInflationTask task = new AsyncInflationTask(sbn, reInflateFlags, mCachedContentViews,
mRow, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
- mUsesIncreasedHeadsUpHeight, mRedactAmbient, mCallback, mRemoteViewClickHandler,
- mSmartActions);
+ mUsesIncreasedHeadsUpHeight, mRedactAmbient, mCallback, mRemoteViewClickHandler);
if (mCallback != null && mCallback.doInflateSynchronous()) {
task.onPostExecute(task.doInBackground());
} else {
@@ -765,15 +756,13 @@
private Exception mError;
private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private CancellationSignal mCancellationSignal;
- private List<Notification.Action> mSmartActions;
private AsyncInflationTask(StatusBarNotification notification,
@InflationFlag int reInflateFlags,
ArrayMap<Integer, RemoteViews> cachedContentViews, ExpandableNotificationRow row,
boolean isLowPriority, boolean isChildInGroup, boolean usesIncreasedHeight,
boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
- InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler,
- List<Notification.Action> smartActions) {
+ InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler) {
mRow = row;
mSbn = notification;
mReInflateFlags = reInflateFlags;
@@ -786,9 +775,6 @@
mRedactAmbient = redactAmbient;
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
- mSmartActions = smartActions == null
- ? Collections.emptyList()
- : new ArrayList<>(smartActions);
NotificationData.Entry entry = row.getEntry();
entry.setInflationTask(this);
}
@@ -806,8 +792,6 @@
= Notification.Builder.recoverBuilder(mContext,
mSbn.getNotification());
- applyChanges(recoveredBuilder);
-
Context packageContext = mSbn.getPackageContext(mContext);
Notification notification = mSbn.getNotification();
if (notification.isMediaNotification()) {
@@ -834,18 +818,6 @@
}
}
- /**
- * Apply changes to the given notification builder, like adding smart actions suggested by
- * a {@link android.service.notification.NotificationAssistantService}.
- */
- private void applyChanges(Notification.Builder builder) {
- if (mSmartActions != null) {
- for (Notification.Action smartAction : mSmartActions) {
- builder.addAction(smartAction);
- }
- }
- }
-
private void handleError(Exception e) {
mRow.getEntry().onInflationTaskFinished();
StatusBarNotification sbn = mRow.getStatusBarNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 2bc4f02..c16b28f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -17,13 +17,16 @@
package com.android.systemui.statusbar.notification.row;
import static com.android.systemui.SwipeHelper.SWIPED_FAR_ENOUGH_SIZE_FRACTION;
+import static com.android.systemui.statusbar.notification.row.NotificationInfo.ACTION_BLOCK;
import static com.android.systemui.statusbar.notification.row.NotificationInfo.ACTION_NONE;
+import static com.android.systemui.statusbar.notification.row.NotificationInfo.ACTION_TOGGLE_SILENT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.app.Notification;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
@@ -41,6 +44,7 @@
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
+import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
import com.android.systemui.statusbar.notification.row.NotificationInfo.NotificationInfoAction;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -69,7 +73,7 @@
private Context mContext;
private FrameLayout mMenuContainer;
- private MenuItem mInfoItem;
+ private NotificationInfoMenuItem mInfoItem;
private MenuItem mAppOpsItem;
private MenuItem mSnoozeItem;
private ArrayList<MenuItem> mLeftMenuItems;
@@ -172,7 +176,9 @@
@Override
public void createMenu(ViewGroup parent, StatusBarNotification sbn) {
mParent = (ExpandableNotificationRow) parent;
- createMenuViews(true /* resetState */);
+ createMenuViews(true /* resetState */,
+ sbn != null && (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)
+ != 0);
}
@Override
@@ -216,7 +222,8 @@
// Menu hasn't been created yet, no need to do anything.
return;
}
- createMenuViews(!isMenuVisible() /* resetState */);
+ createMenuViews(!isMenuVisible() /* resetState */,
+ (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0);
}
@Override
@@ -231,30 +238,47 @@
mParent.removeListener();
}
- private void createMenuViews(boolean resetState) {
+ private void createMenuViews(boolean resetState, final boolean isForeground) {
final Resources res = mContext.getResources();
mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
mLeftMenuItems.clear();
mRightMenuItems.clear();
// Construct the menu items based on the notification
- if (mParent != null && mParent.getStatusBarNotification() != null) {
- int flags = mParent.getStatusBarNotification().getNotification().flags;
- boolean isForeground = (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
- if (!isForeground) {
- // Only show snooze for non-foreground notifications
- mSnoozeItem = createSnoozeItem(mContext);
- mLeftMenuItems.add(mSnoozeItem);
- mRightMenuItems.add(mSnoozeItem);
- }
+ if (!isForeground) {
+ // Only show snooze for non-foreground notifications
+ mSnoozeItem = createSnoozeItem(mContext);
+ mLeftMenuItems.add(mSnoozeItem);
}
mInfoItem = createInfoItem(mContext);
- mLeftMenuItems.add(mInfoItem);
- mRightMenuItems.add(mInfoItem);
+ if (!NotificationUtils.useNewInterruptionModel(mContext)) {
+ mLeftMenuItems.add(mInfoItem);
+ }
mAppOpsItem = createAppOpsItem(mContext);
mLeftMenuItems.add(mAppOpsItem);
- mRightMenuItems.add(mAppOpsItem);
+
+ if (NotificationUtils.useNewInterruptionModel(mContext)) {
+ if (!mParent.getIsNonblockable()) {
+ mRightMenuItems.add(createBlockItem(mContext, mInfoItem.getGutsView()));
+ }
+ // TODO(kprevas): this is duplicated logic
+ // but it's currently spread across NotificationGutsManager and NotificationInfo.
+ // Try to consolidate and reuse here.
+ boolean canToggleSilent = !mParent.getIsNonblockable()
+ && !isForeground
+ && mParent.getEntry().noisy;
+ if (canToggleSilent) {
+ int channelImportance = mParent.getEntry().channel.getImportance();
+ int effectiveImportance =
+ channelImportance == NotificationManager.IMPORTANCE_UNSPECIFIED
+ ? mParent.getEntry().importance : channelImportance;
+ mRightMenuItems.add(createToggleSilentItem(mContext, mInfoItem.getGutsView(),
+ effectiveImportance < NotificationManager.IMPORTANCE_DEFAULT));
+ }
+ } else {
+ mRightMenuItems.addAll(mLeftMenuItems);
+ }
populateMenuViews();
if (resetState) {
@@ -597,7 +621,7 @@
// TODO -- handle / allow custom menu items!
}
- public static MenuItem createSnoozeItem(Context context) {
+ static MenuItem createSnoozeItem(Context context) {
Resources res = context.getResources();
NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(context)
.inflate(R.layout.notification_snooze, null, false);
@@ -607,17 +631,16 @@
return snooze;
}
- public static MenuItem createInfoItem(Context context) {
+ static NotificationInfoMenuItem createInfoItem(Context context) {
Resources res = context.getResources();
String infoDescription = res.getString(R.string.notification_menu_gear_description);
NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
R.layout.notification_info, null, false);
- MenuItem info = new NotificationInfoMenuItem(context, infoDescription, infoContent,
+ return new NotificationInfoMenuItem(context, infoDescription, infoContent,
R.drawable.ic_settings, ACTION_NONE);
- return info;
}
- public static MenuItem createAppOpsItem(Context context) {
+ static MenuItem createAppOpsItem(Context context) {
AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate(
R.layout.app_ops_info, null, false);
MenuItem info = new NotificationMenuItem(context, null, appOpsContent,
@@ -625,6 +648,29 @@
return info;
}
+ private static MenuItem createBlockItem(Context context, NotificationInfo gutsView) {
+ return new NotificationInfoMenuItem(
+ context,
+ context.getResources().getString(R.string.inline_stop_button),
+ gutsView,
+ R.drawable.ic_notification_block,
+ ACTION_BLOCK);
+ }
+
+ private static MenuItem createToggleSilentItem(Context context, NotificationInfo gutsView,
+ boolean isCurrentlySilent) {
+ return new NotificationInfoMenuItem(
+ context,
+ isCurrentlySilent
+ ? context.getResources().getString(R.string.inline_silent_button_alert)
+ : context.getResources().getString(R.string.inline_silent_button_silent),
+ gutsView,
+ isCurrentlySilent
+ ? R.drawable.ic_notifications_alert
+ : R.drawable.ic_notifications_silence,
+ ACTION_TOGGLE_SILENT);
+ }
+
private void addMenuView(MenuItem item, ViewGroup parent) {
View menuView = item.getMenuView();
if (menuView != null) {
@@ -706,7 +752,8 @@
* Add a new 'guts' panel. If iconResId < 0 it will not appear in the slow swipe menu
* but can still be exposed via other affordances.
*/
- public NotificationMenuItem(Context context, String s, GutsContent content, int iconResId) {
+ public NotificationMenuItem(Context context, String contentDescription, GutsContent content,
+ int iconResId) {
Resources res = context.getResources();
int padding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
int tint = res.getColor(R.color.notification_gear_color);
@@ -719,7 +766,7 @@
iv.setAlpha(1f);
mMenuView = iv;
}
- mContentDescription = s;
+ mContentDescription = contentDescription;
mGutsContent = content;
}
@@ -746,11 +793,16 @@
@NotificationInfoAction
int mAction;
- public NotificationInfoMenuItem(Context context, String s,
+ public NotificationInfoMenuItem(Context context, String contentDescription,
NotificationInfo content, int iconResId,
@NotificationInfoAction int action) {
- super(context, s, content, iconResId);
+ super(context, contentDescription, content, iconResId);
this.mAction = action;
}
+
+ @Override
+ public NotificationInfo getGutsView() {
+ return (NotificationInfo) super.getGutsView();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index a8ced7a..1be2afe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -38,6 +38,7 @@
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
import com.android.systemui.statusbar.notification.ImageTransformState;
+import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -69,7 +70,8 @@
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row);
mShowExpandButtonAtEnd = ctx.getResources().getBoolean(
- R.bool.config_showNotificationExpandButtonAtEnd);
+ R.bool.config_showNotificationExpandButtonAtEnd)
+ || NotificationUtils.useNewInterruptionModel(ctx);
mTransformationHelper = new ViewTransformationHelper();
// we want to avoid that the header clashes with the other text when transforming
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index a7329b0..ff31b261 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -401,6 +401,8 @@
*/
private float mBackgroundXFactor = 1f;
+ private boolean mSwipingInProgress;
+
private boolean mUsingLightTheme;
private boolean mQsExpanded;
private boolean mForwardScrollable;
@@ -3286,7 +3288,7 @@
|| ev.getActionMasked() == MotionEvent.ACTION_UP;
handleEmptySpaceClick(ev);
boolean expandWantsIt = false;
- boolean swipingInProgress = mSwipeHelper.isSwipingInProgress();
+ boolean swipingInProgress = mSwipingInProgress;
if (mIsExpanded && !swipingInProgress && !mOnlyScrollingInThisMotion) {
if (isCancelOrUp) {
mExpandHelper.onlyObserveMovements(false);
@@ -3341,7 +3343,7 @@
@Override
@ShadeViewRefactor(RefactorComponent.INPUT)
public boolean onGenericMotionEvent(MotionEvent event) {
- if (!isScrollingEnabled() || !mIsExpanded || mSwipeHelper.isSwipingInProgress() || mExpandingNotification
+ if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification
|| mDisallowScrollingInThisMotion) {
return false;
}
@@ -3568,7 +3570,7 @@
initDownStates(ev);
handleEmptySpaceClick(ev);
boolean expandWantsIt = false;
- boolean swipingInProgress = mSwipeHelper.isSwipingInProgress();
+ boolean swipingInProgress = mSwipingInProgress;
if (!swipingInProgress && !mOnlyScrollingInThisMotion) {
expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
}
@@ -3847,6 +3849,14 @@
}
}
+ @ShadeViewRefactor(RefactorComponent.INPUT)
+ private void setSwipingInProgress(boolean swiping) {
+ mSwipingInProgress = swiping;
+ if (swiping) {
+ requestDisallowInterceptTouchEvent(true);
+ }
+ }
+
@Override
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void onWindowFocusChanged(boolean hasWindowFocus) {
@@ -5642,6 +5652,7 @@
@Override
public void onDragCancelled(View v) {
+ setSwipingInProgress(false);
mFalsingManager.onNotificatonStopDismissing();
}
@@ -5669,6 +5680,7 @@
*/
public void handleChildViewDismissed(View view) {
+ setSwipingInProgress(false);
if (mDismissAllInProgress) {
return;
}
@@ -5737,6 +5749,7 @@
@Override
public void onBeginDrag(View v) {
mFalsingManager.onNotificatonStartDismissing();
+ setSwipingInProgress(true);
mAmbientState.onBeginDrag(v);
updateContinuousShadowDrawing();
if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 599da3b..f1d9549 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -165,15 +165,15 @@
if (menuRow.isSnappedAndOnSameSide()) {
// Menu was snapped to previously and we're on the same side
- handleSwipeFromSnap(ev, animView, velocity, menuRow);
+ handleSwipeFromOpenState(ev, animView, velocity, menuRow);
} else {
// Menu has not been snapped, or was snapped previously but is now on
// the opposite side.
- handleSwipeFromNonSnap(ev, animView, velocity, menuRow);
+ handleSwipeFromClosedState(ev, animView, velocity, menuRow);
}
}
- private void handleSwipeFromNonSnap(MotionEvent ev, View animView, float velocity,
+ private void handleSwipeFromClosedState(MotionEvent ev, View animView, float velocity,
NotificationMenuRowPlugin menuRow) {
boolean isDismissGesture = isDismissGesture(ev);
final boolean gestureTowardsMenu = menuRow.isTowardsMenu(velocity);
@@ -183,10 +183,14 @@
final boolean showMenuForSlowOnGoing = !menuRow.canBeDismissed()
&& timeForGesture >= SWIPE_MENU_TIMING;
- if (!isFalseGesture(ev)
- && (swipedEnoughToShowMenu(menuRow)
- && (!gestureFastEnough || showMenuForSlowOnGoing))
- || (gestureTowardsMenu && !isDismissGesture)) {
+ boolean isNonDismissGestureTowardsMenu = gestureTowardsMenu && !isDismissGesture;
+ boolean isSlowSwipe = !gestureFastEnough || showMenuForSlowOnGoing;
+ boolean slowSwipedFarEnough = swipedEnoughToShowMenu(menuRow) && isSlowSwipe;
+ boolean isFastNonDismissGesture =
+ gestureFastEnough && !gestureTowardsMenu && !isDismissGesture;
+ boolean isMenuRevealingGestureAwayFromMenu = slowSwipedFarEnough || isFastNonDismissGesture;
+ if (isNonDismissGestureTowardsMenu
+ || (!isFalseGesture(ev) && isMenuRevealingGestureAwayFromMenu)) {
// Menu has not been snapped to previously and this is menu revealing gesture
snapOpen(animView, menuRow.getMenuSnapTarget(), velocity);
menuRow.onSnapOpen();
@@ -199,7 +203,7 @@
}
}
- private void handleSwipeFromSnap(MotionEvent ev, View animView, float velocity,
+ private void handleSwipeFromOpenState(MotionEvent ev, View animView, float velocity,
NotificationMenuRowPlugin menuRow) {
boolean isDismissGesture = isDismissGesture(ev);
@@ -227,7 +231,6 @@
if (mCallback.isExpanded()) {
// We don't want to quick-dismiss when it's a heads up as this might lead to closing
// of the panel early.
- mSwipingInProgress = false;
mCallback.handleChildViewDismissed(view);
}
mCallback.onDismiss();
@@ -247,7 +250,6 @@
@Override
public void snapChild(final View animView, final float targetLeft, float velocity) {
superSnapChild(animView, targetLeft, velocity);
- mSwipingInProgress = false;
mCallback.onDragCancelled(animView);
if (targetLeft == 0) {
handleMenuCoveredOrDismissed();
@@ -354,7 +356,6 @@
public void onMenuShown(View animView) {
setExposedMenuView(getTranslatingParentView());
- mSwipingInProgress = false;
mCallback.onDragCancelled(animView);
Handler handler = getHandler();
@@ -422,4 +423,4 @@
void onDismiss();
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt
index 7204d31..b23f667 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt
@@ -27,55 +27,28 @@
@SmallTest
class PrivacyDialogBuilderTest : SysuiTestCase() {
- companion object {
- val MILLIS_IN_MINUTE: Long = 1000 * 60
- val NOW = 4 * MILLIS_IN_MINUTE
- }
-
@Test
- fun testGenerateText_multipleApps() {
+ fun testGenerateAppsList() {
val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
- "Bar", context), 2 * MILLIS_IN_MINUTE)
+ "Bar", context))
val bar3 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication(
- "Bar", context), 3 * MILLIS_IN_MINUTE)
+ "Bar", context))
val foo0 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
- "Foo", context), 0)
+ "Foo", context))
val baz1 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
- "Baz", context), 1 * MILLIS_IN_MINUTE)
+ "Baz", context))
val items = listOf(bar2, foo0, baz1, bar3)
val textBuilder = PrivacyDialogBuilder(context, items)
- val textList = textBuilder.generateText(NOW)
- assertEquals(2, textList.size)
- assertEquals("Bar, Foo, Baz are using your camera", textList[0])
- assertEquals("Bar is using your location for the last 1 min", textList[1])
- }
-
- @Test
- fun testGenerateText_singleApp() {
- val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
- "Bar", context), 0)
- val bar1 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication(
- "Bar", context), 0)
-
- val items = listOf(bar2, bar1)
-
- val textBuilder = PrivacyDialogBuilder(context, items)
- val textList = textBuilder.generateText(NOW)
- assertEquals(1, textList.size)
- assertEquals("Bar is using your camera, location", textList[0])
- }
-
- @Test
- fun testGenerateText_singleApp_singleType() {
- val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
- "Bar", context), 2 * MILLIS_IN_MINUTE)
- val items = listOf(bar2)
- val textBuilder = PrivacyDialogBuilder(context, items)
- val textList = textBuilder.generateText(NOW)
- assertEquals(1, textList.size)
- assertEquals("Bar is using your camera for the last 2 min", textList[0])
+ val list = textBuilder.appsAndTypes
+ assertEquals(3, list.size)
+ val appsList = list.map { it.first }
+ val typesList = list.map { it.second }
+ assertEquals(listOf("Bar", "Baz", "Foo"), appsList.map { it.packageName })
+ assertEquals(listOf(Privacy.TYPE_CAMERA, Privacy.TYPE_LOCATION), typesList[0])
+ assertEquals(listOf(Privacy.TYPE_CAMERA), typesList[1])
+ assertEquals(listOf(Privacy.TYPE_CAMERA), typesList[2])
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
index 8e6bfe3..f59bfae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java
@@ -72,6 +72,8 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -390,10 +392,16 @@
@Test
public void testCreateNotificationDataEntry_RankingUpdate() {
Ranking ranking = mock(Ranking.class);
+ initStatusBarNotification(false);
- ArrayList<Notification.Action> smartActions = new ArrayList<>();
- smartActions.add(createAction());
- when(ranking.getSmartActions()).thenReturn(smartActions);
+ List<Notification.Action> appGeneratedSmartActions =
+ Collections.singletonList(createContextualAction("appGeneratedAction"));
+ mMockStatusBarNotification.getNotification().actions =
+ appGeneratedSmartActions.toArray(new Notification.Action[0]);
+
+ List<Notification.Action> systemGeneratedSmartActions =
+ Collections.singletonList(createAction("systemGeneratedAction"));
+ when(ranking.getSmartActions()).thenReturn(systemGeneratedSmartActions);
when(ranking.getChannel()).thenReturn(NOTIFICATION_CHANNEL);
@@ -407,7 +415,7 @@
NotificationData.Entry entry =
new NotificationData.Entry(mMockStatusBarNotification, ranking);
- assertEquals(smartActions, entry.smartActions);
+ assertEquals(systemGeneratedSmartActions, entry.systemGeneratedSmartActions);
assertEquals(NOTIFICATION_CHANNEL, entry.channel);
assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.userSentiment);
assertEquals(snoozeCriterions, entry.snoozeCriteria);
@@ -459,10 +467,20 @@
}
}
- private Notification.Action createAction() {
+ private Notification.Action createContextualAction(String title) {
return new Notification.Action.Builder(
Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
- "action",
+ title,
+ PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0))
+ .setSemanticAction(
+ Notification.Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION)
+ .build();
+ }
+
+ private Notification.Action createAction(String title) {
+ return new Notification.Action.Builder(
+ Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ title,
PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 9f8a5cc..d1fe5af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -438,8 +438,8 @@
mEntryManager.updateNotificationRanking(mRankingMap);
verify(mRow).setEntry(eq(mEntry));
- assertEquals(1, mEntry.smartActions.size());
- assertEquals("action", mEntry.smartActions.get(0).title);
+ assertEquals(1, mEntry.systemGeneratedSmartActions.size());
+ assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
}
@Test
@@ -453,7 +453,7 @@
mEntryManager.updateNotificationRanking(mRankingMap);
verify(mRow, never()).setEntry(eq(mEntry));
- assertEquals(0, mEntry.smartActions.size());
+ assertEquals(0, mEntry.systemGeneratedSmartActions.size());
}
@Test
@@ -467,8 +467,8 @@
mEntryManager.updateNotificationRanking(mRankingMap);
verify(mRow, never()).setEntry(eq(mEntry));
- assertEquals(1, mEntry.smartActions.size());
- assertEquals("action", mEntry.smartActions.get(0).title);
+ assertEquals(1, mEntry.systemGeneratedSmartActions.size());
+ assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
}
@Test
@@ -482,8 +482,8 @@
mEntryManager.updateNotificationRanking(mRankingMap);
verify(mRow, never()).setEntry(eq(mEntry));
- assertEquals(1, mEntry.smartActions.size());
- assertEquals("action", mEntry.smartActions.get(0).title);
+ assertEquals(1, mEntry.systemGeneratedSmartActions.size());
+ assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
}
private Notification.Action createAction() {
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 1ad83ec..c12a5e7 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -104,7 +104,6 @@
import com.android.server.backup.fullbackup.FullBackupEntry;
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.internal.BackupHandler;
-import com.android.server.backup.keyvalue.BackupRequest;
import com.android.server.backup.internal.ClearDataObserver;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
@@ -112,6 +111,7 @@
import com.android.server.backup.internal.ProvisionedObserver;
import com.android.server.backup.internal.RunBackupReceiver;
import com.android.server.backup.internal.RunInitializeReceiver;
+import com.android.server.backup.keyvalue.BackupRequest;
import com.android.server.backup.params.AdbBackupParams;
import com.android.server.backup.params.AdbParams;
import com.android.server.backup.params.AdbRestoreParams;
@@ -160,7 +160,6 @@
import java.util.concurrent.atomic.AtomicInteger;
public class BackupManagerService {
-
public static final String TAG = "BackupManagerService";
public static final boolean DEBUG = true;
public static final boolean MORE_DEBUG = false;
@@ -170,6 +169,9 @@
// nonzero == enabled. File missing or contains a zero byte == disabled.
private static final String BACKUP_ENABLE_FILE = "backup_enabled";
+ // Persistently track the need to do a full init.
+ private static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
+
// System-private key used for backing up an app's widget state. Must
// begin with U+FFxx by convention (we reserve all keys starting
// with U+FF00 or higher for system use).
@@ -196,11 +198,16 @@
public static final int BACKUP_METADATA_VERSION = 1;
public static final int BACKUP_WIDGET_METADATA_TOKEN = 0x01FFED01;
- private static final boolean COMPRESS_FULL_BACKUPS = true; // should be true in production
+ private static final int CURRENT_ANCESTRAL_RECORD_VERSION = 1;
+
+ // Round-robin queue for scheduling full backup passes.
+ private static final int SCHEDULE_FILE_VERSION = 1;
public static final String SETTINGS_PACKAGE = "com.android.providers.settings";
public static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
- private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
+
+ // Pseudoname that we use for the Package Manager metadata "package".
+ public static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
// Retry interval for clear/init when the transport is unavailable
private static final long TRANSPORT_RETRY_INTERVAL = 1 * AlarmManager.INTERVAL_HOUR;
@@ -210,6 +217,21 @@
public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
+ // Bookkeeping of in-flight operations. The operation token is the index of the entry in the
+ // pending operations list.
+ public static final int OP_PENDING = 0;
+ private static final int OP_ACKNOWLEDGED = 1;
+ private static final int OP_TIMEOUT = -1;
+
+ // Waiting for backup agent to respond during backup operation.
+ public static final int OP_TYPE_BACKUP_WAIT = 0;
+
+ // Waiting for backup agent to respond during restore operation.
+ public static final int OP_TYPE_RESTORE_WAIT = 1;
+
+ // An entire backup operation spanning multiple packages.
+ public static final int OP_TYPE_BACKUP = 2;
+
// Time delay for initialization operations that can be delayed so as not to consume too much CPU
// on bring-up and increase time-to-UI.
private static final long INITIALIZATION_DELAY_MILLIS = 3000;
@@ -226,8 +248,62 @@
private static final long BUSY_BACKOFF_MIN_MILLIS = 1000 * 60 * 60; // one hour
private static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2; // two hours
- private BackupManagerConstants mConstants;
+ // The published binder is a singleton Trampoline object that calls through to the proper code.
+ // This indirection lets us turn down the heavy implementation object on the fly without
+ // disturbing binders that have been cached elsewhere in the system.
+ private static Trampoline sInstance;
+
+ static Trampoline getInstance() {
+ // Always constructed during system bring up, so no need to lazy-init.
+ return sInstance;
+ }
+
+ /** Helper to create the {@link BackupManagerService} instance. */
+ public static BackupManagerService create(
+ Context context,
+ Trampoline parent,
+ HandlerThread backupThread) {
+ // Set up our transport options and initialize the default transport
+ SystemConfig systemConfig = SystemConfig.getInstance();
+ Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
+ if (transportWhitelist == null) {
+ transportWhitelist = Collections.emptySet();
+ }
+
+ String transport =
+ Settings.Secure.getString(
+ context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
+ if (TextUtils.isEmpty(transport)) {
+ transport = null;
+ }
+ if (DEBUG) {
+ Slog.v(TAG, "Starting with transport " + transport);
+ }
+ TransportManager transportManager =
+ new TransportManager(
+ context,
+ transportWhitelist,
+ transport);
+
+ // If encrypted file systems is enabled or disabled, this call will return the
+ // correct directory.
+ File baseStateDir = new File(Environment.getDataDirectory(), "backup");
+
+ // This dir on /cache is managed directly in init.rc
+ File dataDir = new File(Environment.getDownloadCacheDirectory(), "backup_stage");
+
+ return new BackupManagerService(
+ context,
+ parent,
+ backupThread,
+ baseStateDir,
+ dataDir,
+ transportManager);
+ }
+
private final BackupAgentTimeoutParameters mAgentTimeoutParameters;
+ private final TransportManager mTransportManager;
+
private Context mContext;
private PackageManager mPackageManager;
private IPackageManager mPackageManagerBinder;
@@ -235,20 +311,21 @@
private PowerManager mPowerManager;
private AlarmManager mAlarmManager;
private IStorageManager mStorageManager;
+ private BackupManagerConstants mConstants;
+ private PowerManager.WakeLock mWakelock;
+ private BackupHandler mBackupHandler;
private IBackupManager mBackupManagerBinder;
- private final TransportManager mTransportManager;
-
private boolean mEnabled; // access to this is synchronized on 'this'
private boolean mProvisioned;
private boolean mAutoRestore;
- private PowerManager.WakeLock mWakelock;
- private BackupHandler mBackupHandler;
+
private PendingIntent mRunBackupIntent;
private PendingIntent mRunInitIntent;
- private BroadcastReceiver mRunBackupReceiver;
- private BroadcastReceiver mRunInitReceiver;
+
+ private final ArraySet<String> mPendingInits = new ArraySet<>(); // transport names
+
// map UIDs to the set of participating packages under that UID
private final SparseArray<HashSet<String>> mBackupParticipants
= new SparseArray<>();
@@ -257,9 +334,6 @@
private HashMap<String, BackupRequest> mPendingBackups
= new HashMap<>();
- // Pseudoname that we use for the Package Manager metadata "package"
- public static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
-
// locking around the pending-backup management
private final Object mQueueLock = new Object();
@@ -269,25 +343,32 @@
// completed.
private final Object mAgentConnectLock = new Object();
private IBackupAgent mConnectedAgent;
- private volatile boolean mBackupRunning;
private volatile boolean mConnecting;
- private volatile long mLastBackupPass;
- // For debugging, we maintain a progress trace of operations during backup
- public static final boolean DEBUG_BACKUP_TRACE = true;
- private final List<String> mBackupTrace = new ArrayList<>();
+ private volatile boolean mBackupRunning;
+ private volatile long mLastBackupPass;
// A similar synchronization mechanism around clearing apps' data for restore
private final Object mClearDataLock = new Object();
private volatile boolean mClearingData;
+ // Used by ADB.
private final BackupPasswordManager mBackupPasswordManager;
+ private final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<>();
+ private final SecureRandom mRng = new SecureRandom();
// Time when we post the transport registration operation
private final long mRegisterTransportsRequestedTime;
+ @GuardedBy("mQueueLock")
+ private PerformFullTransportBackupTask mRunningFullBackupTask;
+
+ @GuardedBy("mQueueLock")
+ private ArrayList<FullBackupEntry> mFullBackupQueue;
+
@GuardedBy("mPendingRestores")
private boolean mIsRestoreInProgress;
+
@GuardedBy("mPendingRestores")
private final Queue<PerformUnifiedRestoreTask> mPendingRestores = new ArrayDeque<>();
@@ -296,17 +377,155 @@
// Watch the device provisioning operation during setup
private ContentObserver mProvisionedObserver;
- // The published binder is actually to a singleton trampoline object that calls
- // through to the proper code. This indirection lets us turn down the heavy
- // implementation object on the fly without disturbing binders that have been
- // cached elsewhere in the system.
- static Trampoline sInstance;
+ /**
+ * mCurrentOperations contains the list of currently active operations.
+ *
+ * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
+ * An operation wraps a BackupRestoreTask within it.
+ * It's the responsibility of this task to remove the operation from this array.
+ *
+ * A BackupRestore task gets notified of ack/timeout for the operation via
+ * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
+ * on the mCurrentOpLock.
+ * {@link BackupManagerService#waitUntilOperationComplete(int)} is
+ * used in various places to 'wait' for notifyAll and detect change of pending state of an
+ * operation. So typically, an operation will be removed from this array by:
+ * - BackupRestoreTask#handleCancel and
+ * - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
+ * these places because waitUntilOperationComplete relies on the operation being present to
+ * determine its completion status.
+ *
+ * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
+ * cancel backup tasks.
+ */
+ @GuardedBy("mCurrentOpLock")
+ private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
+ private final Object mCurrentOpLock = new Object();
+ private final Random mTokenGenerator = new Random();
+ final AtomicInteger mNextToken = new AtomicInteger();
- static Trampoline getInstance() {
- // Always constructed during system bringup, so no need to lazy-init
- return sInstance;
+ // Where we keep our journal files and other bookkeeping.
+ private File mBaseStateDir;
+ private File mDataDir;
+ private File mJournalDir;
+ @Nullable
+ private DataChangedJournal mJournal;
+ private File mFullBackupScheduleFile;
+
+ // Keep a log of all the apps we've ever backed up.
+ private ProcessedPackagesJournal mProcessedPackagesJournal;
+
+ private File mTokenFile;
+ private Set<String> mAncestralPackages = null;
+ private long mAncestralToken = 0;
+ private long mCurrentToken = 0;
+
+ @VisibleForTesting
+ public BackupManagerService(
+ Context context,
+ Trampoline parent,
+ HandlerThread backupThread,
+ File baseStateDir,
+ File dataDir,
+ TransportManager transportManager) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mPackageManagerBinder = AppGlobals.getPackageManager();
+ mActivityManager = ActivityManager.getService();
+
+ mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
+
+ mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
+
+ mAgentTimeoutParameters = new
+ BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver());
+ mAgentTimeoutParameters.start();
+
+ // spin up the backup/restore handler thread
+ mBackupHandler = new BackupHandler(this, backupThread.getLooper());
+
+ // Set up our bookkeeping
+ final ContentResolver resolver = context.getContentResolver();
+ mProvisioned = Settings.Global.getInt(resolver,
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ mAutoRestore = Settings.Secure.getInt(resolver,
+ Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0;
+
+ mProvisionedObserver = new ProvisionedObserver(this, mBackupHandler);
+ resolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ false, mProvisionedObserver);
+
+ mBaseStateDir = baseStateDir;
+ mBaseStateDir.mkdirs();
+ if (!SELinux.restorecon(mBaseStateDir)) {
+ Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir);
+ }
+
+ mDataDir = dataDir;
+
+ mBackupPasswordManager = new BackupPasswordManager(mContext, mBaseStateDir, mRng);
+
+ // Alarm receivers for scheduled backups & initialization operations
+ BroadcastReceiver mRunBackupReceiver = new RunBackupReceiver(this);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(RUN_BACKUP_ACTION);
+ context.registerReceiver(mRunBackupReceiver, filter,
+ android.Manifest.permission.BACKUP, null);
+
+ BroadcastReceiver mRunInitReceiver = new RunInitializeReceiver(this);
+ filter = new IntentFilter();
+ filter.addAction(RUN_INITIALIZE_ACTION);
+ context.registerReceiver(mRunInitReceiver, filter,
+ android.Manifest.permission.BACKUP, null);
+
+ Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
+ backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mRunBackupIntent = PendingIntent.getBroadcast(context, 0, backupIntent, 0);
+
+ Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
+ initIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mRunInitIntent = PendingIntent.getBroadcast(context, 0, initIntent, 0);
+
+ // Set up the backup-request journaling
+ mJournalDir = new File(mBaseStateDir, "pending");
+ mJournalDir.mkdirs(); // creates mBaseStateDir along the way
+ mJournal = null; // will be created on first use
+
+ mConstants = new BackupManagerConstants(mBackupHandler, mContext.getContentResolver());
+ // We are observing changes to the constants throughout the lifecycle of BMS. This is
+ // because we reference the constants in multiple areas of BMS, which otherwise would
+ // require frequent starting and stopping.
+ mConstants.start();
+
+ // Set up the various sorts of package tracking we do
+ mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule");
+ initPackageTracking();
+
+ // Build our mapping of uid to backup client services. This implicitly
+ // schedules a backup pass on the Package Manager metadata the first
+ // time anything needs to be backed up.
+ synchronized (mBackupParticipants) {
+ addPackageParticipantsLocked(null);
+ }
+
+ mTransportManager = transportManager;
+ mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered);
+ mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime();
+ mBackupHandler.postDelayed(
+ mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS);
+
+ // Now that we know about valid backup participants, parse any leftover journal files into
+ // the pending backup set
+ mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
+
+ // Power management
+ mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
}
+
public BackupManagerConstants getConstants() {
return mConstants;
}
@@ -549,6 +768,7 @@
return mPendingInits;
}
+ /** Clear all pending transport initializations. */
public void clearPendingInits() {
mPendingInits.clear();
}
@@ -562,28 +782,10 @@
mRunningFullBackupTask = runningFullBackupTask;
}
- public static final class Lifecycle extends SystemService {
-
- public Lifecycle(Context context) {
- super(context);
- sInstance = new Trampoline(context);
- }
-
- @Override
- public void onStart() {
- publishBinderService(Context.BACKUP_SERVICE, sInstance);
- }
-
- @Override
- public void onUnlockUser(int userId) {
- if (userId == UserHandle.USER_SYSTEM) {
- sInstance.unlockSystemUser();
- }
- }
- }
-
- // Called through the trampoline from onUnlockUser(), then we buck the work
- // off to the background thread to keep the unlock time down.
+ /**
+ * Called through Trampoline from {@link Lifecycle#onUnlockUser(int)}. We run the heavy work on
+ * a background thread to keep the unlock time down.
+ */
public void unlockSystemUser() {
// Migrate legacy setting
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
@@ -618,89 +820,10 @@
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
- // Bookkeeping of in-flight operations for timeout etc. purposes. The operation
- // token is the index of the entry in the pending-operations list.
- public static final int OP_PENDING = 0;
- private static final int OP_ACKNOWLEDGED = 1;
- private static final int OP_TIMEOUT = -1;
-
- // Waiting for backup agent to respond during backup operation.
- public static final int OP_TYPE_BACKUP_WAIT = 0;
-
- // Waiting for backup agent to respond during restore operation.
- public static final int OP_TYPE_RESTORE_WAIT = 1;
-
- // An entire backup operation spanning multiple packages.
- public static final int OP_TYPE_BACKUP = 2;
-
/**
- * mCurrentOperations contains the list of currently active operations.
- *
- * If type of operation is OP_TYPE_WAIT, it are waiting for an ack or timeout.
- * An operation wraps a BackupRestoreTask within it.
- * It's the responsibility of this task to remove the operation from this array.
- *
- * A BackupRestore task gets notified of ack/timeout for the operation via
- * BackupRestoreTask#handleCancel, BackupRestoreTask#operationComplete and notifyAll called
- * on the mCurrentOpLock.
- * {@link BackupManagerService#waitUntilOperationComplete(int)} is
- * used in various places to 'wait' for notifyAll and detect change of pending state of an
- * operation. So typically, an operation will be removed from this array by:
- * - BackupRestoreTask#handleCancel and
- * - BackupRestoreTask#operationComplete OR waitUntilOperationComplete. Do not remove at both
- * these places because waitUntilOperationComplete relies on the operation being present to
- * determine its completion status.
- *
- * If type of operation is OP_BACKUP, it is a task running backups. It provides a handle to
- * cancel backup tasks.
+ * Utility: build a new random integer token. The low bits are the ordinal of the operation for
+ * near-time uniqueness, and the upper bits are random for app-side unpredictability.
*/
- @GuardedBy("mCurrentOpLock")
- private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
- private final Object mCurrentOpLock = new Object();
- private final Random mTokenGenerator = new Random();
- final AtomicInteger mNextToken = new AtomicInteger();
-
- private final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<>();
-
- // Where we keep our journal files and other bookkeeping
- private File mBaseStateDir;
- private File mDataDir;
- private File mJournalDir;
- @Nullable
- private DataChangedJournal mJournal;
-
- private final SecureRandom mRng = new SecureRandom();
-
- // Keep a log of all the apps we've ever backed up, and what the dataset tokens are for both
- // the current backup dataset and the ancestral dataset.
- private ProcessedPackagesJournal mProcessedPackagesJournal;
-
- private static final int CURRENT_ANCESTRAL_RECORD_VERSION = 1;
- // increment when the schema changes
- private File mTokenFile;
- private Set<String> mAncestralPackages = null;
- private long mAncestralToken = 0;
- private long mCurrentToken = 0;
-
- // Persistently track the need to do a full init
- private static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
- private final ArraySet<String> mPendingInits = new ArraySet<>(); // transport names
-
- // Round-robin queue for scheduling full backup passes
- private static final int SCHEDULE_FILE_VERSION = 1; // current version of the schedule file
-
- private File mFullBackupScheduleFile;
- // If we're running a schedule-driven full backup, this is the task instance doing it
-
- @GuardedBy("mQueueLock")
- private PerformFullTransportBackupTask mRunningFullBackupTask;
-
- @GuardedBy("mQueueLock")
- private ArrayList<FullBackupEntry> mFullBackupQueue;
-
- // Utility: build a new random integer token. The low bits are the ordinal of the
- // operation for near-time uniqueness, and the upper bits are random for app-
- // side unpredictability.
public int generateRandomIntegerToken() {
int token = mTokenGenerator.nextInt();
if (token < 0) token = -token;
@@ -709,10 +832,9 @@
return token;
}
- /*
- * Construct a backup agent instance for the metadata pseudopackage. This is a
- * process-local non-lifecycle agent instance, so we manually set up the context
- * topology for it.
+ /**
+ * Construct a backup agent instance for the metadata pseudopackage. This is a process-local
+ * non-lifecycle agent instance, so we manually set up the context topology for it.
*/
public BackupAgent makeMetadataAgent() {
PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager);
@@ -721,8 +843,8 @@
return pmAgent;
}
- /*
- * Same as above but with the explicit package-set configuration.
+ /**
+ * Same as {@link #makeMetadataAgent()} but with explicit package-set configuration.
*/
public PackageManagerBackupAgent makeMetadataAgent(List<PackageInfo> packages) {
PackageManagerBackupAgent pmAgent =
@@ -732,172 +854,6 @@
return pmAgent;
}
- // ----- Debug-only backup operation trace -----
- public void addBackupTrace(String s) {
- if (DEBUG_BACKUP_TRACE) {
- synchronized (mBackupTrace) {
- mBackupTrace.add(s);
- }
- }
- }
-
- public void clearBackupTrace() {
- if (DEBUG_BACKUP_TRACE) {
- synchronized (mBackupTrace) {
- mBackupTrace.clear();
- }
- }
- }
-
- // ----- Main service implementation -----
-
- public static BackupManagerService create(
- Context context,
- Trampoline parent,
- HandlerThread backupThread) {
- // Set up our transport options and initialize the default transport
- SystemConfig systemConfig = SystemConfig.getInstance();
- Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
- if (transportWhitelist == null) {
- transportWhitelist = Collections.emptySet();
- }
-
- String transport =
- Settings.Secure.getString(
- context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
- if (TextUtils.isEmpty(transport)) {
- transport = null;
- }
- if (DEBUG) {
- Slog.v(TAG, "Starting with transport " + transport);
- }
- TransportManager transportManager =
- new TransportManager(
- context,
- transportWhitelist,
- transport);
-
- // If encrypted file systems is enabled or disabled, this call will return the
- // correct directory.
- File baseStateDir = new File(Environment.getDataDirectory(), "backup");
-
- // This dir on /cache is managed directly in init.rc
- File dataDir = new File(Environment.getDownloadCacheDirectory(), "backup_stage");
-
- return new BackupManagerService(
- context,
- parent,
- backupThread,
- baseStateDir,
- dataDir,
- transportManager);
- }
-
- @VisibleForTesting
- public BackupManagerService(
- Context context,
- Trampoline parent,
- HandlerThread backupThread,
- File baseStateDir,
- File dataDir,
- TransportManager transportManager) {
- mContext = context;
- mPackageManager = context.getPackageManager();
- mPackageManagerBinder = AppGlobals.getPackageManager();
- mActivityManager = ActivityManager.getService();
-
- mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- mStorageManager = IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
-
- mBackupManagerBinder = Trampoline.asInterface(parent.asBinder());
-
- mAgentTimeoutParameters = new
- BackupAgentTimeoutParameters(Handler.getMain(), mContext.getContentResolver());
- mAgentTimeoutParameters.start();
-
- // spin up the backup/restore handler thread
- mBackupHandler = new BackupHandler(this, backupThread.getLooper());
-
- // Set up our bookkeeping
- final ContentResolver resolver = context.getContentResolver();
- mProvisioned = Settings.Global.getInt(resolver,
- Settings.Global.DEVICE_PROVISIONED, 0) != 0;
- mAutoRestore = Settings.Secure.getInt(resolver,
- Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0;
-
- mProvisionedObserver = new ProvisionedObserver(this, mBackupHandler);
- resolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
- false, mProvisionedObserver);
-
- mBaseStateDir = baseStateDir;
- mBaseStateDir.mkdirs();
- if (!SELinux.restorecon(mBaseStateDir)) {
- Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir);
- }
-
- mDataDir = dataDir;
-
- mBackupPasswordManager = new BackupPasswordManager(mContext, mBaseStateDir, mRng);
-
- // Alarm receivers for scheduled backups & initialization operations
- mRunBackupReceiver = new RunBackupReceiver(this);
- IntentFilter filter = new IntentFilter();
- filter.addAction(RUN_BACKUP_ACTION);
- context.registerReceiver(mRunBackupReceiver, filter,
- android.Manifest.permission.BACKUP, null);
-
- mRunInitReceiver = new RunInitializeReceiver(this);
- filter = new IntentFilter();
- filter.addAction(RUN_INITIALIZE_ACTION);
- context.registerReceiver(mRunInitReceiver, filter,
- android.Manifest.permission.BACKUP, null);
-
- Intent backupIntent = new Intent(RUN_BACKUP_ACTION);
- backupIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mRunBackupIntent = PendingIntent.getBroadcast(context, 0, backupIntent, 0);
-
- Intent initIntent = new Intent(RUN_INITIALIZE_ACTION);
- initIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mRunInitIntent = PendingIntent.getBroadcast(context, 0, initIntent, 0);
-
- // Set up the backup-request journaling
- mJournalDir = new File(mBaseStateDir, "pending");
- mJournalDir.mkdirs(); // creates mBaseStateDir along the way
- mJournal = null; // will be created on first use
-
- mConstants = new BackupManagerConstants(mBackupHandler, mContext.getContentResolver());
- // We are observing changes to the constants throughout the lifecycle of BMS. This is
- // because we reference the constants in multiple areas of BMS, which otherwise would
- // require frequent starting and stopping.
- mConstants.start();
-
- // Set up the various sorts of package tracking we do
- mFullBackupScheduleFile = new File(mBaseStateDir, "fb-schedule");
- initPackageTracking();
-
- // Build our mapping of uid to backup client services. This implicitly
- // schedules a backup pass on the Package Manager metadata the first
- // time anything needs to be backed up.
- synchronized (mBackupParticipants) {
- addPackageParticipantsLocked(null);
- }
-
- mTransportManager = transportManager;
- mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered);
- mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime();
- mBackupHandler.postDelayed(
- mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS);
-
- // Now that we know about valid backup participants, parse any leftover journal files into
- // the pending backup set
- mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
-
- // Power management
- mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
- }
-
private void initPackageTracking() {
if (MORE_DEBUG) Slog.v(TAG, "` tracking");
@@ -2750,54 +2706,6 @@
}
}
- private static boolean backupSettingMigrated(int userId) {
- File base = new File(Environment.getDataDirectory(), "backup");
- File enableFile = new File(base, BACKUP_ENABLE_FILE);
- return enableFile.exists();
- }
-
- private static boolean readBackupEnableState(int userId) {
- File base = new File(Environment.getDataDirectory(), "backup");
- File enableFile = new File(base, BACKUP_ENABLE_FILE);
- if (enableFile.exists()) {
- try (FileInputStream fin = new FileInputStream(enableFile)) {
- int state = fin.read();
- return state != 0;
- } catch (IOException e) {
- // can't read the file; fall through to assume disabled
- Slog.e(TAG, "Cannot read enable state; assuming disabled");
- }
- } else {
- if (DEBUG) {
- Slog.i(TAG, "isBackupEnabled() => false due to absent settings file");
- }
- }
- return false;
- }
-
- private static void writeBackupEnableState(boolean enable, int userId) {
- File base = new File(Environment.getDataDirectory(), "backup");
- File enableFile = new File(base, BACKUP_ENABLE_FILE);
- File stage = new File(base, BACKUP_ENABLE_FILE + "-stage");
- try (FileOutputStream fout = new FileOutputStream(stage)) {
- fout.write(enable ? 1 : 0);
- fout.close();
- stage.renameTo(enableFile);
- // will be synced immediately by the try-with-resources call to close()
- } catch (IOException | RuntimeException e) {
- // Whoops; looks like we're doomed. Roll everything out, disabled,
- // including the legacy state.
- Slog.e(TAG, "Unable to record backup enable state; reverting to disabled: "
- + e.getMessage());
-
- ContentResolver resolver = sInstance.getContext().getContentResolver();
- Settings.Secure.putStringForUser(resolver,
- Settings.Secure.BACKUP_ENABLED, null, userId);
- enableFile.delete();
- stage.delete();
- }
- }
-
// Enable/disable backups
public void setBackupEnabled(boolean enable) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
@@ -3565,17 +3473,6 @@
pw.println(" " + s);
}
- if (DEBUG_BACKUP_TRACE) {
- synchronized (mBackupTrace) {
- if (!mBackupTrace.isEmpty()) {
- pw.println("Most recent backup trace:");
- for (String s : mBackupTrace) {
- pw.println(" " + s);
- }
- }
- }
- }
-
pw.print("Ancestral: ");
pw.println(Long.toHexString(mAncestralToken));
pw.print("Current: ");
@@ -3627,4 +3524,71 @@
return mBackupManagerBinder;
}
+ private static boolean backupSettingMigrated(int userId) {
+ File base = new File(Environment.getDataDirectory(), "backup");
+ File enableFile = new File(base, BACKUP_ENABLE_FILE);
+ return enableFile.exists();
+ }
+
+ private static boolean readBackupEnableState(int userId) {
+ File base = new File(Environment.getDataDirectory(), "backup");
+ File enableFile = new File(base, BACKUP_ENABLE_FILE);
+ if (enableFile.exists()) {
+ try (FileInputStream fin = new FileInputStream(enableFile)) {
+ int state = fin.read();
+ return state != 0;
+ } catch (IOException e) {
+ // can't read the file; fall through to assume disabled
+ Slog.e(TAG, "Cannot read enable state; assuming disabled");
+ }
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "isBackupEnabled() => false due to absent settings file");
+ }
+ }
+ return false;
+ }
+
+ private static void writeBackupEnableState(boolean enable, int userId) {
+ File base = new File(Environment.getDataDirectory(), "backup");
+ File enableFile = new File(base, BACKUP_ENABLE_FILE);
+ File stage = new File(base, BACKUP_ENABLE_FILE + "-stage");
+ try (FileOutputStream fout = new FileOutputStream(stage)) {
+ fout.write(enable ? 1 : 0);
+ fout.close();
+ stage.renameTo(enableFile);
+ // will be synced immediately by the try-with-resources call to close()
+ } catch (IOException | RuntimeException e) {
+ // Whoops; looks like we're doomed. Roll everything out, disabled,
+ // including the legacy state.
+ Slog.e(TAG, "Unable to record backup enable state; reverting to disabled: "
+ + e.getMessage());
+
+ ContentResolver resolver = sInstance.getContext().getContentResolver();
+ Settings.Secure.putStringForUser(resolver,
+ Settings.Secure.BACKUP_ENABLED, null, userId);
+ enableFile.delete();
+ stage.delete();
+ }
+ }
+
+ /** Implementation to receive lifecycle event callbacks for system services. */
+ public static final class Lifecycle extends SystemService {
+ public Lifecycle(Context context) {
+ super(context);
+ sInstance = new Trampoline(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.BACKUP_SERVICE, sInstance);
+ }
+
+ @Override
+ public void onUnlockUser(int userId) {
+ if (userId == UserHandle.USER_SYSTEM) {
+ sInstance.unlockSystemUser();
+ }
+ }
+ }
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index e108026..755095e 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -45,9 +45,9 @@
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.BackupManagerService;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.FullBackupJob;
-import com.android.server.backup.BackupManagerService;
import com.android.server.backup.TransportManager;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.internal.Operation;
@@ -599,7 +599,6 @@
cleanUpPipes(enginePipes);
if (currentPackage.applicationInfo != null) {
Slog.i(TAG, "Unbinding agent in " + packageName);
- backupManagerService.addBackupTrace("unbinding " + packageName);
try {
backupManagerService.getActivityManager().unbindBackupAgent(
currentPackage.applicationInfo);
@@ -709,7 +708,6 @@
try {
backupManagerService.prepareOperationTimeout(
mCurrentOpToken, fullBackupAgentTimeoutMillis, this, OP_TYPE_BACKUP_WAIT);
- backupManagerService.addBackupTrace("preflighting");
if (MORE_DEBUG) {
Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b5217ad..c660cc6 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -677,6 +677,8 @@
stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
}
r.callStart = false;
+ StatsLog.write(StatsLog.SERVICE_STATE_CHANGED, r.appInfo.uid, r.name.getPackageName(),
+ r.name.getClassName(), StatsLog.SERVICE_STATE_CHANGED__STATE__START);
synchronized (r.stats.getBatteryStats()) {
r.stats.startRunningLocked();
}
@@ -715,6 +717,9 @@
service.delayedStop = true;
return;
}
+ StatsLog.write(StatsLog.SERVICE_STATE_CHANGED, service.appInfo.uid,
+ service.name.getPackageName(), service.name.getClassName(),
+ StatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
synchronized (service.stats.getBatteryStats()) {
service.stats.stopRunningLocked();
}
@@ -856,6 +861,8 @@
}
}
+ StatsLog.write(StatsLog.SERVICE_STATE_CHANGED, r.appInfo.uid, r.name.getPackageName(),
+ r.name.getClassName(), StatsLog.SERVICE_STATE_CHANGED__STATE__STOP);
synchronized (r.stats.getBatteryStats()) {
r.stats.stopRunningLocked();
}
@@ -2517,6 +2524,8 @@
EventLogTags.writeAmCreateService(
r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
}
+ StatsLog.write(StatsLog.SERVICE_LAUNCH_REPORTED, r.appInfo.uid, r.name.getPackageName(),
+ r.name.getClassName());
synchronized (r.stats.getBatteryStats()) {
r.stats.startLaunchedLocked();
}
diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java
index 002d4e1..6612d02 100644
--- a/services/core/java/com/android/server/location/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java
@@ -40,6 +40,8 @@
* notification callbacks. This class implements the IContextHubClient object, and the implemented
* APIs must be thread-safe.
*
+ * TODO: Consider refactoring this class via inheritance
+ *
* @hide
*/
public class ContextHubClientBroker extends IContextHubClient.Stub
@@ -92,7 +94,7 @@
/*
* The PendingIntent registered with this client.
*/
- private final PendingIntentRequest mPendingIntentRequest = new PendingIntentRequest();
+ private final PendingIntentRequest mPendingIntentRequest;
/*
* Helper class to manage registered PendingIntent requests from the client.
@@ -130,41 +132,31 @@
public void clear() {
mPendingIntent = null;
}
-
- public boolean register(PendingIntent pendingIntent, long nanoAppId) {
- boolean success = false;
- if (hasPendingIntent()) {
- Log.e(TAG, "Failed to register PendingIntent: registered PendingIntent exists");
- } else {
- mNanoAppId = nanoAppId;
- mPendingIntent = pendingIntent;
- success = true;
- }
-
- return success;
- }
-
- public boolean unregister(PendingIntent pendingIntent) {
- boolean success = false;
- if (!hasPendingIntent() || !mPendingIntent.equals(pendingIntent)) {
- Log.e(TAG, "Failed to unregister PendingIntent: PendingIntent is not registered");
- } else {
- mPendingIntent = null;
- success = true;
- }
-
- return success;
- }
}
/* package */ ContextHubClientBroker(
Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
- ContextHubInfo contextHubInfo, short hostEndPointId) {
+ ContextHubInfo contextHubInfo, short hostEndPointId,
+ IContextHubClientCallback callback) {
mContext = context;
mContextHubProxy = contextHubProxy;
mClientManager = clientManager;
mAttachedContextHubInfo = contextHubInfo;
mHostEndPointId = hostEndPointId;
+ mCallbackInterface = callback;
+ mPendingIntentRequest = new PendingIntentRequest();
+ }
+
+ /* package */ ContextHubClientBroker(
+ Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
+ ContextHubInfo contextHubInfo, short hostEndPointId, PendingIntent pendingIntent,
+ long nanoAppId) {
+ mContext = context;
+ mContextHubProxy = contextHubProxy;
+ mClientManager = clientManager;
+ mAttachedContextHubInfo = contextHubInfo;
+ mHostEndPointId = hostEndPointId;
+ mPendingIntentRequest = new PendingIntentRequest(pendingIntent, nanoAppId);
}
/**
@@ -179,11 +171,7 @@
ContextHubServiceUtil.checkPermissions(mContext);
int result;
- IContextHubClientCallback callback = null;
- synchronized (this) {
- callback = mCallbackInterface;
- }
- if (callback != null) {
+ if (isRegistered()) {
ContextHubMsg messageToNanoApp =
ContextHubServiceUtil.createHidlContextHubMessage(mHostEndPointId, message);
@@ -204,64 +192,16 @@
}
/**
- * @param pendingIntent the intent to register
- * @param nanoAppId the ID of the nanoapp to send events for
- * @return true on success, false otherwise
- */
- @Override
- public boolean registerIntent(PendingIntent pendingIntent, long nanoAppId) {
- ContextHubServiceUtil.checkPermissions(mContext);
- if (mClientManager.isPendingIntentRegistered(pendingIntent)) {
- Log.e(TAG, "Failed to register PendingIntent: already registered");
- return false;
- }
-
- boolean success = false;
- synchronized (this) {
- if (mCallbackInterface == null) {
- Log.e(TAG, "Failed to register PendingIntent: client connection is closed");
- } else {
- success = mPendingIntentRequest.register(pendingIntent, nanoAppId);
- }
- }
-
- return success;
- }
-
- /**
- * @param pendingIntent the intent to unregister
- * @return true on success, false otherwise
- */
- @Override
- public boolean unregisterIntent(PendingIntent pendingIntent) {
- ContextHubServiceUtil.checkPermissions(mContext);
-
- boolean success = false;
- synchronized (this) {
- success = mPendingIntentRequest.unregister(pendingIntent);
- if (mCallbackInterface == null) {
- close();
- }
- }
-
- return success;
- }
-
- /**
* Closes the connection for this client with the service.
+ *
+ * If the client has a PendingIntent registered, this method also unregisters it.
*/
@Override
public void close() {
synchronized (this) {
- if (mCallbackInterface != null) {
- mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
- mCallbackInterface = null;
- }
- if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
- mClientManager.unregisterClient(mHostEndPointId);
- mRegistered = false;
- }
+ mPendingIntentRequest.clear();
}
+ onClientExit();
}
/**
@@ -269,38 +209,7 @@
*/
@Override
public void binderDied() {
- close();
- }
-
- /**
- * Sets the callback interface for this client, only if the callback is currently unregistered.
- *
- * Also attaches a death recipient to a ContextHubClientBroker object. If unsuccessful, the
- * connection is closed.
- *
- * @param callback the callback interface
- * @return true if the callback was successfully set, false otherwise
- *
- * @throws IllegalStateException if the client has already been registered to a callback
- */
- /* package */
- synchronized boolean setCallback(IContextHubClientCallback callback) {
- boolean success = false;
- if (mCallbackInterface != null) {
- throw new IllegalStateException("Client is already registered with a callback");
- } else {
- mCallbackInterface = callback;
- try {
- mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
- success = true;
- } catch (RemoteException e) {
- // The client process has died, so we close the connection.
- Log.e(TAG, "Failed to attach death recipient to client");
- close();
- }
- }
-
- return success;
+ onClientExit();
}
/**
@@ -375,15 +284,30 @@
}
/**
- * @param intent the PendingIntent to compare to
+ * @param intent the PendingIntent to compare to
+ * @param nanoAppId the ID of the nanoapp of the PendingIntent to compare to
* @return true if the given PendingIntent is currently registered, false otherwise
*/
- /* package */ boolean hasPendingIntent(PendingIntent intent) {
+ /* package */ boolean hasPendingIntent(PendingIntent intent, long nanoAppId) {
PendingIntent pendingIntent = null;
+ long intentNanoAppId;
synchronized (this) {
pendingIntent = mPendingIntentRequest.getPendingIntent();
+ intentNanoAppId = mPendingIntentRequest.getNanoAppId();
}
- return (pendingIntent != null) && pendingIntent.equals(intent);
+ return (pendingIntent != null) && pendingIntent.equals(intent)
+ && intentNanoAppId == nanoAppId;
+ }
+
+ /**
+ * Attaches the death recipient to the callback interface object, if any.
+ *
+ * @throws RemoteException if the client process already died
+ */
+ /* package */ void attachDeathRecipient() throws RemoteException {
+ if (mCallbackInterface != null) {
+ mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+ }
}
/**
@@ -446,11 +370,29 @@
// The PendingIntent is no longer valid
Log.w(TAG, "PendingIntent has been canceled, unregistering from client"
+ " (host endpoint ID " + mHostEndPointId + ")");
- mPendingIntentRequest.clear();
- if (mCallbackInterface == null) {
- close();
- }
+ close();
}
}
}
+
+ /**
+ * @return true if the client is still registered with the service, false otherwise
+ */
+ private synchronized boolean isRegistered() {
+ return mRegistered;
+ }
+
+ /**
+ * Invoked when a client exits either explicitly or by binder death.
+ */
+ private synchronized void onClientExit() {
+ if (mCallbackInterface != null) {
+ mCallbackInterface.asBinder().unlinkToDeath(this, 0 /* flags */);
+ mCallbackInterface = null;
+ }
+ if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
+ mClientManager.unregisterClient(mHostEndPointId);
+ mRegistered = false;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/ContextHubClientManager.java b/services/core/java/com/android/server/location/ContextHubClientManager.java
index fe93a1a..7293440 100644
--- a/services/core/java/com/android/server/location/ContextHubClientManager.java
+++ b/services/core/java/com/android/server/location/ContextHubClientManager.java
@@ -24,6 +24,7 @@
import android.hardware.location.IContextHubClient;
import android.hardware.location.IContextHubClientCallback;
import android.hardware.location.NanoAppMessage;
+import android.os.RemoteException;
import android.util.Log;
import java.util.concurrent.ConcurrentHashMap;
@@ -68,7 +69,7 @@
/*
* The next host endpoint ID to start iterating for the next available host endpoint ID.
*/
- private int mNextHostEndpointId = 0;
+ private int mNextHostEndPointId = 0;
/* package */ ContextHubClientManager(
Context context, IContexthub contextHubProxy) {
@@ -88,9 +89,22 @@
*/
/* package */ IContextHubClient registerClient(
IContextHubClientCallback clientCallback, ContextHubInfo contextHubInfo) {
- ContextHubClientBroker broker = createNewClientBroker(contextHubInfo);
- if (!broker.setCallback(clientCallback)) {
- return null; // Client process has died, so we return null
+ ContextHubClientBroker broker;
+ synchronized (this) {
+ short hostEndPointId = getHostEndPointId();
+ broker = new ContextHubClientBroker(
+ mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
+ hostEndPointId, clientCallback);
+ mHostEndPointIdToClientMap.put(hostEndPointId, broker);
+ }
+
+ try {
+ broker.attachDeathRecipient();
+ } catch (RemoteException e) {
+ // The client process has died, so we close the connection and return null
+ Log.e(TAG, "Failed to attach death recipient to client");
+ broker.close();
+ return null;
}
Log.d(TAG, "Registered client with host endpoint ID " + broker.getHostEndPointId());
@@ -98,32 +112,36 @@
}
/**
- * Binds a existing and registered client with a new callback interface, provided a previously
- * registered PendingIntent.
+ * Registers a new client with the service.
*
- * @param pendingIntent a previously registered PendingIntent for a registered client
- * @param clientCallback the callback interface of the client to bind to
- * @param contextHubId the ID of the hub this client is attached to
+ * @param pendingIntent the callback interface of the client to register
+ * @param contextHubInfo the object describing the hub this client is attached to
+ * @param nanoAppId the ID of the nanoapp to receive Intent events for
*
* @return the client interface
*
- * @throws IllegalArgumentException if no matching client is found
- * @throws IllegalStateException if the client has already been registered to a callback
+ * @throws IllegalArgumentException the PendingIntent was already registered for a different
+ * ContextHubClient
+ * @throws IllegalStateException if there were too many registered clients at the service
*/
- /* package */ IContextHubClient bindClient(
- PendingIntent pendingIntent, IContextHubClientCallback clientCallback,
- int contextHubId) {
- ContextHubClientBroker broker = getClientBroker(pendingIntent, contextHubId);
- if (broker == null) {
- throw new IllegalArgumentException("Could not find client of Context Hub (ID = "
- + contextHubId + ") with PendingIntent");
+ /* package */ IContextHubClient registerClient(
+ ContextHubInfo contextHubInfo, PendingIntent pendingIntent, long nanoAppId) {
+ ContextHubClientBroker broker;
+ String registerString = "Regenerated";
+ synchronized (this) {
+ broker = getClientBroker(contextHubInfo.getId(), pendingIntent, nanoAppId);
+
+ if (broker == null) {
+ short hostEndPointId = getHostEndPointId();
+ broker = new ContextHubClientBroker(
+ mContext, mContextHubProxy, this /* clientManager */, contextHubInfo,
+ hostEndPointId, pendingIntent, nanoAppId);
+ mHostEndPointIdToClientMap.put(hostEndPointId, broker);
+ registerString = "Registered";
+ }
}
- if (!broker.setCallback(clientCallback)) {
- return null; // Client process has died, so we return null
- }
-
- Log.d(TAG, "Re-registered client with host endpoint ID " + broker.getHostEndPointId());
+ Log.d(TAG, registerString + " client with host endpoint ID " + broker.getHostEndPointId());
return IContextHubClient.Stub.asInterface(broker);
}
@@ -203,50 +221,28 @@
}
/**
- * @param pendingIntent the PendingIntent to check
- * @return true if the given PendingIntent is registered by a client, false otherwise
- */
- /* package */ boolean isPendingIntentRegistered(PendingIntent pendingIntent) {
- for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
- if (broker.hasPendingIntent(pendingIntent)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Creates a new ContextHubClientBroker object for a client and registers it with the client
- * manager.
+ * Returns an available host endpoint ID.
*
- * @param contextHubInfo the object describing the hub this client is attached to
- *
- * @return the ContextHubClientBroker object
+ * @returns an available host endpoint ID
*
* @throws IllegalStateException if max number of clients have already registered
*/
- private synchronized ContextHubClientBroker createNewClientBroker(
- ContextHubInfo contextHubInfo) {
+ private short getHostEndPointId() {
if (mHostEndPointIdToClientMap.size() == MAX_CLIENT_ID + 1) {
throw new IllegalStateException("Could not register client - max limit exceeded");
}
- ContextHubClientBroker broker = null;
- int id = mNextHostEndpointId;
+ int id = mNextHostEndPointId;
for (int i = 0; i <= MAX_CLIENT_ID; i++) {
if (!mHostEndPointIdToClientMap.containsKey((short) id)) {
- broker = new ContextHubClientBroker(
- mContext, mContextHubProxy, this, contextHubInfo, (short) id);
- mHostEndPointIdToClientMap.put((short) id, broker);
- mNextHostEndpointId = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+ mNextHostEndPointId = (id == MAX_CLIENT_ID) ? 0 : id + 1;
break;
}
id = (id == MAX_CLIENT_ID) ? 0 : id + 1;
}
- return broker;
+ return (short) id;
}
/**
@@ -280,9 +276,10 @@
* @param contextHubId the ID of the Context Hub the client is attached to
* @return the matching ContextHubClientBroker, null if not found
*/
- private ContextHubClientBroker getClientBroker(PendingIntent pendingIntent, int contextHubId) {
+ private ContextHubClientBroker getClientBroker(
+ int contextHubId, PendingIntent pendingIntent, long nanoAppId) {
for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
- if (broker.hasPendingIntent(pendingIntent)
+ if (broker.hasPendingIntent(pendingIntent, nanoAppId)
&& broker.getAttachedContextHubId() == contextHubId) {
return broker;
}
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index 215e67c..52f1c6b 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -632,34 +632,26 @@
}
/**
- * Recreates and binds a IContextHubClientCallback interface to an existing and registered
- * client at the service for the specified Context Hub, provided a previously registered
- * PendingIntent.
+ * Creates and registers a PendingIntent client at the service for the specified Context Hub.
*
- * @param pendingIntent the PendingIntent previously registered for the client
- * @param clientCallback the client interface to register with the service
- * @param contextHubId the ID of the hub this client is attached to
- * @return the generated client interface, null if registration was unsuccessful
+ * @param contextHubId the ID of the hub this client is attached to
+ * @param pendingIntent the PendingIntent associated with this client
+ * @param nanoAppId the ID of the nanoapp PendingIntent events will be sent for
+ * @return the generated client interface
*
- * @throws IllegalArgumentException if contextHubId is not a valid ID
- * @throws NullPointerException if clientCallback or pendingIntent is null
+ * @throws IllegalArgumentException if hubInfo does not represent a valid hub
+ * @throws IllegalStateException if there were too many registered clients at the service
*/
@Override
- public IContextHubClient bindClient(
- PendingIntent pendingIntent, IContextHubClientCallback clientCallback,
- int contextHubId) throws RemoteException {
+ public IContextHubClient createPendingIntentClient(
+ int contextHubId, PendingIntent pendingIntent, long nanoAppId) throws RemoteException {
checkPermissions();
if (!isValidContextHubId(contextHubId)) {
throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
}
- if (pendingIntent == null) {
- throw new NullPointerException("Cannot create client with null pending intent");
- }
- if (clientCallback == null) {
- throw new NullPointerException("Cannot create client with null callback");
- }
- return mClientManager.bindClient(pendingIntent, clientCallback, contextHubId);
+ ContextHubInfo contextHubInfo = mContextHubIdToInfoMap.get(contextHubId);
+ return mClientManager.registerClient(contextHubInfo, pendingIntent, nanoAppId);
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index cfbe83d..6195ed9e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -870,6 +870,7 @@
}
if (expanded && userAction) {
r.recordExpanded();
+ reportUserInteraction(r);
}
EventLogTags.writeNotificationExpansion(key,
userAction ? 1 : 0, expanded ? 1 : 0,
@@ -1987,7 +1988,7 @@
}
/**
- * Report to usage stats that the notification was clicked.
+ * Report to usage stats that the user interacted with the notification.
* @param r notification record
*/
protected void reportUserInteraction(NotificationRecord r) {
@@ -4539,6 +4540,7 @@
mDuration)
.addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
mSnoozeCriterionId == null ? 0 : 1));
+ reportUserInteraction(r);
boolean wasPosted = removeFromNotificationListsLocked(r);
cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null);
updateLightsLocked();
@@ -5547,7 +5549,7 @@
ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
ArrayList<Integer> suppressVisuallyBefore = new ArrayList<>(N);
- ArrayList<ArrayList<Notification.Action>> smartActionsBefore = new ArrayList<>(N);
+ ArrayList<ArrayList<Notification.Action>> systemSmartActionsBefore = new ArrayList<>(N);
ArrayList<ArrayList<CharSequence>> smartRepliesBefore = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
@@ -5560,7 +5562,7 @@
snoozeCriteriaBefore.add(r.getSnoozeCriteria());
userSentimentBefore.add(r.getUserSentiment());
suppressVisuallyBefore.add(r.getSuppressedVisualEffects());
- smartActionsBefore.add(r.getSmartActions());
+ systemSmartActionsBefore.add(r.getSystemGeneratedSmartActions());
smartRepliesBefore.add(r.getSmartReplies());
mRankingHelper.extractSignals(r);
}
@@ -5577,7 +5579,8 @@
|| !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())
|| !Objects.equals(suppressVisuallyBefore.get(i),
r.getSuppressedVisualEffects())
- || !Objects.equals(smartActionsBefore.get(i), r.getSmartActions())
+ || !Objects.equals(systemSmartActionsBefore.get(i),
+ r.getSystemGeneratedSmartActions())
|| !Objects.equals(smartRepliesBefore.get(i), r.getSmartReplies())) {
mHandler.scheduleSendRankingUpdate();
return;
@@ -6579,7 +6582,7 @@
Bundle showBadge = new Bundle();
Bundle userSentiment = new Bundle();
Bundle hidden = new Bundle();
- Bundle smartActions = new Bundle();
+ Bundle systemGeneratedSmartActions = new Bundle();
Bundle smartReplies = new Bundle();
Bundle audiblyAlerted = new Bundle();
Bundle noisy = new Bundle();
@@ -6610,7 +6613,8 @@
showBadge.putBoolean(key, record.canShowBadge());
userSentiment.putInt(key, record.getUserSentiment());
hidden.putBoolean(key, record.isHidden());
- smartActions.putParcelableArrayList(key, record.getSmartActions());
+ systemGeneratedSmartActions.putParcelableArrayList(key,
+ record.getSystemGeneratedSmartActions());
smartReplies.putCharSequenceArrayList(key, record.getSmartReplies());
audiblyAlerted.putBoolean(key, record.getAudiblyAlerted());
noisy.putBoolean(key, record.getSound() != null || record.getVibration() != null);
@@ -6625,7 +6629,7 @@
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden,
- smartActions, smartReplies, audiblyAlerted, noisy);
+ systemGeneratedSmartActions, smartReplies, audiblyAlerted, noisy);
}
boolean hasCompanionDevice(ManagedServiceInfo info) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index a11b03f..1a9257c 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -163,7 +163,11 @@
private Light mLight;
private String mGroupLogTag;
private String mChannelIdLogTag;
- private ArrayList<Notification.Action> mSmartActions;
+ /**
+ * This list contains system generated smart actions from NAS, app-generated smart actions are
+ * stored in Notification.actions marked as SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION.
+ */
+ private ArrayList<Notification.Action> mSystemGeneratedSmartActions;
private ArrayList<CharSequence> mSmartReplies;
private final List<Adjustment> mAdjustments;
@@ -653,10 +657,11 @@
}
}
if (signals.containsKey(Adjustment.KEY_SMART_ACTIONS)) {
- setSmartActions(signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS));
+ setSystemGeneratedSmartActions(
+ signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS));
MetricsLogger.action(getAdjustmentLogMaker()
.addTaggedData(MetricsEvent.ADJUSTMENT_KEY_SMART_ACTIONS,
- getSmartActions().size()));
+ getSystemGeneratedSmartActions().size()));
}
if (signals.containsKey(Adjustment.KEY_SMART_REPLIES)) {
setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES));
@@ -1132,12 +1137,13 @@
mHasSeenSmartReplies = hasSeenSmartReplies;
}
- public void setSmartActions(ArrayList<Notification.Action> smartActions) {
- mSmartActions = smartActions;
+ public void setSystemGeneratedSmartActions(
+ ArrayList<Notification.Action> systemGeneratedSmartActions) {
+ mSystemGeneratedSmartActions = systemGeneratedSmartActions;
}
- public ArrayList<Notification.Action> getSmartActions() {
- return mSmartActions;
+ public ArrayList<Notification.Action> getSystemGeneratedSmartActions() {
+ return mSystemGeneratedSmartActions;
}
public void setSmartReplies(ArrayList<CharSequence> smartReplies) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8a0c416..e4e8010 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -12937,6 +12937,25 @@
}
}
+ @Override
+ public boolean canSuspendPackageForUser(String packageName, int userId) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.SUSPEND_APPS,
+ "canSuspendPackageForUser");
+ final int callingUid = Binder.getCallingUid();
+ if (UserHandle.getUserId(callingUid) != userId) {
+ throw new SecurityException("Calling uid " + callingUid
+ + " cannot query canSuspendPackageForUser for user " + userId);
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mPackages) {
+ return canSuspendPackageForUserLocked(packageName, userId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@GuardedBy("mPackages")
private boolean canSuspendPackageForUserLocked(String packageName, int userId) {
if (isPackageDeviceAdmin(packageName, userId)) {
@@ -13000,7 +13019,7 @@
}
if (PLATFORM_PACKAGE_NAME.equals(packageName)) {
- Slog.w(TAG, "Cannot suspend package: " + packageName);
+ Slog.w(TAG, "Cannot suspend the platform package: " + packageName);
return false;
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 073601d..912cb7f 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -809,7 +809,7 @@
updateBoundsForDisplayChanges();
}
- if (mAnimationBackgroundSurface != null) {
+ if (mAnimationBackgroundSurface == null) {
mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer(true)
.setName("animation background stackId=" + mStackId)
.build();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
index f17a30d..410ab87 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
@@ -72,7 +72,7 @@
assertTrue(r.getGroupKey().contains(GroupHelper.AUTOGROUP_KEY));
assertEquals(people, r.getPeopleOverride());
assertEquals(snoozeCriteria, r.getSnoozeCriteria());
- assertEquals(smartActions, r.getSmartActions());
+ assertEquals(smartActions, r.getSystemGeneratedSmartActions());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index 9b41fdd..8690110 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -708,14 +708,14 @@
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
false /* lights */, false /* defaultLights */, groupId /* group */);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
- assertNull(record.getSmartActions());
+ assertNull(record.getSystemGeneratedSmartActions());
ArrayList<Notification.Action> smartActions = new ArrayList<>();
smartActions.add(new Notification.Action.Builder(
Icon.createWithResource(getContext(), R.drawable.btn_default),
"text", null).build());
- record.setSmartActions(smartActions);
- assertEquals(smartActions, record.getSmartActions());
+ record.setSystemGeneratedSmartActions(smartActions);
+ assertEquals(smartActions, record.getSystemGeneratedSmartActions());
}
@Test
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 32875da..94d7dbb 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -915,8 +915,12 @@
return "SCREEN_INTERACTIVE";
case UsageEvents.Event.SCREEN_NON_INTERACTIVE:
return "SCREEN_NON_INTERACTIVE";
+ case UsageEvents.Event.KEYGUARD_SHOWN:
+ return "KEYGUARD_SHOWN";
+ case UsageEvents.Event.KEYGUARD_HIDDEN:
+ return "KEYGUARD_HIDDEN";
default:
- return "UNKNOWN";
+ return "UNKNOWN_TYPE_" + eventType;
}
}
diff --git a/telephony/java/android/telephony/CellSignalStrength.java b/telephony/java/android/telephony/CellSignalStrength.java
index 6090d5c..fd21d42 100644
--- a/telephony/java/android/telephony/CellSignalStrength.java
+++ b/telephony/java/android/telephony/CellSignalStrength.java
@@ -21,15 +21,20 @@
*/
public abstract class CellSignalStrength {
- public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN = 0;
+ public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN =
+ TelephonyProtoEnums.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; // 0
- public static final int SIGNAL_STRENGTH_POOR = 1;
+ public static final int SIGNAL_STRENGTH_POOR =
+ TelephonyProtoEnums.SIGNAL_STRENGTH_POOR; // 1
- public static final int SIGNAL_STRENGTH_MODERATE = 2;
+ public static final int SIGNAL_STRENGTH_MODERATE =
+ TelephonyProtoEnums.SIGNAL_STRENGTH_MODERATE; // 2
- public static final int SIGNAL_STRENGTH_GOOD = 3;
+ public static final int SIGNAL_STRENGTH_GOOD =
+ TelephonyProtoEnums.SIGNAL_STRENGTH_GOOD; // 3
- public static final int SIGNAL_STRENGTH_GREAT = 4;
+ public static final int SIGNAL_STRENGTH_GREAT =
+ TelephonyProtoEnums.SIGNAL_STRENGTH_GREAT; // 4
/** @hide */
public static final int NUM_SIGNAL_STRENGTH_BINS = 5;
diff --git a/telephony/java/android/telephony/ICellInfoCallback.aidl b/telephony/java/android/telephony/ICellInfoCallback.aidl
new file mode 100644
index 0000000..7fb62682
--- /dev/null
+++ b/telephony/java/android/telephony/ICellInfoCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.telephony.CellInfo;
+
+import java.util.List;
+
+/**
+ * Callback to provide asynchronous CellInfo.
+ * @hide
+ */
+oneway interface ICellInfoCallback
+{
+ void onCellInfo(in List<CellInfo> state);
+}
diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java
index 75e8eda..aee744f 100644
--- a/telephony/java/android/telephony/NetworkRegistrationState.java
+++ b/telephony/java/android/telephony/NetworkRegistrationState.java
@@ -70,6 +70,43 @@
/** Registered on roaming network */
public static final int REG_STATE_ROAMING = 5;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "NR_STATUS_",
+ value = {NR_STATUS_NONE, NR_STATUS_RESTRICTED, NR_STATUS_NOT_RESTRICTED,
+ NR_STATUS_CONNECTED})
+ public @interface NRStatus {}
+
+ /**
+ * The device isn't camped on an LTE cell or the LTE cell doesn't support E-UTRA-NR
+ * Dual Connectivity(EN-DC).
+ * @hide
+ */
+ public static final int NR_STATUS_NONE = -1;
+
+ /**
+ * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) but
+ * either the use of dual connectivity with NR(DCNR) is restricted or NR is not supported by
+ * the selected PLMN.
+ * @hide
+ */
+ public static final int NR_STATUS_RESTRICTED = 1;
+
+ /**
+ * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) and both
+ * the use of dual connectivity with NR(DCNR) is not restricted and NR is supported by the
+ * selected PLMN.
+ * @hide
+ */
+ public static final int NR_STATUS_NOT_RESTRICTED = 2;
+
+ /**
+ * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) and
+ * also connected to at least one 5G cell as a secondary serving cell.
+ * @hide
+ */
+ public static final int NR_STATUS_CONNECTED = 3;
+
/**
* Supported service type
* @hide
@@ -104,6 +141,9 @@
private int mAccessNetworkTechnology;
+ @NRStatus
+ private int mNrStatus;
+
private final int mRejectCause;
private final boolean mEmergencyOnly;
@@ -154,6 +194,7 @@
mAvailableServices = availableServices;
mCellIdentity = cellIdentity;
mEmergencyOnly = emergencyOnly;
+ mNrStatus = NR_STATUS_NONE;
}
/**
@@ -200,6 +241,7 @@
VoiceSpecificRegistrationStates.class.getClassLoader());
mDataSpecificStates = source.readParcelable(
DataSpecificRegistrationStates.class.getClassLoader());
+ mNrStatus = source.readInt();
}
/**
@@ -213,6 +255,19 @@
public @Domain int getDomain() { return mDomain; }
/**
+ * @return the 5G NR connection status.
+ * @hide
+ */
+ public @NRStatus int getNrStatus() {
+ return mNrStatus;
+ }
+
+ /** @hide */
+ public void setNrStatus(@NRStatus int nrStatus) {
+ mNrStatus = nrStatus;
+ }
+
+ /**
* @return The registration state.
*/
public @RegState int getRegState() {
@@ -315,6 +370,19 @@
return "Unknown reg state " + regState;
}
+ private static String nrStatusToString(@NRStatus int nrStatus) {
+ switch (nrStatus) {
+ case NR_STATUS_RESTRICTED:
+ return "RESTRICTED";
+ case NR_STATUS_NOT_RESTRICTED:
+ return "NOT_RESTRICTED";
+ case NR_STATUS_CONNECTED:
+ return "CONNECTED";
+ default:
+ return "NONE";
+ }
+ }
+
@Override
public String toString() {
return new StringBuilder("NetworkRegistrationState{")
@@ -330,6 +398,7 @@
.append(" cellIdentity=").append(mCellIdentity)
.append(" voiceSpecificStates=").append(mVoiceSpecificStates)
.append(" dataSpecificStates=").append(mDataSpecificStates)
+ .append(" nrStatus=").append(nrStatusToString(mNrStatus))
.append("}").toString();
}
@@ -337,7 +406,7 @@
public int hashCode() {
return Objects.hash(mDomain, mTransportType, mRegState, mRoamingType,
mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices,
- mCellIdentity, mVoiceSpecificStates, mDataSpecificStates);
+ mCellIdentity, mVoiceSpecificStates, mDataSpecificStates, mNrStatus);
}
@Override
@@ -359,7 +428,8 @@
&& Arrays.equals(mAvailableServices, other.mAvailableServices)
&& Objects.equals(mCellIdentity, other.mCellIdentity)
&& Objects.equals(mVoiceSpecificStates, other.mVoiceSpecificStates)
- && Objects.equals(mDataSpecificStates, other.mDataSpecificStates);
+ && Objects.equals(mDataSpecificStates, other.mDataSpecificStates)
+ && mNrStatus == other.mNrStatus;
}
@Override
@@ -375,6 +445,7 @@
dest.writeParcelable(mCellIdentity, 0);
dest.writeParcelable(mVoiceSpecificStates, 0);
dest.writeParcelable(mDataSpecificStates, 0);
+ dest.writeInt(mNrStatus);
}
public static final Parcelable.Creator<NetworkRegistrationState> CREATOR =
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 0937b10..ab80e25 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -83,7 +83,45 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef({DUPLEX_MODE_UNKNOWN, DUPLEX_MODE_FDD, DUPLEX_MODE_TDD})
+ @IntDef(prefix = "FREQUENCY_RANGE_",
+ value = {FREQUENCY_RANGE_UNKNOWN, FREQUENCY_RANGE_LOW, FREQUENCY_RANGE_MID,
+ FREQUENCY_RANGE_HIGH, FREQUENCY_RANGE_MMWAVE})
+ public @interface FrequencyRange {}
+
+ /**
+ * Indicates frequency range is unknown.
+ * @hide
+ */
+ public static final int FREQUENCY_RANGE_UNKNOWN = -1;
+
+ /**
+ * Indicates the frequency range is below 1GHz.
+ * @hide
+ */
+ public static final int FREQUENCY_RANGE_LOW = 1;
+
+ /**
+ * Indicates the frequency range is between 1GHz to 3GHz.
+ * @hide
+ */
+ public static final int FREQUENCY_RANGE_MID = 2;
+
+ /**
+ * Indicates the frequency range is between 3GHz and 6GHz.
+ * @hide
+ */
+ public static final int FREQUENCY_RANGE_HIGH = 3;
+
+ /**
+ * Indicates the frequency range is above 6GHz (millimeter wave frequency).
+ * @hide
+ */
+ public static final int FREQUENCY_RANGE_MMWAVE = 4;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "DUPLEX_MODE_",
+ value = {DUPLEX_MODE_UNKNOWN, DUPLEX_MODE_FDD, DUPLEX_MODE_TDD})
public @interface DuplexMode {}
/**
@@ -283,6 +321,8 @@
@UnsupportedAppUsage
private boolean mIsUsingCarrierAggregation;
+ @FrequencyRange
+ private int mNrFrequencyRange;
private int mChannelNumber;
private int[] mCellBandwidths = new int[0];
@@ -375,6 +415,7 @@
mLteEarfcnRsrpBoost = s.mLteEarfcnRsrpBoost;
mNetworkRegistrationStates = s.mNetworkRegistrationStates == null ? null :
new ArrayList<>(s.mNetworkRegistrationStates);
+ mNrFrequencyRange = s.mNrFrequencyRange;
}
/**
@@ -406,6 +447,7 @@
in.readList(mNetworkRegistrationStates, NetworkRegistrationState.class.getClassLoader());
mChannelNumber = in.readInt();
mCellBandwidths = in.createIntArray();
+ mNrFrequencyRange = in.readInt();
}
public void writeToParcel(Parcel out, int flags) {
@@ -433,6 +475,7 @@
out.writeList(mNetworkRegistrationStates);
out.writeInt(mChannelNumber);
out.writeIntArray(mCellBandwidths);
+ out.writeInt(mNrFrequencyRange);
}
public int describeContents() {
@@ -792,7 +835,8 @@
mIsEmergencyOnly,
mIsUsingCarrierAggregation,
mLteEarfcnRsrpBoost,
- mNetworkRegistrationStates);
+ mNetworkRegistrationStates,
+ mNrFrequencyRange);
}
@Override
@@ -823,7 +867,8 @@
&& mIsUsingCarrierAggregation == s.mIsUsingCarrierAggregation)
&& (mNetworkRegistrationStates == null ? s.mNetworkRegistrationStates == null :
s.mNetworkRegistrationStates != null &&
- mNetworkRegistrationStates.containsAll(s.mNetworkRegistrationStates));
+ mNetworkRegistrationStates.containsAll(s.mNetworkRegistrationStates))
+ && mNrFrequencyRange == s.mNrFrequencyRange;
}
/**
@@ -958,6 +1003,7 @@
.append(", mIsUsingCarrierAggregation=").append(mIsUsingCarrierAggregation)
.append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost)
.append(", mNetworkRegistrationStates=").append(mNetworkRegistrationStates)
+ .append(", mNrFrequencyRange=").append(mNrFrequencyRange)
.append("}").toString();
}
@@ -987,6 +1033,7 @@
mIsUsingCarrierAggregation = false;
mLteEarfcnRsrpBoost = 0;
mNetworkRegistrationStates = new ArrayList<>();
+ mNrFrequencyRange = FREQUENCY_RANGE_UNKNOWN;
}
public void setStateOutOfService() {
@@ -1225,6 +1272,7 @@
m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost);
m.putInt("ChannelNumber", mChannelNumber);
m.putIntArray("CellBandwidths", mCellBandwidths);
+ m.putInt("mNrFrequencyRange", mNrFrequencyRange);
}
/** @hide */
@@ -1288,6 +1336,22 @@
mIsUsingCarrierAggregation = ca;
}
+ /**
+ * @return the frequency range of 5G NR.
+ * @hide
+ */
+ public @FrequencyRange int getNrFrequencyRange() {
+ return mNrFrequencyRange;
+ }
+
+ /**
+ * @param nrFrequencyRange the frequency range of 5G NR.
+ * @hide
+ */
+ public void setNrFrequencyRange(@FrequencyRange int nrFrequencyRange) {
+ mNrFrequencyRange = nrFrequencyRange;
+ }
+
/** @hide */
public int getLteEarfcnRsrpBoost() {
return mLteEarfcnRsrpBoost;
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index bc832c3..240b8a9 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -17,12 +17,12 @@
package android.telephony;
import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.CarrierConfigManager;
import android.util.Log;
-import android.content.res.Resources;
import java.util.ArrayList;
import java.util.Arrays;
@@ -37,25 +37,25 @@
private static final boolean DBG = false;
/** @hide */
- @UnsupportedAppUsage
- public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN
- = TelephonyProtoEnums.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; // = 0
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public static final int SIGNAL_STRENGTH_NONE_OR_UNKNOWN =
+ CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN; // = 0
/** @hide */
- @UnsupportedAppUsage
- public static final int SIGNAL_STRENGTH_POOR
- = TelephonyProtoEnums.SIGNAL_STRENGTH_POOR; // = 1
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public static final int SIGNAL_STRENGTH_POOR =
+ CellSignalStrength.SIGNAL_STRENGTH_POOR; // = 1
/** @hide */
- @UnsupportedAppUsage
- public static final int SIGNAL_STRENGTH_MODERATE
- = TelephonyProtoEnums.SIGNAL_STRENGTH_MODERATE; // = 2
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public static final int SIGNAL_STRENGTH_MODERATE =
+ CellSignalStrength.SIGNAL_STRENGTH_MODERATE; // = 2
/** @hide */
- @UnsupportedAppUsage
- public static final int SIGNAL_STRENGTH_GOOD
- = TelephonyProtoEnums.SIGNAL_STRENGTH_GOOD; // = 3
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public static final int SIGNAL_STRENGTH_GOOD =
+ CellSignalStrength.SIGNAL_STRENGTH_GOOD; // = 3
/** @hide */
- @UnsupportedAppUsage
- public static final int SIGNAL_STRENGTH_GREAT
- = TelephonyProtoEnums.SIGNAL_STRENGTH_GREAT; // = 4
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ public static final int SIGNAL_STRENGTH_GREAT =
+ CellSignalStrength.SIGNAL_STRENGTH_GREAT; // = 4
/** @hide */
@UnsupportedAppUsage
public static final int NUM_SIGNAL_STRENGTH_BINS = 5;
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 22c1e58..b41e14e 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -143,9 +143,11 @@
private boolean mIsOpportunistic;
/**
- * SubId of the parent subscription, if there is one.
+ * A UUID assigned to the subscription group. It returns
+ * null if not assigned.
*/
- private int mParentSubId;
+ @Nullable
+ private String mGroupUUID;
/**
* @hide
@@ -156,7 +158,7 @@
@Nullable UiccAccessRule[] accessRules, String cardId) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardId,
- false, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ false, null);
}
/**
@@ -166,7 +168,7 @@
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules, String cardId, boolean isOpportunistic,
- int parentSubId) {
+ @Nullable String groupUUID) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
@@ -184,7 +186,7 @@
this.mAccessRules = accessRules;
this.mCardId = cardId;
this.mIsOpportunistic = isOpportunistic;
- this.mParentSubId = parentSubId;
+ this.mGroupUUID = groupUUID;
}
/**
@@ -388,16 +390,16 @@
}
/**
- * Used in scenarios where a child subscription is bundled with a primary parent subscription.
- * The child subscription will typically be opportunistic (see {@link #isOpportunistic()})
- * and will be used to provide data services where available, with the parent being the primary
- * fallback subscription.
+ * Used in scenarios where different subscriptions are bundled as a group.
+ * It's typically a primary and an opportunistic subscription. (see {@link #isOpportunistic()})
+ * Such that those subscriptions will have some affiliated behaviors such as opportunistic
+ * subscription may be invisible to the user.
*
- * @return subId of parent subscription if it’s bundled with a primary subscription.
- * If there isn't one, {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}
+ * @return group UUID a String of group UUID if it belongs to a group. Otherwise
+ * it will return null.
*/
- public int getParentSubId() {
- return mParentSubId;
+ public String getGroupUuid() {
+ return mGroupUUID;
}
/**
@@ -493,11 +495,11 @@
UiccAccessRule[] accessRules = source.createTypedArray(UiccAccessRule.CREATOR);
String cardId = source.readString();
boolean isOpportunistic = source.readBoolean();
- int parentSubId = source.readInt();
+ String groupUUID = source.readString();
return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
- isEmbedded, accessRules, cardId, isOpportunistic, parentSubId);
+ isEmbedded, accessRules, cardId, isOpportunistic, groupUUID);
}
@Override
@@ -525,7 +527,7 @@
dest.writeTypedArray(mAccessRules, flags);
dest.writeString(mCardId);
dest.writeBoolean(mIsOpportunistic);
- dest.writeInt(mParentSubId);
+ dest.writeString(mGroupUUID);
}
@Override
@@ -559,13 +561,13 @@
+ " mnc " + mMnc + "mCountryIso=" + mCountryIso + " isEmbedded " + mIsEmbedded
+ " accessRules " + Arrays.toString(mAccessRules)
+ " cardId=" + cardIdToPrint + " isOpportunistic " + mIsOpportunistic
- + " parentSubId=" + mParentSubId + "}";
+ + " mGroupUUID=" + mGroupUUID + "}";
}
@Override
public int hashCode() {
return Objects.hash(mId, mSimSlotIndex, mNameSource, mIconTint, mDataRoaming, mIsEmbedded,
- mIsOpportunistic, mParentSubId, mIccId, mNumber, mMcc, mMnc, mCountryIso,
+ mIsOpportunistic, mGroupUUID, mIccId, mNumber, mMcc, mMnc, mCountryIso,
mCardId, mDisplayName, mCarrierName, mAccessRules);
}
@@ -588,7 +590,7 @@
&& mDataRoaming == toCompare.mDataRoaming
&& mIsEmbedded == toCompare.mIsEmbedded
&& mIsOpportunistic == toCompare.mIsOpportunistic
- && mParentSubId == toCompare.mParentSubId
+ && Objects.equals(mGroupUUID, toCompare.mGroupUUID)
&& Objects.equals(mIccId, toCompare.mIccId)
&& Objects.equals(mNumber, toCompare.mNumber)
&& Objects.equals(mMcc, toCompare.mMcc)
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 31770ec..1dafc12 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -119,7 +119,6 @@
@UnsupportedAppUsage
public static final Uri CONTENT_URI = Uri.parse("content://telephony/siminfo");
-
/**
* Generates a content {@link Uri} used to receive updates on simInfo change
* on the given subscriptionId
@@ -577,6 +576,15 @@
public static final String PARENT_SUB_ID = "parent_sub_id";
/**
+ * TelephonyProvider column name for group ID. Subscriptions with same group ID
+ * are considered bundled together, and should behave as a single subscription at
+ * certain scenarios.
+ *
+ * @hide
+ */
+ public static final String GROUP_UUID = "group_uuid";
+
+ /**
* Broadcast Action: The user has changed one of the default subs related to
* data, phone calls, or sms</p>
*
@@ -2290,7 +2298,7 @@
* subscription dynamically in multi-SIM devices.
*
* @param subId which subscription is preferred to for cellular data. If it's
- * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}, it means
+ * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID}, it means
* it's unset and {@link SubscriptionManager#getDefaultDataSubscriptionId()}
* is used to determine which modem is preferred.
* @hide
@@ -2365,19 +2373,40 @@
}
/**
- * Set parent subId by simInfo index
+ * Inform SubscriptionManager that subscriptions in the list are bundled
+ * as a group. Typically it's a primary subscription and an opportunistic
+ * subscription. It should only affect multi-SIM scenarios where primary
+ * and opportunistic subscriptions can be activated together.
+ * Being in the same group means they might be activated or deactivated
+ * together, some of them may be invisible to the users, etc.
*
- * @param parentSubId subId of its parent subscription.
- * @param subId the unique SubscriptionInfo index in database
- * @return the number of records updated
- * @hide
+ * Caller will either have {@link android.Manifest.permission.MODIFY_PHONE_STATE}
+ * permission or can manage all subscriptions in the list, according to their
+ * acess rules.
+ *
+ * @param subIdList list of subId that will be in the same group
+ * @return groupUUID a UUID assigned to the subscription group. It returns
+ * null if fails.
*
*/
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public int setParentSubId(int parentSubId, int subId) {
- if (VDBG) logd("[setParentSubId]+ parentSubId:" + parentSubId + " subId:" + subId);
- return setSubscriptionPropertyHelper(subId, "parentSubId",
- (iSub)-> iSub.setParentSubId(parentSubId, subId));
+ public String setSubscriptionGroup(int[] subIdList) {
+ String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+ if (VDBG) {
+ logd("[setSubscriptionGroup]+ subIdList:" + Arrays.toString(subIdList));
+ }
+
+ String groupUUID = null;
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub != null) {
+ groupUUID = iSub.setSubscriptionGroup(subIdList, pkgForDebug);
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+
+ return groupUUID;
}
private interface CallISubMethodHelper {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index bd1a0fb..d72b5ec 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -21,6 +21,7 @@
import static com.android.internal.util.Preconditions.checkNotNull;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -43,6 +44,7 @@
import android.net.Uri;
import android.os.AsyncTask;
import android.os.BatteryStats;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -52,6 +54,7 @@
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.os.WorkSource;
import android.provider.Settings.SettingNotFoundException;
import android.service.carrier.CarrierIdentifier;
import android.telecom.PhoneAccount;
@@ -4750,37 +4753,42 @@
}
/**
- * Returns all observed cell information from all radios on the
- * device including the primary and neighboring cells. Calling this method does
- * not trigger a call to {@link android.telephony.PhoneStateListener#onCellInfoChanged
- * onCellInfoChanged()}, or change the rate at which
- * {@link android.telephony.PhoneStateListener#onCellInfoChanged
- * onCellInfoChanged()} is called.
+ * Requests all available cell information from all radios on the device including the
+ * camped/registered, serving, and neighboring cells.
*
- *<p>
- * The list can include one or more {@link android.telephony.CellInfoGsm CellInfoGsm},
+ * <p>The response can include one or more {@link android.telephony.CellInfoGsm CellInfoGsm},
* {@link android.telephony.CellInfoCdma CellInfoCdma},
+ * {@link android.telephony.CellInfoTdscdma CellInfoTdscdma},
* {@link android.telephony.CellInfoLte CellInfoLte}, and
* {@link android.telephony.CellInfoWcdma CellInfoWcdma} objects, in any combination.
- * On devices with multiple radios it is typical to see instances of
- * one or more of any these in the list. In addition, zero, one, or more
- * of the returned objects may be considered registered; that is, their
+ * It is typical to see instances of one or more of any these in the list. In addition, zero
+ * or more of the returned objects may be considered registered; that is, their
* {@link android.telephony.CellInfo#isRegistered CellInfo.isRegistered()}
- * methods may return true.
+ * methods may return true, indicating that the cell is being used or would be used for
+ * signaling communication if necessary.
*
- * <p>This method returns valid data for registered cells on devices with
- * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY}. In cases where only
- * partial information is available for a particular CellInfo entry, unavailable fields
- * will be reported as Integer.MAX_VALUE. All reported cells will include at least a
- * valid set of technology-specific identification info and a power level measurement.
+ * <p>Beginning with {@link android.os.Build.VERSION_CODES#Q Android Q},
+ * if this API results in a change of the cached CellInfo, that change will be reported via
+ * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}.
*
- *<p>
- * This method is preferred over using {@link
+ * <p>Apps targeting {@link android.os.Build.VERSION_CODES#Q Android Q} or higher will no
+ * longer trigger a refresh of the cached CellInfo by invoking this API. Instead, those apps
+ * will receive the latest cached results. Apps targeting
+ * {@link android.os.Build.VERSION_CODES#Q Android Q} or higher that wish to request updated
+ * CellInfo should call
+ * {android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()} and
+ * listen for responses via {@link android.telephony.PhoneStateListener#onCellInfoChanged
+ * onCellInfoChanged()}.
+ *
+ * <p>This method returns valid data for devices with
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. In cases
+ * where only partial information is available for a particular CellInfo entry, unavailable
+ * fields will be reported as {@link android.telephony.CellInfo#UNAVAILABLE}. All reported
+ * cells will include at least a valid set of technology-specific identification info and a
+ * power level measurement.
+ *
+ * <p>This method is preferred over using {@link
* android.telephony.TelephonyManager#getCellLocation getCellLocation()}.
- * However, for older devices, <code>getAllCellInfo()</code> may return
- * null. In these cases, you should call {@link
- * android.telephony.TelephonyManager#getCellLocation getCellLocation()}
- * instead.
*
* @return List of {@link android.telephony.CellInfo}; null if cell
* information is unavailable.
@@ -4791,11 +4799,92 @@
ITelephony telephony = getITelephony();
if (telephony == null)
return null;
- return telephony.getAllCellInfo(getOpPackageName());
+ return telephony.getAllCellInfo(
+ getOpPackageName());
} catch (RemoteException ex) {
- return null;
} catch (NullPointerException ex) {
- return null;
+ }
+ return null;
+ }
+
+ /** Callback for providing asynchronous {@link CellInfo} on request */
+ public abstract static class CellInfoCallback {
+ /**
+ * Response to
+ * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}.
+ *
+ * <p>Invoked when there is a response to
+ * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}
+ * to provide a list of {@link CellInfo}. If no {@link CellInfo} is available then an empty
+ * list will be provided. If an error occurs, null will be provided.
+ *
+ * @param cellInfo a list of {@link CellInfo}, an empty list, or null.
+ *
+ * {@see android.telephony.TelephonyManager#getAllCellInfo getAllCellInfo()}
+ */
+ public abstract void onCellInfo(List<CellInfo> cellInfo);
+ };
+
+ /**
+ * Requests all available cell information from the current subscription for observed
+ * camped/registered, serving, and neighboring cells.
+ *
+ * <p>Any available results from this request will be provided by calls to
+ * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
+ * for each active subscription.
+ *
+ * @param executor the executor on which callback will be invoked.
+ * @param callback a callback to receive CellInfo.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ public void requestCellInfoUpdate(
+ @NonNull Executor executor, @NonNull CellInfoCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) return;
+ telephony.requestCellInfoUpdate(
+ getSubId(),
+ new ICellInfoCallback.Stub() {
+ public void onCellInfo(List<CellInfo> cellInfo) {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> callback.onCellInfo(cellInfo)));
+ }
+ }, getOpPackageName());
+
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * Requests all available cell information from the current subscription for observed
+ * camped/registered, serving, and neighboring cells.
+ *
+ * <p>Any available results from this request will be provided by calls to
+ * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
+ * for each active subscription.
+ *
+ * @param workSource the requestor to whom the power consumption for this should be attributed.
+ * @param executor the executor on which callback will be invoked.
+ * @param callback a callback to receive CellInfo.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_COARSE_LOCATION,
+ android.Manifest.permission.MODIFY_PHONE_STATE})
+ public void requestCellInfoUpdate(@NonNull WorkSource workSource,
+ @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) return;
+ telephony.requestCellInfoUpdateWithWorkSource(
+ getSubId(),
+ new ICellInfoCallback.Stub() {
+ public void onCellInfo(List<CellInfo> cellInfo) {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> callback.onCellInfo(cellInfo)));
+ }
+ }, getOpPackageName(), workSource);
+ } catch (RemoteException ex) {
}
}
@@ -6349,7 +6438,6 @@
/**
* Set the preferred network type.
- * Used for device configuration by some CDMA operators.
*
* <p>Requires Permission:
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
diff --git a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl b/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
index 07bb18b..34c9067 100644
--- a/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
+++ b/telephony/java/com/android/internal/telephony/IApnSourceService.aidl
@@ -20,5 +20,5 @@
interface IApnSourceService {
/** Retreive APNs. */
- ContentValues[] getApns();
+ ContentValues[] getApns(int subId);
}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 4bdec08..bc44519 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -165,13 +165,23 @@
int setOpportunistic(boolean opportunistic, int subId);
/**
- * Set parent subId by simInfo index
+ * Inform SubscriptionManager that subscriptions in the list are bundled
+ * as a group. Typically it's a primary subscription and an opportunistic
+ * subscription. It should only affect multi-SIM scenarios where primary
+ * and opportunistic subscriptions can be activated together.
+ * Being in the same group means they might be activated or deactivated
+ * together, some of them may be invisible to the users, etc.
*
- * @param parentSubId: subId of its parent subscription.
- * @param subId the unique SubscriptionInfo index in database
- * @return the number of records updated
+ * Caller will either have {@link android.Manifest.permission.MODIFY_PHONE_STATE}
+ * permission or can manage all subscriptions in the list, according to their
+ * acess rules.
+ *
+ * @param subIdList list of subId that will be in the same group
+ * @return groupUUID a UUID assigned to the subscription group. It returns
+ * null if fails.
+ *
*/
- int setParentSubId(int parentSubId, int subId);
+ String setSubscriptionGroup(in int[] subIdList, String callingPackage);
/**
* Set which subscription is preferred for cellular data. It's
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 85a9cf5..f0e8586 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -22,6 +22,7 @@
import android.os.IBinder;
import android.os.Messenger;
import android.os.ResultReceiver;
+import android.os.WorkSource;
import android.net.NetworkStats;
import android.net.Uri;
import android.service.carrier.CarrierIdentifier;
@@ -30,6 +31,7 @@
import android.telephony.CellInfo;
import android.telephony.ClientRequestStats;
import android.telephony.IccOpenLogicalChannelResponse;
+import android.telephony.ICellInfoCallback;
import android.telephony.ModemActivityInfo;
import android.telephony.NeighboringCellInfo;
import android.telephony.NetworkScanRequest;
@@ -507,11 +509,26 @@
int getLteOnCdmaModeForSubscriber(int subId, String callingPackage);
/**
- * Returns the all observed cell information of the device.
+ * Returns all observed cell information of the device.
*/
List<CellInfo> getAllCellInfo(String callingPkg);
/**
+ * Request a cell information update for the specified subscription,
+ * reported via the CellInfoCallback.
+ */
+ void requestCellInfoUpdate(int subId, in ICellInfoCallback cb, String callingPkg);
+
+ /**
+ * Request a cell information update for the specified subscription,
+ * reported via the CellInfoCallback.
+ *
+ * @param workSource the requestor to whom the power consumption for this should be attributed.
+ */
+ void requestCellInfoUpdateWithWorkSource(
+ int subId, in ICellInfoCallback cb, in String callingPkg, in WorkSource ws);
+
+ /**
* Sets minimum time in milli-seconds between onCellInfoChanged
*/
void setCellInfoListRate(int rateInMillis);
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 0bc5221..583f14a 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -431,7 +431,7 @@
void Debug::DumpResStringPool(const android::ResStringPool* pool, text::Printer* printer) {
using namespace android;
-
+
if (pool->getError() == NO_INIT) {
printer->Print("String pool is unitialized.\n");
return;
@@ -460,7 +460,7 @@
const size_t NS = pool->size();
for (size_t s=0; s<NS; s++) {
String8 str = pool->string8ObjectAt(s);
- printer->Print(StringPrintf("String #%zd : %s\n", s, str.string()));
+ printer->Print(StringPrintf("String #%zd: %s\n", s, str.string()));
}
}
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index dbe5ac5..da22e88 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -718,7 +718,7 @@
// This must be a FileReference.
std::unique_ptr<FileReference> file_ref =
util::make_unique<FileReference>(dst_pool->MakeRef(
- str, StringPool::Context(StringPool::Context::kHighPriority, config)));
+ str, StringPool::Context(StringPool::Context::kHighPriority, config), data));
if (type == ResourceType::kRaw) {
file_ref->type = ResourceFile::Type::kUnknown;
} else if (util::EndsWith(*file_ref->path, ".xml")) {
@@ -730,7 +730,7 @@
}
// There are no styles associated with this string, so treat it as a simple string.
- return util::make_unique<String>(dst_pool->MakeRef(str, StringPool::Context(config)));
+ return util::make_unique<String>(dst_pool->MakeRef(str, StringPool::Context(config), data));
}
} break;
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index 8eabd32..a8c2666 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -165,12 +165,13 @@
return MakeRefImpl(str, Context{}, true);
}
-StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context) {
- return MakeRefImpl(str, context, true);
+StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context,
+ Maybe<size_t> index) {
+ return MakeRefImpl(str, context, true, index);
}
StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context,
- bool unique) {
+ bool unique, Maybe<size_t> index) {
if (unique) {
auto range = indexed_strings_.equal_range(str);
for (auto iter = range.first; iter != range.second; ++iter) {
@@ -180,15 +181,26 @@
}
}
+ const size_t size = strings_.size();
+ // Insert the string at the end of the string vector if no index is specified
+ const size_t insertion_index = index ? index.value() : size;
+
std::unique_ptr<Entry> entry(new Entry());
entry->value = str.to_string();
entry->context = context;
- entry->index_ = strings_.size();
+ entry->index_ = insertion_index;
entry->ref_ = 0;
entry->pool_ = this;
Entry* borrow = entry.get();
- strings_.emplace_back(std::move(entry));
+ if (insertion_index == size) {
+ strings_.emplace_back(std::move(entry));
+ } else {
+ // Allocate enough space for the string at the index
+ strings_.resize(std::max(insertion_index + 1, size));
+ strings_[insertion_index] = std::move(entry);
+ }
+
indexed_strings_.insert(std::make_pair(StringPiece(borrow->value), borrow));
return Ref(borrow);
}
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 1006ca9..115d5d3 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -166,7 +166,8 @@
// Adds a string to the pool, unless it already exists, with a context object that can be used
// when sorting the string pool. Returns a reference to the string in the pool.
- Ref MakeRef(const android::StringPiece& str, const Context& context);
+ Ref MakeRef(const android::StringPiece& str, const Context& context,
+ Maybe<size_t> index = {});
// Adds a string from another string pool. Returns a reference to the string in the string pool.
Ref MakeRef(const Ref& ref);
@@ -210,7 +211,8 @@
static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag);
- Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique);
+ Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique,
+ Maybe<size_t> index = {});
void ReAssignIndices();
std::vector<std::unique_ptr<Entry>> strings_;
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index 9a7238b..648be7d 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -84,6 +84,24 @@
EXPECT_THAT(ref_c.index(), Eq(2u));
}
+TEST(StringPoolTest, AssignStringIndex) {
+ StringPool pool;
+
+ StringPool::Ref ref_a = pool.MakeRef("0", StringPool::Context{}, 0u);
+ StringPool::Ref ref_b = pool.MakeRef("1", StringPool::Context{}, 1u);
+ StringPool::Ref ref_c = pool.MakeRef("5", StringPool::Context{}, 5u);
+ StringPool::Ref ref_d = pool.MakeRef("2", StringPool::Context{}, 2u);
+ StringPool::Ref ref_e = pool.MakeRef("4", StringPool::Context{}, 4u);
+ StringPool::Ref ref_f = pool.MakeRef("3", StringPool::Context{}, 3u);
+
+ EXPECT_THAT(ref_a.index(), Eq(0u));
+ EXPECT_THAT(ref_b.index(), Eq(1u));
+ EXPECT_THAT(ref_d.index(), Eq(2u));
+ EXPECT_THAT(ref_f.index(), Eq(3u));
+ EXPECT_THAT(ref_e.index(), Eq(4u));
+ EXPECT_THAT(ref_c.index(), Eq(5u));
+}
+
TEST(StringPoolTest, PruneStringsWithNoReferences) {
StringPool pool;
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 3ea1755..4492f6b 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -57,78 +57,6 @@
Source source_;
};
-bool ConvertApk(IAaptContext* context, unique_ptr<LoadedApk> apk, IApkSerializer* serializer,
- IArchiveWriter* writer) {
- io::IFile* manifest = apk->GetFileCollection()->FindFile(kAndroidManifestPath);
- if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer,
- (manifest != nullptr && manifest->WasCompressed())
- ? ArchiveEntry::kCompress : 0u)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to serialize AndroidManifest.xml");
- return false;
- }
-
- if (apk->GetResourceTable() != nullptr) {
- // The table might be modified by below code.
- auto converted_table = apk->GetResourceTable();
-
- // Resources
- for (const auto& package : converted_table->packages) {
- for (const auto& type : package->types) {
- for (const auto& entry : type->entries) {
- for (const auto& config_value : entry->values) {
- FileReference* file = ValueCast<FileReference>(config_value->value.get());
- if (file != nullptr) {
- if (file->file == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "no file associated with " << *file);
- return false;
- }
-
- if (!serializer->SerializeFile(file, writer)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to serialize file " << *file->path);
- return false;
- }
- } // file
- } // config_value
- } // entry
- } // type
- } // package
-
- // Converted resource table
- if (!serializer->SerializeTable(converted_table, writer)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to serialize the resource table");
- return false;
- }
- }
-
- // Other files
- std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
- while (iterator->HasNext()) {
- io::IFile* file = iterator->Next();
- std::string path = file->GetSource().path;
-
- // Manifest, resource table and resources have already been taken care of.
- if (path == kAndroidManifestPath ||
- path == kApkResourceTablePath ||
- path == kProtoResourceTablePath ||
- path.find("res/") == 0) {
- continue;
- }
-
- if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to copy file " << path);
- return false;
- }
- }
-
- return true;
-}
-
-
class BinaryApkSerializer : public IApkSerializer {
public:
BinaryApkSerializer(IAaptContext* context, const Source& source,
@@ -323,12 +251,97 @@
StdErrDiagnostics diag_;
};
+int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer,
+ ApkFormat output_format, TableFlattenerOptions& options) {
+ // Do not change the ordering of strings in the values string pool
+ options.sort_stringpool_entries = false;
+
+ unique_ptr<IApkSerializer> serializer;
+ if (output_format == ApkFormat::kBinary) {
+ serializer.reset(new BinaryApkSerializer(context, apk->GetSource(), options));
+ } else if (output_format == ApkFormat::kProto) {
+ serializer.reset(new ProtoApkSerializer(context, apk->GetSource()));
+ } else {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "Cannot convert APK to unknown format");
+ return 1;
+ }
+
+ io::IFile* manifest = apk->GetFileCollection()->FindFile(kAndroidManifestPath);
+ if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/,
+ output_writer, (manifest != nullptr && manifest->WasCompressed())
+ ? ArchiveEntry::kCompress : 0u)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize AndroidManifest.xml");
+ return 1;
+ }
+
+ if (apk->GetResourceTable() != nullptr) {
+ // The table might be modified by below code.
+ auto converted_table = apk->GetResourceTable();
+
+ // Resources
+ for (const auto& package : converted_table->packages) {
+ for (const auto& type : package->types) {
+ for (const auto& entry : type->entries) {
+ for (const auto& config_value : entry->values) {
+ FileReference* file = ValueCast<FileReference>(config_value->value.get());
+ if (file != nullptr) {
+ if (file->file == nullptr) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "no file associated with " << *file);
+ return 1;
+ }
+
+ if (!serializer->SerializeFile(file, output_writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize file " << *file->path);
+ return 1;
+ }
+ } // file
+ } // config_value
+ } // entry
+ } // type
+ } // package
+
+ // Converted resource table
+ if (!serializer->SerializeTable(converted_table, output_writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize the resource table");
+ return 1;
+ }
+ }
+
+ // Other files
+ std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
+ while (iterator->HasNext()) {
+ io::IFile* file = iterator->Next();
+ std::string path = file->GetSource().path;
+
+ // Manifest, resource table and resources have already been taken care of.
+ if (path == kAndroidManifestPath ||
+ path == kApkResourceTablePath ||
+ path == kProtoResourceTablePath ||
+ path.find("res/") == 0) {
+ continue;
+ }
+
+ if (!io::CopyFileToArchivePreserveCompression(context, file, path, output_writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to copy file " << path);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
const char* ConvertCommand::kOutputFormatProto = "proto";
const char* ConvertCommand::kOutputFormatBinary = "binary";
int ConvertCommand::Action(const std::vector<std::string>& args) {
if (args.size() != 1) {
- std::cerr << "must supply a single proto APK\n";
+ std::cerr << "must supply a single APK\n";
Usage(&std::cerr);
return 1;
}
@@ -341,34 +354,31 @@
return 1;
}
- Maybe<AppInfo> app_info =
- ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
+ Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*apk->GetManifest(),
+ context.GetDiagnostics());
if (!app_info) {
return 1;
}
context.package_ = app_info.value().package;
-
- unique_ptr<IArchiveWriter> writer =
- CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path_);
+ unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(context.GetDiagnostics(),
+ output_path_);
if (writer == nullptr) {
return 1;
}
- unique_ptr<IApkSerializer> serializer;
+ ApkFormat format;
if (!output_format_ || output_format_.value() == ConvertCommand::kOutputFormatBinary) {
-
- serializer.reset(new BinaryApkSerializer(&context, apk->GetSource(), options_));
+ format = ApkFormat::kBinary;
} else if (output_format_.value() == ConvertCommand::kOutputFormatProto) {
- serializer.reset(new ProtoApkSerializer(&context, apk->GetSource()));
+ format = ApkFormat::kProto;
} else {
- context.GetDiagnostics()->Error(DiagMessage(path)
- << "Invalid value for flag --output-format: "
- << output_format_.value());
+ context.GetDiagnostics()->Error(DiagMessage(path) << "Invalid value for flag --output-format: "
+ << output_format_.value());
return 1;
}
- return ConvertApk(&context, std::move(apk), serializer.get(), writer.get()) ? 0 : 1;
+ return Convert(&context, apk.get(), writer.get(), format, options_);
}
} // namespace aapt
diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h
index fcec23d..6a6719c 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -18,6 +18,7 @@
#define AAPT2_CONVERT_H
#include "Command.h"
+#include "LoadedApk.h"
#include "format/binary/TableFlattener.h"
namespace aapt {
@@ -49,6 +50,9 @@
bool verbose_ = false;
};
-}// namespace aapt
+int Convert(IAaptContext* context, LoadedApk* input, IArchiveWriter* output_writer,
+ ApkFormat output_format, TableFlattenerOptions& options);
+
+} // namespace aapt
#endif //AAPT2_CONVERT_H
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 8a86f63a..6c1a9ba 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -573,15 +573,17 @@
} // namespace
bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
- // We must do this before writing the resources, since the string pool IDs may change.
- table->string_pool.Prune();
- table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int {
- int diff = util::compare(a.priority, b.priority);
- if (diff == 0) {
- diff = a.config.compare(b.config);
- }
- return diff;
- });
+ if (options_.sort_stringpool_entries) {
+ // We must do this before writing the resources, since the string pool IDs may change.
+ table->string_pool.Prune();
+ table->string_pool.Sort([](const StringPool::Context &a, const StringPool::Context &b) -> int {
+ int diff = util::compare(a.priority, b.priority);
+ if (diff == 0) {
+ diff = a.config.compare(b.config);
+ }
+ return diff;
+ });
+ }
// Write the ResTable header.
ChunkWriter table_writer(buffer_);
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index c2e1d4b..635cb21 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -43,6 +43,9 @@
// Set of whitelisted resource names to avoid altering in key stringpool
std::set<std::string> whitelisted_resources;
+
+ // When true, sort the entries in the values string pool by priority and configuration.
+ bool sort_stringpool_entries = true;
};
class TableFlattener : public IResourceTableConsumer {
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 1fd68ec..92f60ef 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -61,11 +61,9 @@
ParceledListSlice getPrivilegedConfiguredNetworks();
- WifiConfiguration getMatchingWifiConfig(in ScanResult scanResult);
+ List<WifiConfiguration> getAllMatchingWifiConfigs(in List<ScanResult> scanResult);
- List<WifiConfiguration> getAllMatchingWifiConfigs(in ScanResult scanResult);
-
- List<OsuProvider> getMatchingOsuProviders(in ScanResult scanResult);
+ List<OsuProvider> getMatchingOsuProviders(in List<ScanResult> scanResult);
int addOrUpdateNetwork(in WifiConfiguration config, String packageName);
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 7919074..f934380 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1073,55 +1073,43 @@
}
/**
- * Returns a WifiConfiguration matching this ScanResult
- *
- * @param scanResult scanResult that represents the BSSID
- * @return {@link WifiConfiguration} that matches this BSSID or null
- * @throws UnsupportedOperationException if Passpoint is not enabled on the device.
- * @hide
- */
- @UnsupportedAppUsage
- public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
- try {
- return mService.getMatchingWifiConfig(scanResult);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Return all matching WifiConfigurations for this ScanResult.
+ * Returns all matching WifiConfigurations for a given list of ScanResult.
*
* An empty list will be returned when no configurations are installed or if no configurations
* match the ScanResult.
- *
- * @param scanResult scanResult that represents the BSSID
- * @return A list of {@link WifiConfiguration}
+
+ * @param scanResults a list of scanResult that represents the BSSID
+ * @return A list of {@link WifiConfiguration} that can have duplicate entries.
* @throws UnsupportedOperationException if Passpoint is not enabled on the device.
* @hide
*/
- public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public List<WifiConfiguration> getAllMatchingWifiConfigs(
+ @NonNull List<ScanResult> scanResults) {
try {
- return mService.getAllMatchingWifiConfigs(scanResult);
+ return mService.getAllMatchingWifiConfigs(scanResults);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
-
/**
- * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP.
+ * Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given
+ * list of ScanResult.
*
* An empty list will be returned if no match is found.
*
- * @param scanResult scanResult that represents the BSSID
- * @return list of {@link OsuProvider}
+ * @param scanResults a list of ScanResult
+ * @return A list of {@link OsuProvider} that does not contain duplicate entries.
* @throws UnsupportedOperationException if Passpoint is not enabled on the device.
* @hide
*/
- public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public List<OsuProvider> getMatchingOsuProviders(List<ScanResult> scanResults) {
try {
- return mService.getMatchingOsuProviders(scanResult);
+ return mService.getMatchingOsuProviders(scanResults);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index e6892be..f58a006 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -491,6 +491,17 @@
/** @hide */
public static final int FACTORY_RESET_SUCCEEDED = BASE + 84;
+ /** @hide */
+ public static final int REQUEST_ONGOING_PEER_CONFIG = BASE + 85;
+ /** @hide */
+ public static final int RESPONSE_ONGOING_PEER_CONFIG = BASE + 86;
+ /** @hide */
+ public static final int SET_ONGOING_PEER_CONFIG = BASE + 87;
+ /** @hide */
+ public static final int SET_ONGOING_PEER_CONFIG_FAILED = BASE + 88;
+ /** @hide */
+ public static final int SET_ONGOING_PEER_CONFIG_SUCCEEDED = BASE + 89;
+
/**
* Create a new WifiP2pManager instance. Applications use
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -680,6 +691,18 @@
}
/**
+ * Interface for callback invocation when ongoing peer info is available
+ * @hide
+ */
+ public interface OngoingPeerInfoListener {
+ /**
+ * The requested ongoing WifiP2pConfig is available
+ * @param peerConfig WifiP2pConfig for current connecting session
+ */
+ void onOngoingPeerAvailable(WifiP2pConfig peerConfig);
+ }
+
+ /**
* A channel that connects the application to the Wifi p2p framework.
* Most p2p operations require a Channel as an argument. An instance of Channel is obtained
* by doing a call on {@link #initialize}
@@ -787,6 +810,7 @@
case SET_CHANNEL_FAILED:
case REPORT_NFC_HANDOVER_FAILED:
case FACTORY_RESET_FAILED:
+ case SET_ONGOING_PEER_CONFIG_FAILED:
if (listener != null) {
((ActionListener) listener).onFailure(message.arg1);
}
@@ -814,6 +838,7 @@
case SET_CHANNEL_SUCCEEDED:
case REPORT_NFC_HANDOVER_SUCCEEDED:
case FACTORY_RESET_SUCCEEDED:
+ case SET_ONGOING_PEER_CONFIG_SUCCEEDED:
if (listener != null) {
((ActionListener) listener).onSuccess();
}
@@ -857,6 +882,13 @@
.onHandoverMessageAvailable(handoverMessage);
}
break;
+ case RESPONSE_ONGOING_PEER_CONFIG:
+ WifiP2pConfig peerConfig = (WifiP2pConfig) message.obj;
+ if (listener != null) {
+ ((OngoingPeerInfoListener) listener)
+ .onOngoingPeerAvailable(peerConfig);
+ }
+ break;
default:
Log.d(TAG, "Ignored " + message);
break;
@@ -1536,6 +1568,7 @@
/**
* Removes all saved p2p groups.
+ *
* @param c is the channel created at {@link #initialize}.
* @param listener for callback on success or failure. Can be null.
* @hide
@@ -1550,4 +1583,37 @@
callingPackage);
}
+ /**
+ * Request saved WifiP2pConfig which used for an ongoing peer connection
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callback when ongoing peer config updated. Can't be null.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public void requestOngoingPeerConfig(@NonNull Channel c,
+ @NonNull OngoingPeerInfoListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(REQUEST_ONGOING_PEER_CONFIG,
+ Binder.getCallingUid(), c.putListener(listener));
+ }
+
+ /**
+ * Set saved WifiP2pConfig which used for an ongoing peer connection
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param config used for change an ongoing peer connection
+ * @param listener for callback when ongoing peer config updated. Can be null.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
+ public void setOngoingPeerConfig(@NonNull Channel c, @NonNull WifiP2pConfig config,
+ @Nullable ActionListener listener) {
+ checkChannel(c);
+ checkP2pConfig(config);
+ c.mAsyncChannel.sendMessage(SET_ONGOING_PEER_CONFIG, 0,
+ c.putListener(listener), config);
+ }
}
diff --git a/wifi/java/com/android/server/wifi/AbstractWifiService.java b/wifi/java/com/android/server/wifi/AbstractWifiService.java
index eede23b..e532428 100644
--- a/wifi/java/com/android/server/wifi/AbstractWifiService.java
+++ b/wifi/java/com/android/server/wifi/AbstractWifiService.java
@@ -25,7 +25,6 @@
import android.net.wifi.ISoftApCallback;
import android.net.wifi.ITrafficStateCallback;
import android.net.wifi.IWifiManager;
-import android.net.wifi.PasspointManagementObjectDefinition;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiActivityEnergyInfo;
import android.net.wifi.WifiConfiguration;
@@ -37,7 +36,6 @@
import android.os.Messenger;
import android.os.ResultReceiver;
import android.os.WorkSource;
-import android.util.Slog;
import java.util.List;
@@ -83,22 +81,51 @@
throw new UnsupportedOperationException();
}
- @Override
+ /**
+ * Returns a WifiConfiguration matching this ScanResult
+ * @param scanResult a single ScanResult Object
+ * @return
+ * @deprecated use {@link #getAllMatchingWifiConfigs(List)} instead.
+ */
+ @Deprecated
public WifiConfiguration getMatchingWifiConfig(ScanResult scanResult) {
throw new UnsupportedOperationException();
}
- @Override
+ /**
+ * Returns all matching WifiConfigurations for this ScanResult.
+ * @param scanResult a single ScanResult Object
+ * @return
+ * @deprecated use {@link #getAllMatchingWifiConfigs(List)} instead.
+ */
+ @Deprecated
public List<WifiConfiguration> getAllMatchingWifiConfigs(ScanResult scanResult) {
throw new UnsupportedOperationException();
}
@Override
+ public List<WifiConfiguration> getAllMatchingWifiConfigs(List<ScanResult> scanResults) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns a list of Hotspot 2.0 OSU (Online Sign-Up) providers associated with the given AP.
+ *
+ * @param scanResult a single ScanResult Object
+ * @return
+ * @deprecated use {@link #getMatchingOsuProviders(List)} instead.
+ */
+ @Deprecated
public List<OsuProvider> getMatchingOsuProviders(ScanResult scanResult) {
throw new UnsupportedOperationException();
}
@Override
+ public List<OsuProvider> getMatchingOsuProviders(List<ScanResult> scanResults) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public int addOrUpdateNetwork(WifiConfiguration config, String packageName) {
throw new UnsupportedOperationException();
}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index ea41bb3..a3deb93 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -35,7 +35,20 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyList;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -62,6 +75,7 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.List;
/**
* Unit tests for {@link android.net.wifi.WifiManager}.
@@ -1250,4 +1264,26 @@
userSelectionCallbackCaptor.getValue().reject();
verify(iUserSelectionCallback).reject();
}
+
+ /**
+ * Check the call to getAllMatchingWifiConfigs calls getAllMatchingWifiConfigs of WifiService
+ * with the provided a list of ScanResult.
+ */
+ @Test
+ public void testGetAllMatchingWifiConfigs() throws Exception {
+ mWifiManager.getAllMatchingWifiConfigs(new ArrayList<>());
+
+ verify(mWifiService).getAllMatchingWifiConfigs(any(List.class));
+ }
+
+ /**
+ * Check the call to getMatchingOsuProviders calls getMatchingOsuProviders of WifiService
+ * with the provided a list of ScanResult.
+ */
+ @Test
+ public void testGetMatchingOsuProviders() throws Exception {
+ mWifiManager.getMatchingOsuProviders(new ArrayList<>());
+
+ verify(mWifiService).getMatchingOsuProviders(any(List.class));
+ }
}