Merge "passpoint-r2: define getMatchingOsuProviders(List<ScanResult>) API"
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 e2d31d9..b458fac 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -24465,6 +24465,7 @@
}
public static final class MediaExtractor.CasInfo {
+ method public byte[] getPrivateData();
method public android.media.MediaCas.Session getSession();
method public int getSystemId();
}
@@ -43556,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();
@@ -43563,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();
@@ -43597,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>);
@@ -43728,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);
@@ -43830,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);
@@ -54359,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 {
@@ -54369,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 fc2096d..4bd6220 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);
@@ -3647,12 +3648,9 @@
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);
@@ -4112,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);
@@ -5699,6 +5698,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 e6efc4c..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;
@@ -494,7 +495,6 @@
optional State state = 3;
}
-
/**
* Logs when GPS state changes.
*
@@ -511,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.
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/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/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/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/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_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/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/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/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/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/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/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/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..ab80e252 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 b5cbf3b..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>
*
@@ -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/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 {