Merge "More granular reporting of size configurations."
diff --git a/Android.mk b/Android.mk
index 5713f3e..f0c0117 100644
--- a/Android.mk
+++ b/Android.mk
@@ -68,6 +68,8 @@
core/java/android/app/IActivityContainerCallback.aidl \
core/java/android/app/IActivityController.aidl \
core/java/android/app/IActivityPendingResult.aidl \
+ core/java/android/app/IAlarmCompleteListener.aidl \
+ core/java/android/app/IAlarmListener.aidl \
core/java/android/app/IAlarmManager.aidl \
core/java/android/app/IAppTask.aidl \
core/java/android/app/ITaskStackListener.aidl \
diff --git a/api/current.txt b/api/current.txt
index eb888af..0bbf2f9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3764,17 +3764,21 @@
public class AlarmManager {
method public void cancel(android.app.PendingIntent);
+ method public void cancel(android.app.AlarmManager.OnAlarmListener);
method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
method public void set(int, long, android.app.PendingIntent);
+ method public void set(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
method public void setExact(int, long, android.app.PendingIntent);
+ method public void setExact(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
method public void setInexactRepeating(int, long, long, android.app.PendingIntent);
method public void setRepeating(int, long, long, android.app.PendingIntent);
method public void setTime(long);
method public void setTimeZone(java.lang.String);
method public void setWindow(int, long, long, android.app.PendingIntent);
+ method public void setWindow(int, long, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
field public static final java.lang.String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
field public static final int ELAPSED_REALTIME = 3; // 0x3
field public static final int ELAPSED_REALTIME_WAKEUP = 2; // 0x2
@@ -3796,6 +3800,10 @@
field public static final android.os.Parcelable.Creator<android.app.AlarmManager.AlarmClockInfo> CREATOR;
}
+ public static abstract interface AlarmManager.OnAlarmListener {
+ method public abstract void onAlarm();
+ }
+
public class AlertDialog extends android.app.Dialog implements android.content.DialogInterface {
ctor protected AlertDialog(android.content.Context);
ctor protected AlertDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
@@ -5881,6 +5889,7 @@
method public java.lang.String getStructuredData();
method public android.net.Uri getWebUri();
method public boolean isAppProvidedIntent();
+ method public boolean isAppProvidedWebUri();
method public void setClipData(android.content.ClipData);
method public void setIntent(android.content.Intent);
method public void setStructuredData(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index d288ad1..c5ad20d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3873,18 +3873,22 @@
public class AlarmManager {
method public void cancel(android.app.PendingIntent);
+ method public void cancel(android.app.AlarmManager.OnAlarmListener);
method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
method public void set(int, long, android.app.PendingIntent);
+ method public void set(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
method public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource);
method public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
method public void setExact(int, long, android.app.PendingIntent);
+ method public void setExact(int, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
method public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
method public void setInexactRepeating(int, long, long, android.app.PendingIntent);
method public void setRepeating(int, long, long, android.app.PendingIntent);
method public void setTime(long);
method public void setTimeZone(java.lang.String);
method public void setWindow(int, long, long, android.app.PendingIntent);
+ method public void setWindow(int, long, long, java.lang.String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
field public static final java.lang.String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
field public static final int ELAPSED_REALTIME = 3; // 0x3
field public static final int ELAPSED_REALTIME_WAKEUP = 2; // 0x2
@@ -3906,6 +3910,10 @@
field public static final android.os.Parcelable.Creator<android.app.AlarmManager.AlarmClockInfo> CREATOR;
}
+ public static abstract interface AlarmManager.OnAlarmListener {
+ method public abstract void onAlarm();
+ }
+
public class AlertDialog extends android.app.Dialog implements android.content.DialogInterface {
ctor protected AlertDialog(android.content.Context);
ctor protected AlertDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
@@ -6014,6 +6022,7 @@
method public java.lang.String getStructuredData();
method public android.net.Uri getWebUri();
method public boolean isAppProvidedIntent();
+ method public boolean isAppProvidedWebUri();
method public void setClipData(android.content.ClipData);
method public void setIntent(android.content.Intent);
method public void setStructuredData(java.lang.String);
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index d46bab2..da743f8 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -116,21 +116,22 @@
static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
String tag) {
try {
- getDefault().noteWakeupAlarm(ps.getTarget(), sourceUid, sourcePkg, tag);
+ getDefault().noteWakeupAlarm((ps != null) ? ps.getTarget() : null,
+ sourceUid, sourcePkg, tag);
} catch (RemoteException ex) {
}
}
static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
try {
- getDefault().noteAlarmStart(ps.getTarget(), sourceUid, tag);
+ getDefault().noteAlarmStart((ps != null) ? ps.getTarget() : null, sourceUid, tag);
} catch (RemoteException ex) {
}
}
static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
try {
- getDefault().noteAlarmFinish(ps.getTarget(), sourceUid, tag);
+ getDefault().noteAlarmFinish((ps != null) ? ps.getTarget() : null, sourceUid, tag);
} catch (RemoteException ex) {
}
}
diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java
index fb03b62..3dc9582 100644
--- a/core/java/android/app/AlarmManager.java
+++ b/core/java/android/app/AlarmManager.java
@@ -21,15 +21,21 @@
import android.content.Context;
import android.content.Intent;
import android.os.Build;
+import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.text.TextUtils;
+import android.util.Log;
+
import libcore.util.ZoneInfoDB;
import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
/**
* This class provides access to the system alarm services. These allow you
@@ -72,6 +78,8 @@
* Context.getSystemService(Context.ALARM_SERVICE)}.
*/
public class AlarmManager {
+ private static final String TAG = "AlarmManager";
+
/**
* Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
* (wall clock time in UTC), which will wake up the device when
@@ -160,9 +168,89 @@
public static final int FLAG_IDLE_UNTIL = 1<<4;
private final IAlarmManager mService;
+ private final String mPackageName;
private final boolean mAlwaysExact;
private final int mTargetSdkVersion;
+ private final Handler mMainThreadHandler;
+ /**
+ * Direct-notification alarms: the requester must be running continuously from the
+ * time the alarm is set to the time it is delivered, or delivery will fail. Only
+ * one-shot alarms can be set using this mechanism, not repeating alarms.
+ */
+ public interface OnAlarmListener {
+ /**
+ * Callback method that is invoked by the system when the alarm time is reached.
+ */
+ public void onAlarm();
+ }
+
+ final class ListenerWrapper extends IAlarmListener.Stub implements Runnable {
+ final OnAlarmListener mListener;
+ Handler mHandler;
+ IAlarmCompleteListener mCompletion;
+
+ public ListenerWrapper(OnAlarmListener listener) {
+ mListener = listener;
+ }
+
+ public void setHandler(Handler h) {
+ mHandler = h;
+ }
+
+ public void cancel() {
+ try {
+ mService.remove(null, this);
+ } catch (RemoteException ex) {
+ }
+
+ synchronized (AlarmManager.class) {
+ if (sWrappers != null) {
+ sWrappers.remove(mListener);
+ }
+ }
+ }
+
+ @Override
+ public void doAlarm(IAlarmCompleteListener alarmManager) {
+ mCompletion = alarmManager;
+ mHandler.post(this);
+ }
+
+ @Override
+ public void run() {
+ // Remove this listener from the wrapper cache first; the server side
+ // already considers it gone
+ synchronized (AlarmManager.class) {
+ if (sWrappers != null) {
+ sWrappers.remove(mListener);
+ }
+ }
+
+ // Now deliver it to the app
+ try {
+ mListener.onAlarm();
+ } finally {
+ // No catch -- make sure to report completion to the system process,
+ // but continue to allow the exception to crash the app.
+
+ try {
+ mCompletion.alarmComplete(this);
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to report completion to Alarm Manager!", e);
+ }
+ }
+ }
+ }
+
+ // Tracking of the OnAlarmListener -> wrapper mapping, for cancel() support.
+ // Access is synchronized on the AlarmManager class object.
+ //
+ // These are weak references so that we don't leak listener references if, for
+ // example, the pending-alarm messages are posted to a HandlerThread that is
+ // disposed of prior to alarm delivery. The underlying messages will be GC'd
+ // but this static reference would still persist, orphaned, never deallocated.
+ private static WeakHashMap<OnAlarmListener, WeakReference<ListenerWrapper>> sWrappers;
/**
* package private on purpose
@@ -170,8 +258,10 @@
AlarmManager(IAlarmManager service, Context ctx) {
mService = service;
+ mPackageName = ctx.getPackageName();
mTargetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
mAlwaysExact = (mTargetSdkVersion < Build.VERSION_CODES.KITKAT);
+ mMainThreadHandler = new Handler(ctx.getMainLooper());
}
private long legacyExactLength() {
@@ -249,7 +339,37 @@
* @see #RTC_WAKEUP
*/
public void set(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null);
+ setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,
+ null, null, null);
+ }
+
+ /**
+ * Direct callback version of {@link #set(int, long, PendingIntent)}. Rather than
+ * supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ *
+ * @param type One of {@link #ELAPSED_REALTIME}, {@link #ELAPSED_REALTIME_WAKEUP},
+ * {@link #RTC}, or {@link #RTC_WAKEUP}.
+ * @param triggerAtMillis time in milliseconds that the alarm should go
+ * off, using the appropriate clock (depending on the alarm type).
+ * @param tag string describing the alarm, used for logging and battery-use
+ * attribution
+ * @param listener {@link OnAlarmListener} instance whose
+ * {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * called when the alarm time is reached. A given OnAlarmListener instance can
+ * only be the target of a single pending alarm, just as a given PendingIntent
+ * can only be used with one alarm at a time.
+ * @param targetHandler {@link Handler} on which to execute the listener's onAlarm()
+ * callback, or {@code null} to run that callback on the main looper.
+ */
+ public void set(int type, long triggerAtMillis, String tag, OnAlarmListener listener,
+ Handler targetHandler) {
+ setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, null, listener, tag,
+ targetHandler, null, null);
}
/**
@@ -310,8 +430,8 @@
*/
public void setRepeating(int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation, null,
- null);
+ setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation,
+ null, null, null, null, null);
}
/**
@@ -361,7 +481,23 @@
*/
public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
PendingIntent operation) {
- setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation, null, null);
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation,
+ null, null, null, null, null);
+ }
+
+ /**
+ * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}. Rather
+ * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ */
+ public void setWindow(int type, long windowStartMillis, long windowLengthMillis,
+ String tag, OnAlarmListener listener, Handler targetHandler) {
+ setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
+ targetHandler, null, null);
}
/**
@@ -399,7 +535,23 @@
* @see #RTC_WAKEUP
*/
public void setExact(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null);
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null,
+ null, null);
+ }
+
+ /**
+ * Direct callback version of {@link #setExact(int, long, PendingIntent)}. Rather
+ * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+ * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+ * <p>
+ * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ */
+ public void setExact(int type, long triggerAtMillis, String tag, OnAlarmListener listener,
+ Handler targetHandler) {
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, null, listener, tag,
+ targetHandler, null, null);
}
/**
@@ -408,7 +560,8 @@
* @hide
*/
public void setIdleUntil(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, operation, null, null);
+ setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, operation,
+ null, null, null, null, null);
}
/**
@@ -436,19 +589,38 @@
* @see android.content.Intent#filterEquals
*/
public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
- setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation, null, info);
+ setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
+ null, null, null, null, info);
}
/** @hide */
@SystemApi
public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
PendingIntent operation, WorkSource workSource) {
- setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, workSource,
- null);
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null,
+ null, workSource, null);
+ }
+
+ /**
+ * Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
+ * Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
+ * <p>
+ * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+ * invoked via the specified target Handler, or on the application's main looper
+ * if {@code null} is passed as the {@code targetHandler} parameter.
+ *
+ * @hide
+ */
+ //@SystemApi
+ public void set(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
+ OnAlarmListener listener, Handler targetHandler, WorkSource workSource) {
+ setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null,
+ targetHandler, workSource, null);
}
private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
- int flags, PendingIntent operation, WorkSource workSource, AlarmClockInfo alarmClock) {
+ int flags, PendingIntent operation, final OnAlarmListener listener, String listenerTag,
+ Handler targetHandler, WorkSource workSource, AlarmClockInfo alarmClock) {
if (triggerAtMillis < 0) {
/* NOTYET
if (mAlwaysExact) {
@@ -460,9 +632,30 @@
triggerAtMillis = 0;
}
+ ListenerWrapper recipientWrapper = null;
+ if (listener != null) {
+ synchronized (AlarmManager.class) {
+ if (sWrappers == null) {
+ sWrappers = new WeakHashMap<OnAlarmListener, WeakReference<ListenerWrapper>>();
+ }
+
+ WeakReference<ListenerWrapper> wrapperRef = sWrappers.get(listener);
+ // no existing wrapper *or* we've lost our weak ref to it => build a new one
+ if (wrapperRef == null ||
+ (recipientWrapper = wrapperRef.get()) == null) {
+ recipientWrapper = new ListenerWrapper(listener);
+ wrapperRef = new WeakReference<ListenerWrapper>(recipientWrapper);
+ sWrappers.put(listener, wrapperRef);
+ }
+ }
+
+ final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler;
+ recipientWrapper.setHandler(handler);
+ }
+
try {
- mService.set(type, triggerAtMillis, windowMillis, intervalMillis, flags, operation,
- workSource, alarmClock);
+ mService.set(mPackageName, type, triggerAtMillis, windowMillis, intervalMillis, flags,
+ operation, recipientWrapper, listenerTag, workSource, alarmClock);
} catch (RemoteException ex) {
}
}
@@ -562,7 +755,8 @@
*/
public void setInexactRepeating(int type, long triggerAtMillis,
long intervalMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null, null);
+ setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null,
+ null, null, null, null);
}
/**
@@ -611,8 +805,8 @@
* @see #RTC_WAKEUP
*/
public void setAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) {
- setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE, operation,
- null, null);
+ setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE,
+ operation, null, null, null, null, null);
}
/**
@@ -666,7 +860,7 @@
*/
public void setExactAndAllowWhileIdle(int type, long triggerAtMillis, PendingIntent operation) {
setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
- null, null);
+ null, null, null, null, null);
}
/**
@@ -680,13 +874,44 @@
* @see #set
*/
public void cancel(PendingIntent operation) {
+ if (operation == null) {
+ throw new NullPointerException("operation");
+ }
+
try {
- mService.remove(operation);
+ mService.remove(operation, null);
} catch (RemoteException ex) {
}
}
/**
+ * Remove any alarm scheduled to be delivered to the given {@link OnAlarmListener}.
+ *
+ * @param listener OnAlarmListener instance that is the target of a currently-set alarm.
+ */
+ public void cancel(OnAlarmListener listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener");
+ }
+
+ ListenerWrapper wrapper = null;
+ synchronized (AlarmManager.class) {
+ final WeakReference<ListenerWrapper> wrapperRef;
+ wrapperRef = sWrappers.get(listener);
+ if (wrapperRef != null) {
+ wrapper = wrapperRef.get();
+ }
+ }
+
+ if (wrapper == null) {
+ Log.w(TAG, "Unrecognized alarm listener " + listener);
+ return;
+ }
+
+ wrapper.cancel();
+ }
+
+ /**
* Set the system wall clock time.
* Requires the permission android.permission.SET_TIME.
*
diff --git a/core/java/android/app/IAlarmCompleteListener.aidl b/core/java/android/app/IAlarmCompleteListener.aidl
new file mode 100644
index 0000000..9f9ee40
--- /dev/null
+++ b/core/java/android/app/IAlarmCompleteListener.aidl
@@ -0,0 +1,27 @@
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+package android.app;
+
+import android.os.IBinder;
+
+/**
+ * Callback from app into system process to indicate that processing of
+ * a direct-call alarm has completed.
+ * {@hide}
+ */
+interface IAlarmCompleteListener {
+ void alarmComplete(in IBinder who);
+}
diff --git a/core/java/android/app/IAlarmListener.aidl b/core/java/android/app/IAlarmListener.aidl
new file mode 100644
index 0000000..a110d4d
--- /dev/null
+++ b/core/java/android/app/IAlarmListener.aidl
@@ -0,0 +1,29 @@
+/*
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+package android.app;
+
+import android.app.IAlarmCompleteListener;
+
+/**
+ * System private API for direct alarm callbacks (non-broadcast deliver). See the
+ * AlarmManager#set() variants that take an AlarmReceiver callback object
+ * rather than a PendingIntent.
+ *
+ * {@hide}
+ */
+oneway interface IAlarmListener {
+ void doAlarm(in IAlarmCompleteListener callback);
+}
diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl
index 327c00b..7b05b49 100644
--- a/core/java/android/app/IAlarmManager.aidl
+++ b/core/java/android/app/IAlarmManager.aidl
@@ -17,7 +17,9 @@
package android.app;
import android.app.AlarmManager;
+import android.app.IAlarmListener;
import android.app.PendingIntent;
+import android.content.Intent;
import android.os.WorkSource;
/**
@@ -27,14 +29,12 @@
*/
interface IAlarmManager {
/** windowLength == 0 means exact; windowLength < 0 means the let the OS decide */
- void set(int type, long triggerAtTime, long windowLength,
- long interval, int flags, in PendingIntent operation, in WorkSource workSource,
- in AlarmManager.AlarmClockInfo alarmClock);
+ void set(String callingPackage, int type, long triggerAtTime, long windowLength,
+ long interval, int flags, in PendingIntent operation, in IAlarmListener listener,
+ String listenerTag, in WorkSource workSource, in AlarmManager.AlarmClockInfo alarmClock);
boolean setTime(long millis);
void setTimeZone(String zone);
- void remove(in PendingIntent operation);
+ void remove(in PendingIntent operation, in IAlarmListener listener);
long getNextWakeFromIdleTime();
AlarmManager.AlarmClockInfo getNextAlarmClock(int userId);
}
-
-
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index e95a35a..30232da 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -83,10 +83,8 @@
int getZenMode();
ZenModeConfig getZenModeConfig();
- boolean setZenModeConfig(in ZenModeConfig config, String reason);
oneway void setZenMode(int mode, in Uri conditionId, String reason);
oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
- oneway void requestZenModeConditions(in IConditionListener callback, int relevance);
boolean isNotificationPolicyAccessGranted(String pkg);
NotificationManager.Policy getNotificationPolicy(String pkg);
void setNotificationPolicy(String pkg, in NotificationManager.Policy policy);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f75b22a..07b4d39 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -348,29 +348,6 @@
/**
* @hide
*/
- public boolean setZenModeConfig(ZenModeConfig config, String reason) {
- INotificationManager service = getService();
- try {
- return service.setZenModeConfig(config, reason);
- } catch (RemoteException e) {
- return false;
- }
- }
-
- /**
- * @hide
- */
- public void requestZenModeConditions(IConditionListener listener, int relevance) {
- INotificationManager service = getService();
- try {
- service.requestZenModeConditions(listener, relevance);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * @hide
- */
public int getZenMode() {
INotificationManager service = getService();
try {
diff --git a/core/java/android/app/assist/AssistContent.java b/core/java/android/app/assist/AssistContent.java
index cddf47a..39902d7 100644
--- a/core/java/android/app/assist/AssistContent.java
+++ b/core/java/android/app/assist/AssistContent.java
@@ -14,6 +14,7 @@
*/
public class AssistContent implements Parcelable {
private boolean mIsAppProvidedIntent = false;
+ private boolean mIsAppProvidedWebUri = false;
private Intent mIntent;
private String mStructuredData;
private ClipData mClipData;
@@ -39,7 +40,7 @@
Uri uri = intent.getData();
if (uri != null) {
if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) {
- setWebUri(uri);
+ mUri = uri;
}
}
}
@@ -116,6 +117,7 @@
* leave the null and only report the local intent and clip data.
*/
public void setWebUri(Uri uri) {
+ mIsAppProvidedWebUri = true;
mUri = uri;
}
@@ -128,6 +130,16 @@
}
/**
+ * Returns whether or not the current {@link #getWebUri} was explicitly provided in
+ * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}. If not,
+ * the Intent was automatically set based on
+ * {@link android.app.Activity#getIntent Activity.getIntent}.
+ */
+ public boolean isAppProvidedWebUri() {
+ return mIsAppProvidedWebUri;
+ }
+
+ /**
* Return Bundle for extra vendor-specific data that can be modified and examined.
*/
public Bundle getExtras() {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 6740d43..d7e94d0 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1658,6 +1658,14 @@
= "android.intent.extra.GET_PERMISSIONS_APP_LABEL_LIST_RESULT";
/**
+ * Boolean list describing if the app is a system app for apps that have one or more runtime
+ * permissions.
+ * @hide
+ */
+ public static final String EXTRA_GET_PERMISSIONS_IS_SYSTEM_APP_LIST_RESULT
+ = "android.intent.extra.GET_PERMISSIONS_IS_SYSTEM_APP_LIST_RESULT";
+
+ /**
* Required extra to be sent with {@link #ACTION_GET_PERMISSIONS_COUNT} broadcasts.
* @hide
*/
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 17e1a28..2e31ab6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -533,14 +533,9 @@
/** @hide */
public static final int PIN_VERIFICATION_SUCCESS = -1;
- private static UserManager sInstance = null;
-
/** @hide */
- public synchronized static UserManager get(Context context) {
- if (sInstance == null) {
- sInstance = (UserManager) context.getSystemService(Context.USER_SERVICE);
- }
- return sInstance;
+ public static UserManager get(Context context) {
+ return (UserManager) context.getSystemService(Context.USER_SERVICE);
}
/** @hide */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a1f9743..ad46c3d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5604,11 +5604,6 @@
public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES =
"enabled_notification_policy_access_packages";
- /**
- * @hide
- */
- public static final String ENABLED_CONDITION_PROVIDERS = "enabled_condition_providers";
-
/** @hide */
public static final String BAR_SERVICE_COMPONENT = "bar_service_component";
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index c679eda..bbac023 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -35,7 +35,8 @@
* the {@link android.Manifest.permission#BIND_CONDITION_PROVIDER_SERVICE} permission
* and include an intent filter with the {@link #SERVICE_INTERFACE} action. If you want users to be
* able to create and update conditions for this service to monitor, include the
- * {@link #META_DATA_RULE_TYPE} and {@link #META_DATA_CONFIGURATION_ACTIVITY} tags. For example:</p>
+ * {@link #META_DATA_RULE_TYPE} and {@link #META_DATA_CONFIGURATION_ACTIVITY} tags and request the
+ * {@link android.Manifest.permission#ACCESS_NOTIFICATION_POLICY} permission. For example:</p>
* <pre>
* <service android:name=".MyConditionProvider"
* android:label="@string/service_name"
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index 4a1d7e7..c9586e4 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -42,15 +42,14 @@
static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
-// Time to wait between animation frames.
-static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60;
-
// Time to spend fading out the spot completely.
static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
// Time to spend fading out the pointer completely.
static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
+// The number of events to be read at once for DisplayEventReceiver.
+static const int EVENT_BUFFER_SIZE = 100;
// --- PointerController ---
@@ -59,6 +58,13 @@
mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
mHandler = new WeakMessageHandler(this);
+ if (mDisplayEventReceiver.initCheck() == NO_ERROR) {
+ mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK,
+ Looper::EVENT_INPUT, this, nullptr);
+ } else {
+ ALOGE("Failed to initialize DisplayEventReceiver.");
+ }
+
AutoMutex _l(mLock);
mLocked.animationPending = false;
@@ -416,21 +422,49 @@
void PointerController::handleMessage(const Message& message) {
switch (message.what) {
- case MSG_ANIMATE:
- doAnimate();
- break;
case MSG_INACTIVITY_TIMEOUT:
doInactivityTimeout();
break;
}
}
-void PointerController::doAnimate() {
+int PointerController::handleEvent(int /* fd */, int events, void* /* data */) {
+ if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+ ALOGE("Display event receiver pipe was closed or an error occurred. "
+ "events=0x%x", events);
+ return 0; // remove the callback
+ }
+
+ if (!(events & Looper::EVENT_INPUT)) {
+ ALOGW("Received spurious callback for unhandled poll event. "
+ "events=0x%x", events);
+ return 1; // keep the callback
+ }
+
+ bool gotVsync = false;
+ ssize_t n;
+ nsecs_t timestamp;
+ DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+ while ((n = mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+ for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
+ if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
+ timestamp = buf[i].header.timestamp;
+ gotVsync = true;
+ }
+ }
+ }
+ if (gotVsync) {
+ doAnimate(timestamp);
+ }
+ return 1; // keep the callback
+}
+
+void PointerController::doAnimate(nsecs_t timestamp) {
AutoMutex _l(mLock);
bool keepAnimating = false;
mLocked.animationPending = false;
- nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime;
+ nsecs_t frameDelay = timestamp - mLocked.animationTime;
// Animate pointer fade.
if (mLocked.pointerFadeDirection < 0) {
@@ -481,7 +515,7 @@
if (!mLocked.animationPending) {
mLocked.animationPending = true;
mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
- mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE));
+ mDisplayEventReceiver.requestNextVsync();
}
}
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index 24a1681..6d840db 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -28,6 +28,7 @@
#include <utils/RefBase.h>
#include <utils/Looper.h>
#include <utils/String8.h>
+#include <gui/DisplayEventReceiver.h>
#include <SkBitmap.h>
@@ -68,7 +69,8 @@
*
* Handles pointer acceleration and animation.
*/
-class PointerController : public PointerControllerInterface, public MessageHandler {
+class PointerController : public PointerControllerInterface, public MessageHandler,
+ public LooperCallback {
protected:
virtual ~PointerController();
@@ -106,7 +108,6 @@
static const size_t MAX_SPOTS = 12;
enum {
- MSG_ANIMATE,
MSG_INACTIVITY_TIMEOUT,
};
@@ -136,6 +137,8 @@
sp<SpriteController> mSpriteController;
sp<WeakMessageHandler> mHandler;
+ DisplayEventReceiver mDisplayEventReceiver;
+
PointerResources mResources;
struct Locked {
@@ -173,7 +176,8 @@
void setPositionLocked(float x, float y);
void handleMessage(const Message& message);
- void doAnimate();
+ int handleEvent(int fd, int events, void* data);
+ void doAnimate(nsecs_t timestamp);
void doInactivityTimeout();
void startAnimationLocked();
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index f9e8027..d45345e 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -40,7 +40,7 @@
<activity
android:name=".ManageRootActivity"
- android:theme="@style/DocumentsNonDialogTheme"
+ android:theme="@style/DocumentsFullScreenTheme"
android:icon="@drawable/ic_doc_text">
<intent-filter>
<action android:name="android.provider.action.MANAGE_ROOT" />
@@ -63,7 +63,7 @@
<activity
android:name=".FilesActivity"
- android:theme="@style/FilesTheme"
+ android:theme="@style/DocumentsFullScreenTheme"
android:icon="@drawable/ic_files_app"
android:label="@string/files_label"
android:documentLaunchMode="intoExisting">
diff --git a/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml b/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
index ab414a9..bf19d4e 100644
--- a/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
+++ b/packages/DocumentsUI/res/color/item_doc_grid_overlay.xml
@@ -16,10 +16,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
- android:state_focused="true"
- android:color="@color/platform_blue_a200"
- android:alpha="0.1" />
- <item
android:state_activated="true"
android:color="?android:attr/colorAccent"
android:alpha="0.1" />
diff --git a/packages/DocumentsUI/res/values-sw720dp/styles.xml b/packages/DocumentsUI/res/values-sw720dp/styles.xml
index d415a84..a8dcbb0 100644
--- a/packages/DocumentsUI/res/values-sw720dp/styles.xml
+++ b/packages/DocumentsUI/res/values-sw720dp/styles.xml
@@ -16,7 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="DocumentsBaseTheme" parent="@style/Theme.AppCompat.Dialog">
+ <style name="DocumentsBaseTheme" parent="@style/Theme.AppCompat.Light.Dialog">
<!-- We do not specify width of window here because the max size of
floating window specified by windowFixedWidthis is limited. -->
<item name="*android:windowFixedHeightMajor">80%</item>
diff --git a/packages/DocumentsUI/res/values/colors.xml b/packages/DocumentsUI/res/values/colors.xml
index cb6957d..a376418 100644
--- a/packages/DocumentsUI/res/values/colors.xml
+++ b/packages/DocumentsUI/res/values/colors.xml
@@ -15,23 +15,15 @@
-->
<resources>
- <color name="material_grey_50">#fffafafa</color>
- <color name="material_grey_300">#ffeeeeee</color>
- <color name="material_grey_600">#ff757575</color>
- <color name="material_grey_800">#ff424242</color>
+ <color name="material_grey_400">#ffbdbdbd</color>
- <color name="primary_dark">@*android:color/material_blue_grey_900</color>
- <color name="primary">@*android:color/material_blue_grey_800</color>
- <color name="accent">@*android:color/material_deep_teal_500</color>
-
- <color name="platform_blue_100">#ffd0d9ff</color>
- <color name="platform_blue_500">#ff5677fc</color>
- <color name="platform_blue_700">#ff455ede</color>
- <color name="platform_blue_a100">#ffa6baff</color>
- <color name="platform_blue_a200">#ffff5177</color>
+ <color name="primary_dark">@*android:color/primary_dark_material_dark</color>
+ <color name="primary">@*android:color/material_blue_grey_900</color>
+ <color name="accent">@*android:color/accent_material_light</color>
+ <color name="action_mode">@color/material_grey_400</color>
- <color name="directory_background">@color/material_grey_300</color>
- <color name="item_doc_grid_background">#FFFFFFFF</color>
+ <color name="directory_background">@*android:color/material_grey_300</color>
+ <color name="item_doc_grid_background">@android:color/white</color>
<color name="item_doc_grid_protect_background">#88000000</color>
<color name="band_select_background">#88ffffff</color>
<color name="band_select_border">#44000000</color>
diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml
index c13f144..15d17cc 100644
--- a/packages/DocumentsUI/res/values/styles.xml
+++ b/packages/DocumentsUI/res/values/styles.xml
@@ -28,7 +28,7 @@
<item name="android:colorPrimaryDark">@color/primary_dark</item>
<item name="android:colorPrimary">@color/primary</item>
<item name="android:colorAccent">@color/accent</item>
- <item name="colorActionMode">@color/material_grey_800</item>
+ <item name="colorActionMode">@color/action_mode</item>
<item name="android:listDivider">@*android:drawable/list_divider_material</item>
@@ -37,14 +37,17 @@
<item name="android:windowNoTitle">true</item>
<item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
- <item name="android:alertDialogTheme">@android:style/Theme.Material.Light.Dialog.Alert</item>
</style>
- <style name="DocumentsBaseTheme.FullScreen" parent="@style/Theme.AppCompat.Light.DarkActionBar">
+ <style name="DocumentsFullScreenTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar">
<item name="actionBarWidgetTheme">@null</item>
<item name="actionBarTheme">@style/ActionBarTheme</item>
<item name="actionBarPopupTheme">@style/ActionBarPopupTheme</item>
- <item name="colorActionMode">@color/material_grey_800</item>
+
+ <item name="android:colorPrimaryDark">@color/primary_dark</item>
+ <item name="android:colorPrimary">@color/primary</item>
+ <item name="android:colorAccent">@color/accent</item>
+ <item name="colorActionMode">@color/action_mode</item>
<item name="android:listDivider">@*android:drawable/list_divider_material</item>
@@ -55,38 +58,6 @@
<item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item>
</style>
- <style name="DocumentsNonDialogTheme" parent="@style/DocumentsBaseTheme.FullScreen">
- <item name="android:colorPrimaryDark">@color/primary_dark</item>
- <item name="android:colorPrimary">@color/primary</item>
- <item name="android:colorAccent">@color/accent</item>
-
- <item name="android:actionModeStyle">@style/ActionModeStyle</item>
-
- <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
- </style>
-
- <style name="ActionModeStyle" parent="@android:style/Widget.Material.Light.ActionMode">
- <item name="android:background">@color/material_grey_600</item>
- </style>
-
- <style name="AlertDialogTheme" parent="@android:style/Theme.Material.Light.Dialog.Alert">
- <item name="android:colorAccent">@color/platform_blue_700</item>
- </style>
-
- <style name="FilesTheme" parent="@style/DocumentsBaseTheme.FullScreen">
- <item name="android:colorPrimaryDark">@color/platform_blue_700</item>
- <item name="android:colorPrimary">@color/platform_blue_500</item>
- <item name="android:colorAccent">@color/platform_blue_700</item>
- <item name="colorControlActivated">@color/platform_blue_a100</item>
- <item name="android:actionModeStyle">@style/FilesActionModeStyle</item>
- <item name="colorActionMode">@color/platform_blue_700</item>
- <item name="android:alertDialogTheme">@style/AlertDialogTheme</item>
- </style>
-
- <style name="FilesActionModeStyle" parent="@android:style/Widget.Material.Light.ActionMode">
- <item name="android:background">@color/platform_blue_100</item>
- </style>
-
<style name="TrimmedHorizontalProgressBar" parent="android:Widget.Material.ProgressBar.Horizontal">
<item name="android:indeterminateDrawable">@drawable/progress_indeterminate_horizontal_material_trimmed</item>
<item name="android:minHeight">3dp</item>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index aae5269..18957ee 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -86,7 +86,7 @@
mShowAsDialog = res.getBoolean(R.bool.show_as_dialog);
if (!mShowAsDialog) {
- setTheme(R.style.DocumentsNonDialogTheme);
+ setTheme(R.style.DocumentsFullScreenTheme);
}
if (mShowAsDialog) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
new file mode 100644
index 0000000..cf17ea5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settingslib.accessibility;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.provider.Settings;
+import android.text.TextUtils;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+public class AccessibilityUtils {
+ public static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
+
+ final static TextUtils.SimpleStringSplitter sStringColonSplitter =
+ new TextUtils.SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
+
+ /**
+ * @return the set of enabled accessibility services. If there are not services
+ * it returned the unmodifiable {@link Collections#emptySet()}.
+ */
+ public static Set<ComponentName> getEnabledServicesFromSettings(Context context) {
+ final String enabledServicesSetting = Settings.Secure.getString(
+ context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ if (enabledServicesSetting == null) {
+ return Collections.emptySet();
+ }
+
+ final Set<ComponentName> enabledServices = new HashSet<ComponentName>();
+ final TextUtils.SimpleStringSplitter colonSplitter = sStringColonSplitter;
+ colonSplitter.setString(enabledServicesSetting);
+
+ while (colonSplitter.hasNext()) {
+ final String componentNameString = colonSplitter.next();
+ final ComponentName enabledService = ComponentName.unflattenFromString(
+ componentNameString);
+ if (enabledService != null) {
+ enabledServices.add(enabledService);
+ }
+ }
+
+ return enabledServices;
+ }
+
+ /**
+ * @return a localized version of the text resource specified by resId
+ */
+ public static CharSequence getTextForLocale(Context context, Locale locale, int resId) {
+ final Resources res = context.getResources();
+ final Configuration config = new Configuration(res.getConfiguration());
+ config.setLocale(locale);
+ final Context langContext = context.createConfigurationContext(config);
+ return langContext.getText(resId);
+ }
+}
diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java
index d83b516..0d1ad19 100644
--- a/packages/Shell/src/com/android/shell/BugreportReceiver.java
+++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java
@@ -25,6 +25,7 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -41,14 +42,11 @@
import libcore.io.Streams;
import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import java.util.ArrayList;
@@ -107,11 +105,6 @@
*/
private void triggerLocalNotification(final Context context, final File bugreportFile,
final File screenshotFile) {
- // Files are kept on private storage, so turn into Uris that we can
- // grant temporary permissions for.
- final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile);
- final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile);
-
boolean isPlainText = bugreportFile.getName().toLowerCase().endsWith(".txt");
if (!isPlainText) {
// Already zipped, send it right away.
@@ -133,12 +126,22 @@
*/
private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) {
final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
+ final String mimeType = "application/vnd.android.bugreport";
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addCategory(Intent.CATEGORY_DEFAULT);
- intent.setType("application/vnd.android.bugreport");
+ intent.setType(mimeType);
intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment());
+
+ // EXTRA_TEXT should be an ArrayList, but some clients are expecting a single String.
+ // So, to avoid an exception on Intent.migrateExtraStreamToClipData(), we need to manually
+ // create the ClipData object with the attachments URIs.
intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description"));
+ final ClipData clipData = new ClipData(
+ null, new String[] { mimeType },
+ new ClipData.Item(null, null, null, bugreportUri));
+ clipData.addItem(new ClipData.Item(null, null, null, screenshotUri));
+ intent.setClipData(clipData);
final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 5d622a0..be5c0fe 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -214,6 +214,7 @@
android:resumeWhilePausing="true"
android:screenOrientation="behind"
android:resizeableActivity="true"
+ android:configChanges="orientation|screenSize|smallestScreenSize"
android:theme="@style/RecentsTheme.Wallpaper">
<intent-filter>
<action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 6c8bf0f..dc80259 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -616,15 +616,17 @@
ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t);
ctx.postAnimationTrigger.increment();
if (mSearchWidgetInfo != null) {
- ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
- @Override
- public void run() {
- // Start listening for widget package changes if there is one bound
- if (!Constants.DebugFlags.App.DisableSearchBar && mAppWidgetHost != null) {
- mAppWidgetHost.startListening();
+ if (!Constants.DebugFlags.App.DisableSearchBar) {
+ ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
+ @Override
+ public void run() {
+ // Start listening for widget package changes if there is one bound
+ if (mAppWidgetHost != null) {
+ mAppWidgetHost.startListening();
+ }
}
- }
- });
+ });
+ }
}
ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index d02e2af..c314f3f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -57,7 +57,7 @@
import com.android.systemui.recents.model.TaskGrouping;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.TaskStackView;
-import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm;
+import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
import com.android.systemui.recents.views.TaskViewHeader;
import com.android.systemui.recents.views.TaskViewTransform;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -469,10 +469,10 @@
mSearchBarBounds, mTaskStackBounds);
// Rebind the header bar and draw it for the transition
- TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
+ TaskStackLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
Rect taskStackBounds = new Rect(mTaskStackBounds);
algo.setSystemInsets(systemInsets);
- algo.computeRects(taskStackBounds);
+ algo.initialize(taskStackBounds);
Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
if (!taskViewBounds.equals(mLastTaskViewBounds)) {
mLastTaskViewBounds.set(taskViewBounds);
@@ -695,7 +695,7 @@
// Prepare the dummy stack for the transition
mDummyStackView.updateMinMaxScrollForStack(stack);
- TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
+ TaskStackLayoutAlgorithm.VisibilityReport stackVr =
mDummyStackView.computeStackVisibilityReport();
boolean hasRecentTasks = stack.getTaskCount() > 0;
boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
@@ -742,7 +742,7 @@
*/
private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
- TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
+ TaskStackLayoutAlgorithm.VisibilityReport vr) {
mStartAnimationTriggered = false;
// Update the configuration based on the launch options
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 6fc4e20..6c83b87 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -137,12 +137,19 @@
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail);
- stackTasks.add(task);
+ if (task.isFreeformTask()) {
+ freeformTasks.add(task);
+ } else {
+ stackTasks.add(task);
+ }
}
// Initialize the stacks
+ ArrayList<Task> allTasks = new ArrayList<>();
+ allTasks.addAll(stackTasks);
+ allTasks.addAll(freeformTasks);
mStack = new TaskStack();
- mStack.setTasks(stackTasks);
+ mStack.setTasks(allTasks);
mStack.createAffiliatedGroupings(mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index b3937c3..a96fe98 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -399,6 +399,21 @@
return mTaskList.size();
}
+ /**
+ * Returns the task in this stack which is the launch target.
+ */
+ public Task getLaunchTarget() {
+ ArrayList<Task> tasks = mTaskList.getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ if (task.isLaunchTarget) {
+ return task;
+ }
+ }
+ return null;
+ }
+
/** Returns the index of this task in this current task stack */
public int indexOfTask(Task t) {
return mTaskList.indexOf(t);
@@ -417,6 +432,21 @@
return null;
}
+ /**
+ * Returns whether this stack has freeform tasks.
+ */
+ public boolean hasFreeformTasks() {
+ ArrayList<Task> tasks = mTaskList.getTasks();
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = tasks.get(i);
+ if (task.isFreeformTask()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/******** Filtering ********/
/** Filters the stack into tasks similar to the one specified */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
new file mode 100644
index 0000000..ce993c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.util.Log;
+import com.android.systemui.recents.model.Task;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * The layout logic for the contents of the freeform workspace.
+ */
+public class FreeformWorkspaceLayoutAlgorithm {
+
+ private static final String TAG = "FreeformWorkspaceLayoutAlgorithm";
+ private static final boolean DEBUG = false;
+
+ // The number of cells in the freeform workspace
+ private int mFreeformCellXCount;
+ private int mFreeformCellYCount;
+ // The width and height of the cells in the freeform workspace
+ private int mFreeformCellWidth;
+ private int mFreeformCellHeight;
+
+ // Optimization, allows for quick lookup of task -> index
+ private HashMap<Task.TaskKey, Integer> mTaskIndexMap = new HashMap<>();
+
+ /**
+ * Updates the layout for each of the freeform workspace tasks. This is called after the stack
+ * layout is updated.
+ */
+ public void update(ArrayList<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
+ int numFreeformTasks = stackLayout.mNumFreeformTasks;
+ if (!freeformTasks.isEmpty()) {
+ // Calculate the cell width/height depending on the number of freeform tasks
+ mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(numFreeformTasks)));
+ mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks / mFreeformCellXCount));
+ mFreeformCellWidth = stackLayout.mFreeformRect.width() / mFreeformCellXCount;
+ // For now, make the cells square
+ mFreeformCellHeight = mFreeformCellWidth;
+
+ // Put each of the tasks in the progress map at a fixed index (does not need to actually
+ // map to a scroll position, just by index)
+ int taskCount = freeformTasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task task = freeformTasks.get(i);
+ mTaskIndexMap.put(task.key, i);
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "mFreeformCellXCount: " + mFreeformCellXCount);
+ Log.d(TAG, "mFreeformCellYCount: " + mFreeformCellYCount);
+ Log.d(TAG, "mFreeformCellWidth: " + mFreeformCellWidth);
+ Log.d(TAG, "mFreeformCellHeight: " + mFreeformCellHeight);
+ }
+ }
+ }
+
+ /**
+ * Returns whether the transform is available for the given task.
+ */
+ public boolean isTransformAvailable(Task task, float stackScroll,
+ TaskStackLayoutAlgorithm stackLayout) {
+ if (stackLayout.mNumFreeformTasks == 0 || task == null ||
+ !mTaskIndexMap.containsKey(task.key)) {
+ return false;
+ }
+ return stackScroll > stackLayout.mStackEndScrollP;
+ }
+
+ /**
+ * Returns the transform for the given task. Any rect returned will be offset by the actual
+ * transform for the freeform workspace.
+ */
+ public TaskViewTransform getTransform(Task task, float stackScroll,
+ TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
+ if (Float.compare(stackScroll, stackLayout.mStackEndScrollP) > 0) {
+ // This is a freeform task, so lay it out in the freeform workspace
+ int taskIndex = mTaskIndexMap.get(task.key);
+ int x = taskIndex % mFreeformCellXCount;
+ int y = taskIndex / mFreeformCellXCount;
+ float scale = (float) mFreeformCellWidth / stackLayout.mTaskRect.width();
+ int scaleXOffset = (int) (((1f - scale) * stackLayout.mTaskRect.width()) / 2);
+ int scaleYOffset = (int) (((1f - scale) * stackLayout.mTaskRect.height()) / 2);
+ transformOut.scale = scale * 0.9f;
+ transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
+ transformOut.translationY = y * mFreeformCellHeight - scaleYOffset;
+ transformOut.visible = true;
+ return transformOut;
+ }
+ return null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
rename to packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index c74c654..ff02e03 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -26,6 +26,7 @@
import com.android.systemui.recents.misc.ParametricCurve;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
import java.util.HashMap;
@@ -33,9 +34,8 @@
/**
* The layout logic for a TaskStackView.
- *
*/
-public class TaskStackViewLayoutAlgorithm {
+public class TaskStackLayoutAlgorithm {
private static final String TAG = "TaskStackViewLayoutAlgorithm";
private static final boolean DEBUG = false;
@@ -46,6 +46,10 @@
private static final float SINGLE_TASK_SCALE = 0.95f;
// The percentage of height of task to show between tasks
private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f;
+ // The percentage between the maxStackScroll and the maxScroll where a given scroll will still
+ // snap back to the maxStackScroll instead of to the maxScroll (which shows the freeform
+ // workspace)
+ private static final float SNAP_TO_MAX_STACK_SCROLL_FACTOR = 0.3f;
// A report of the visibility state of the stack
public class VisibilityReport {
@@ -64,11 +68,9 @@
// This is the view bounds inset exactly by the search bar, but without the bottom inset
// see RecentsConfiguration.getTaskStackBounds()
public Rect mStackRect = new Rect();
-
// This is the task view bounds for layout (untransformed), the rect is top-aligned to the top
// of the stack rect
public Rect mTaskRect = new Rect();
-
// The bounds of the freeform workspace, the rect is top-aligned to the top of the stack rect
public Rect mFreeformRect = new Rect();
// This is the current system insets
@@ -79,10 +81,14 @@
// The largest scroll progress, at this value, the front most task will be visible above the
// navigation bar
float mMaxScrollP;
- // The scroll progress at which the stack scroll ends and the overscroll begins. This serves
- // as the point at which we can show the freeform space.
- float mMaxStackScrollP;
- // The initial progress that the scroller is set
+ // The scroll progress at which bottom of the first task of the stack is aligned with the bottom
+ // of the stack
+ float mStackEndScrollP;
+ // The scroll progress that we actually want to scroll the user to when they want to go to the
+ // end of the stack (it accounts for the nav bar, so that the bottom of the task is offset from
+ // the bottom of the stack)
+ float mPreferredStackEndScrollP;
+ // The initial progress that the scroller is set when you first enter recents
float mInitialScrollP;
// The task progress for the front-most task in the stack
float mFrontMostTaskP;
@@ -96,20 +102,18 @@
float mTaskHeightPOffset;
// The relative progress to ensure that the half task height is respected
float mTaskHalfHeightPOffset;
+ // The front-most task bottom offset
+ int mStackBottomOffset;
// The relative progress to ensure that the offset from the bottom of the stack to the bottom
// of the task is respected
- float mTaskBottomPOffset;
- // The relative progress to ensure that the freeform workspace height is respected
+ float mStackBottomPOffset;
+ // The freeform workspace gap
+ int mFreeformWorkspaceGapOffset;
+ float mFreeformWorkspaceGapPOffset;
+ // The relative progress to ensure that the freeform workspace height + gap + stack bottom
+ // padding is respected
+ int mFreeformWorkspaceOffset;
float mFreeformWorkspacePOffset;
- // The front-most task bottom offset
- int mTaskBottomOffset;
-
- // The number of cells in the freeform workspace
- int mFreeformCellXCount;
- int mFreeformCellYCount;
- // The width and height of the cells in the freeform workspace
- int mFreeformCellWidth;
- int mFreeformCellHeight;
// The last computed task counts
int mNumStackTasks;
@@ -121,14 +125,21 @@
// Optimization, allows for quick lookup of task -> progress
HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<>();
+ // The freeform workspace layout
+ FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
+
+ // Temporary task view transform
+ TaskViewTransform mTmpTransform = new TaskViewTransform();
+
// Log function
static ParametricCurve sCurve;
- public TaskStackViewLayoutAlgorithm(Context context) {
+ public TaskStackLayoutAlgorithm(Context context) {
Resources res = context.getResources();
mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min);
mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max);
mContext = context;
+ mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm();
if (sCurve == null) {
sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() {
// The large the XScale, the longer the flat area of the curve
@@ -151,6 +162,11 @@
}, new ParametricCurve.ParametricCurveFunction() {
@Override
public float f(float p) {
+ // Don't scale when there are freeform tasks
+ if (mNumFreeformTasks > 0) {
+ return 1f;
+ }
+
if (p < 0) return STACK_PEEK_MIN_SCALE;
if (p > 1) return 1f;
float scaleRange = (1f - STACK_PEEK_MIN_SCALE);
@@ -174,7 +190,7 @@
/**
* Computes the stack and task rects.
*/
- public void computeRects(Rect taskStackBounds) {
+ public void initialize(Rect taskStackBounds) {
RecentsConfiguration config = Recents.getConfiguration();
int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
int heightPadding = mContext.getResources().getDimensionPixelSize(
@@ -183,14 +199,17 @@
// Compute the stack rect, inset from the given task stack bounds
mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding,
taskStackBounds.right - widthPadding, taskStackBounds.bottom);
- mTaskBottomOffset = mSystemInsets.bottom + heightPadding;
+ mStackBottomOffset = mSystemInsets.bottom + heightPadding;
// Compute the task rect, align it to the top-center square in the stack rect
- int size = Math.min(mStackRect.width(), mStackRect.height() - mTaskBottomOffset);
+ int size = Math.min(mStackRect.width(), mStackRect.height() - mStackBottomOffset);
int xOffset = (mStackRect.width() - size) / 2;
mTaskRect.set(mStackRect.left + xOffset, mStackRect.top,
mStackRect.right - xOffset, mStackRect.top + size);
- mFreeformRect.set(mTaskRect);
+
+ // Compute the freeform rect, align it to the top-left of the stack rect
+ mFreeformRect.set(mStackRect);
+ mFreeformRect.bottom = taskStackBounds.bottom - mStackBottomOffset;
// Compute the progress offsets
int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize(
@@ -204,12 +223,17 @@
mStackRect);
mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2,
mStackRect);
- mTaskBottomPOffset = sCurve.computePOffsetForHeight(mTaskBottomOffset, mStackRect);
- mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformRect.height(),
+ mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mStackRect);
+ mFreeformWorkspaceGapOffset = mStackBottomOffset;
+ mFreeformWorkspaceGapPOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceGapOffset,
+ mStackRect);
+ mFreeformWorkspaceOffset = mFreeformWorkspaceGapOffset + mFreeformRect.height() +
+ mStackBottomOffset;
+ mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceOffset,
mStackRect);
if (DEBUG) {
- Log.d(TAG, "computeRects");
+ Log.d(TAG, "initialize");
Log.d(TAG, "\tarclength: " + sCurve.getArcLength());
Log.d(TAG, "\tmStackRect: " + mStackRect);
Log.d(TAG, "\tmTaskRect: " + mTaskRect);
@@ -219,12 +243,14 @@
Log.d(TAG, "\tpBetweenAffiliateOffset: " + mBetweenAffiliationPOffset);
Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset);
Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset);
- Log.d(TAG, "\tmTaskBottomPOffset: " + mTaskBottomPOffset);
+ Log.d(TAG, "\tmStackBottomPOffset: " + mStackBottomPOffset);
+ Log.d(TAG, "\tmFreeformWorkspacePOffset: " + mFreeformWorkspacePOffset);
+ Log.d(TAG, "\tmFreeformWorkspaceGapPOffset: " + mFreeformWorkspaceGapPOffset);
Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mStackRect));
Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mStackRect));
- for (int height = 0; height <= 1000; height += 50) {
+ for (int height = 0; height <= 2000; height += 50) {
float p = sCurve.computePOffsetForScaledHeight(height, mStackRect);
float p2 = sCurve.computePOffsetForHeight(height, mStackRect);
Log.d(TAG, "offset: " + height + ", " +
@@ -236,20 +262,22 @@
}
/**
- * Computes the minimum and maximum scroll progress values. This method may be called before
- * the RecentsConfiguration is set, so we need to pass in the alt-tab state.
+ * Computes the minimum and maximum scroll progress values and the progress values for each task
+ * in the stack.
*/
- void computeMinMaxScroll(ArrayList<Task> tasks) {
+ void update(TaskStack stack) {
if (DEBUG) {
- Log.d(TAG, "computeMinMaxScroll");
+ Log.d(TAG, "update");
}
// Clear the progress map
mTaskProgressMap.clear();
// Return early if we have no tasks
+ ArrayList<Task> tasks = stack.getTasks();
if (tasks.isEmpty()) {
- mMinScrollP = mMaxScrollP = mMaxStackScrollP = 0;
+ mFrontMostTaskP = 0;
+ mMinScrollP = mMaxScrollP = mStackEndScrollP = mPreferredStackEndScrollP = 0;
mNumStackTasks = mNumFreeformTasks = 0;
return;
}
@@ -268,9 +296,6 @@
mNumStackTasks = stackTasks.size();
mNumFreeformTasks = freeformTasks.size();
- // TODO: In the case where there is only freeform tasks, then the scrolls should be set to
- // zero
-
if (!stackTasks.isEmpty()) {
// Update the for each task from back to front.
float pAtBackMostTaskTop = 0;
@@ -289,52 +314,47 @@
}
mFrontMostTaskP = pAtFrontMostTaskTop;
- // Set the max scroll progress to the point at which the top of the front-most task
- // is aligned to the bottom of the stack (offset by nav bar, padding, and task height)
- mMaxStackScrollP = getBottomAlignedScrollProgress(pAtFrontMostTaskTop,
- mTaskBottomPOffset + mTaskHeightPOffset);
+ // Set the stack end scroll progress to the point at which the bottom of the front-most
+ // task is aligned to the bottom of the stack
+ mStackEndScrollP = alignToStackBottom(pAtFrontMostTaskTop,
+ mTaskHeightPOffset);
+ // Set the preferred stack end scroll progress to the point where the bottom of the
+ // front-most task is offset by the navbar and padding from the bottom of the stack
+ mPreferredStackEndScrollP = mStackEndScrollP + mStackBottomPOffset;
// Basically align the back-most task such that its progress is the same as the top of
// the front most task at the max stack scroll
- mMinScrollP = getBottomAlignedScrollProgress(pAtBackMostTaskTop,
- mTaskBottomPOffset + mTaskHeightPOffset);
+ mMinScrollP = alignToStackBottom(pAtBackMostTaskTop,
+ mStackBottomPOffset + mTaskHeightPOffset);
+ } else {
+ // TODO: In the case where there is only freeform tasks, then the scrolls should be
+ // set to zero
}
if (!freeformTasks.isEmpty()) {
- // Calculate the cell width/height depending on the number of freeform tasks
- mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(mNumFreeformTasks)));
- mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) mNumFreeformTasks / mFreeformCellXCount));
- mFreeformCellWidth = mFreeformRect.width() / mFreeformCellXCount;
- mFreeformCellHeight = mFreeformRect.height() / mFreeformCellYCount;
-
- // Put each of the tasks in the progress map at a fixed index (does not need to actually
- // map to a scroll position, just by index)
- int taskCount = freeformTasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task task = freeformTasks.get(i);
- mTaskProgressMap.put(task.key, (mFrontMostTaskP + 1) + i);
- }
-
// The max scroll includes the freeform workspace offset. As the scroll progress exceeds
- // mMaxStackScrollP up to mMaxScrollP, the stack will translate upwards and the freeform
+ // mStackEndScrollP up to mMaxScrollP, the stack will translate upwards and the freeform
// workspace will be visible
- mMaxScrollP = mMaxStackScrollP + mFreeformWorkspacePOffset;
- mInitialScrollP = mMaxScrollP;
+ mFreeformLayoutAlgorithm.update(freeformTasks, this);
+ mMaxScrollP = mStackEndScrollP + mFreeformWorkspacePOffset;
+ mInitialScrollP = isInitialStateFreeform(stack) ?
+ mMaxScrollP : mPreferredStackEndScrollP;
} else {
- mMaxScrollP = mMaxStackScrollP;
- mInitialScrollP = Math.max(mMinScrollP, mMaxStackScrollP - mTaskHalfHeightPOffset);
+ mMaxScrollP = mPreferredStackEndScrollP;
+ mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset);
}
+
if (DEBUG) {
Log.d(TAG, "mNumStackTasks: " + mNumStackTasks);
Log.d(TAG, "mNumFreeformTasks: " + mNumFreeformTasks);
Log.d(TAG, "mMinScrollP: " + mMinScrollP);
- Log.d(TAG, "mMaxStackScrollP: " + mMaxStackScrollP);
+ Log.d(TAG, "mStackEndScrollP: " + mStackEndScrollP);
Log.d(TAG, "mMaxScrollP: " + mMaxScrollP);
}
}
/**
* Computes the maximum number of visible tasks and thumbnails. Requires that
- * computeMinMaxScroll() is called first.
+ * update() is called first.
*/
public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
// Ensure minimum visibility count
@@ -380,7 +400,7 @@
prevScreenY = screenY;
} else {
// Once we hit the next front most task that does not have a visible thumbnail,
- // walk through remaining visible set
+ // w alk through remaining visible set
for (int j = i; j >= 0; j--) {
numVisibleTasks++;
progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP;
@@ -404,50 +424,42 @@
*/
public TaskViewTransform getStackTransform(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform prevTransform) {
- // Return early if we have an invalid index
- if (task == null || !mTaskProgressMap.containsKey(task.key)) {
- transformOut.reset();
+ if (mFreeformLayoutAlgorithm.isTransformAvailable(task, stackScroll, this)) {
+ mFreeformLayoutAlgorithm.getTransform(task, stackScroll, transformOut, this);
+ if (transformOut.visible) {
+ getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
+ transformOut.translationY += mTmpTransform.translationY;
+ transformOut.translationZ = mMaxTranslationZ;
+ transformOut.rect.set(mTaskRect);
+ transformOut.rect.offset(0, transformOut.translationY);
+ Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+ transformOut.p = 0;
+ }
return transformOut;
+ } else {
+ // Return early if we have an invalid index
+ if (task == null || !mTaskProgressMap.containsKey(task.key)) {
+ transformOut.reset();
+ return transformOut;
+ }
+ return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
+ prevTransform);
}
- return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
- prevTransform);
}
/** Update/get the transform */
public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform prevTransform) {
- float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset;
- int overscrollYOffset = 0;
- if (mNumFreeformTasks > 0) {
- overscrollYOffset = (int) (Math.max(0, stackOverscroll) * mFreeformRect.height());
- }
- if ((mNumFreeformTasks > 0) && (stackScroll > mMaxStackScrollP) &&
- (taskProgress > mFrontMostTaskP)) {
- // This is a freeform task, so lay it out in the freeform workspace
- int taskIndex = Math.round(taskProgress - (mFrontMostTaskP + 1));
- int x = taskIndex % mFreeformCellXCount;
- int y = taskIndex / mFreeformCellXCount;
- int frontTaskBottom = mStackRect.height() - mTaskBottomOffset;
- float scale = (float) mFreeformCellWidth / mTaskRect.width();
- int scaleXOffset = (int) (((1f - scale) * mTaskRect.width()) / 2);
- int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
- transformOut.scale = scale;
- transformOut.translationX = x * mFreeformCellWidth - scaleXOffset;
- transformOut.translationY = frontTaskBottom - overscrollYOffset +
- (y * mFreeformCellHeight) - scaleYOffset;
- transformOut.visible = true;
- return transformOut;
-
- } else if (mNumStackTasks == 1) {
+ if (mNumStackTasks == 1) {
// Center the task in the stack, changing the scale will not follow the curve, but just
// modulate some values directly
float pTaskRelative = mMinScrollP - stackScroll;
float scale = SINGLE_TASK_SCALE;
- int topOffset = (mStackRect.height() - mTaskBottomOffset - mTaskRect.height()) / 2;
+ int topOffset = (mStackRect.height() - mTaskRect.height()) / 2;
transformOut.scale = scale;
- transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height())) -
- overscrollYOffset;
+ transformOut.translationX = 0;
+ transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height()));
transformOut.translationZ = mMaxTranslationZ;
transformOut.rect.set(mTaskRect);
transformOut.rect.offset(0, transformOut.translationY);
@@ -457,11 +469,20 @@
return transformOut;
} else {
- float pTaskRelative = taskProgress - stackScroll;
- if (mNumFreeformTasks > 0) {
- pTaskRelative = Math.min(mMaxStackScrollP, pTaskRelative);
+ // Once we scroll past the preferred stack end scroll, then we should start translating
+ // the cards in screen space and lock their final state at the end stack progress
+ int overscrollYOffset = 0;
+ if (mNumFreeformTasks > 0 && stackScroll > mStackEndScrollP) {
+ float stackOverscroll = (stackScroll - mPreferredStackEndScrollP) /
+ (mFreeformWorkspacePOffset - mFreeformWorkspaceGapPOffset);
+ overscrollYOffset = (int) (Math.max(0, stackOverscroll) *
+ (mFreeformWorkspaceOffset - mFreeformWorkspaceGapPOffset));
+ stackScroll = Math.min(mPreferredStackEndScrollP, stackScroll);
}
+
+ float pTaskRelative = taskProgress - stackScroll;
float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
+
// If the task top is outside of the bounds below the screen, then immediately reset it
if (pTaskRelative > 1f) {
transformOut.reset();
@@ -480,6 +501,7 @@
float scale = sCurve.pToScale(pBounded);
int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
transformOut.scale = scale;
+ transformOut.translationX = 0;
transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top -
scaleYOffset - overscrollYOffset;
transformOut.translationZ = Math.max(mMinTranslationZ,
@@ -489,11 +511,31 @@
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
transformOut.p = pTaskRelative;
+ if (DEBUG) {
+ Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll);
+ Log.d(TAG, "\t" + transformOut);
+ }
+
return transformOut;
}
}
/**
+ * Returns whether this stack should be initialized to show the freeform workspace or not.
+ */
+ public boolean isInitialStateFreeform(TaskStack stack) {
+ Task launchTarget = stack.getLaunchTarget();
+ if (launchTarget != null) {
+ return launchTarget.isFreeformTask();
+ }
+ Task frontTask = stack.getFrontMostTask();
+ if (frontTask != null) {
+ return frontTask.isFreeformTask();
+ }
+ return false;
+ }
+
+ /**
* Update/get the transform
*/
public TaskViewTransform getFreeformWorkspaceBounds(float stackScroll,
@@ -503,18 +545,37 @@
return transformOut;
}
- if (stackScroll > mMaxStackScrollP) {
- float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset;
- int overscrollYOffset = (int) (stackOverscroll * mFreeformRect.height());
- int frontTaskBottom = mStackRect.height() - mTaskBottomOffset;
+ if (stackScroll > mStackEndScrollP) {
+ // mStackEndScroll is the point at which the first stack task is bottom aligned with the
+ // stack, so we offset from on the stack rect height.
+ float stackOverscroll = (Math.max(0, stackScroll - mStackEndScrollP)) /
+ mFreeformWorkspacePOffset;
+ int overscrollYOffset = (int) (stackOverscroll * mFreeformWorkspaceOffset);
+ transformOut.scale = 1f;
+ transformOut.alpha = 1f;
+ transformOut.translationY = mStackRect.height() + mFreeformWorkspaceGapOffset -
+ overscrollYOffset;
+ transformOut.rect.set(mFreeformRect);
+ transformOut.rect.offset(0, transformOut.translationY);
+ Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
- transformOut.alpha =
- transformOut.translationY = frontTaskBottom - overscrollYOffset;
}
return transformOut;
}
/**
+ * Returns the preferred maximum scroll position for a stack at the given {@param scroll}.
+ */
+ public float getPreferredMaxScrollPosition(float scroll) {
+ float maxStackScrollBounds = mStackEndScrollP + SNAP_TO_MAX_STACK_SCROLL_FACTOR *
+ (mMaxScrollP - mStackEndScrollP);
+ if (scroll < maxStackScrollBounds) {
+ return mPreferredStackEndScrollP;
+ }
+ return mMaxScrollP;
+ }
+
+ /**
* Returns the untransformed task view bounds.
*/
public Rect getUntransformedTaskViewBounds() {
@@ -549,7 +610,12 @@
return -y;
}
- private float getBottomAlignedScrollProgress(float p, float pOffsetFromBottom) {
+ private float alignToStackTop(float p) {
+ // At scroll progress == p, then p is at the top of the stack
+ return p;
+ }
+
+ private float alignToStackBottom(float p, float pOffsetFromBottom) {
// At scroll progress == p, then p is at the top of the stack
// At scroll progress == p + 1, then p is at the bottom of the stack
return p - (1 - pOffsetFromBottom);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index dfa36d8..a7bfc40 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -22,12 +22,12 @@
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
@@ -79,11 +79,12 @@
}
TaskStack mStack;
- TaskStackViewLayoutAlgorithm mLayoutAlgorithm;
+ TaskStackLayoutAlgorithm mLayoutAlgorithm;
TaskStackViewFilterAlgorithm mFilterAlgorithm;
TaskStackViewScroller mStackScroller;
TaskStackViewTouchHandler mTouchHandler;
TaskStackViewCallbacks mCb;
+ ColorDrawable mFreeformWorkspaceBackground;
ViewPool<TaskView, Task> mViewPool;
ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
DozeTrigger mUIDozeTrigger;
@@ -101,6 +102,8 @@
Rect mTmpRect = new Rect();
RectF mTmpTaskRect = new RectF();
TaskViewTransform mTmpTransform = new TaskViewTransform();
+ TaskViewTransform mTmpStackBackTransform = new TaskViewTransform();
+ TaskViewTransform mTmpStackFrontTransform = new TaskViewTransform();
HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<>();
ArrayList<TaskView> mTaskViews = new ArrayList<>();
List<TaskView> mImmutableTaskViews = new ArrayList<>();
@@ -122,7 +125,7 @@
setStack(stack);
mViewPool = new ViewPool<>(context, this);
mInflater = LayoutInflater.from(context);
- mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context);
+ mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context);
mFilterAlgorithm = new TaskStackViewFilterAlgorithm(this, mViewPool);
mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm);
mStackScroller.setCallbacks(this);
@@ -143,6 +146,8 @@
}
});
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+
+ mFreeformWorkspaceBackground = new ColorDrawable(0x33000000);
}
/** Sets the callbacks */
@@ -270,7 +275,7 @@
}
/** Returns the stack algorithm for this task stack. */
- public TaskStackViewLayoutAlgorithm getStackAlgorithm() {
+ public TaskStackLayoutAlgorithm getStackAlgorithm() {
return mLayoutAlgorithm;
}
@@ -342,6 +347,11 @@
int[] visibleRange = mTmpVisibleRange;
boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks,
stackScroll, visibleRange, false);
+ boolean hasStackBackTransform = false;
+ boolean hasStackFrontTransform = false;
+ if (DEBUG) {
+ Log.d(TAG, "visibleRange: " + visibleRange[0] + " to " + visibleRange[1]);
+ }
// Return all the invisible children to the pool
mTmpTaskViewMap.clear();
@@ -381,11 +391,20 @@
// For items in the list, put them in start animating them from the
// approriate ends of the list where they are expected to appear
if (Float.compare(transform.p, 0f) <= 0) {
- mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null);
+ if (!hasStackBackTransform) {
+ hasStackBackTransform = true;
+ mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpStackBackTransform,
+ null);
+ }
+ tv.updateViewPropertiesToTaskTransform(mTmpStackBackTransform, 0);
} else {
- mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null);
+ if (!hasStackFrontTransform) {
+ hasStackFrontTransform = true;
+ mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpStackFrontTransform,
+ null);
+ }
+ tv.updateViewPropertiesToTaskTransform(mTmpStackFrontTransform, 0);
}
- tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
}
}
@@ -403,6 +422,16 @@
}
}
+ // Update the freeform workspace
+ mLayoutAlgorithm.getFreeformWorkspaceBounds(stackScroll, mTmpTransform);
+ if (mTmpTransform.visible) {
+ mTmpTransform.rect.roundOut(mTmpRect);
+ mFreeformWorkspaceBackground.setAlpha(255);
+ mFreeformWorkspaceBackground.setBounds(mTmpRect);
+ } else {
+ mFreeformWorkspaceBackground.setAlpha(0);
+ }
+
// Reset the request-synchronize params
mStackViewsAnimationDuration = 0;
mStackViewsDirty = false;
@@ -460,7 +489,7 @@
/** Updates the min and max virtual scroll bounds */
void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
// Compute the min and max scroll values
- mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks());
+ mLayoutAlgorithm.update(mStack);
// Debug logging
if (boundScrollToNewMinMax) {
@@ -512,7 +541,7 @@
};
if (scrollToTask) {
- // TODO: Center the newly focused task view
+ // TODO: Center the newly focused task view, only if not freeform
float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask) - 0.5f;
newScroll = mStackScroller.getBoundedStackScroll(newScroll);
mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll,
@@ -633,7 +662,7 @@
/** Computes the stack and task rects */
public void computeRects(Rect taskStackBounds) {
// Compute the rects in the stack algorithm
- mLayoutAlgorithm.computeRects(taskStackBounds);
+ mLayoutAlgorithm.initialize(taskStackBounds);
// Update the scroll bounds
updateMinMaxScroll(false);
@@ -652,7 +681,7 @@
* Computes the maximum number of visible tasks and thumbnails. Requires that
* updateMinMaxScrollForStack() is called first.
*/
- public TaskStackViewLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
+ public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
}
@@ -710,7 +739,7 @@
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- // Layout each of the children
+ // Layout each of the TaskViews
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
@@ -720,10 +749,9 @@
} else {
mTmpRect.setEmpty();
}
- tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left,
- mLayoutAlgorithm.mTaskRect.top - mTmpRect.top,
- mLayoutAlgorithm.mTaskRect.right + mTmpRect.right,
- mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom);
+ Rect taskRect = mLayoutAlgorithm.mTaskRect;
+ tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
+ taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
}
if (mAwaitingFirstLayout) {
@@ -737,17 +765,9 @@
int offscreenY = mLayoutAlgorithm.mStackRect.bottom;
// Find the launch target task
- Task launchTargetTask = null;
+ Task launchTargetTask = mStack.getLaunchTarget();
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
- for (int i = taskViewCount - 1; i >= 0; i--) {
- TaskView tv = taskViews.get(i);
- Task task = tv.getTask();
- if (task.isLaunchTarget) {
- launchTargetTask = task;
- break;
- }
- }
// Prepare the first view for its enter animation
for (int i = taskViewCount - 1; i >= 0; i--) {
@@ -755,7 +775,8 @@
Task task = tv.getTask();
boolean occludesLaunchTarget = (launchTargetTask != null) &&
launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
- tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY);
+ tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget,
+ offscreenY);
}
// If the enter animation started already and we haven't completed a layout yet, do the
@@ -794,17 +815,9 @@
if (mStack.getTaskCount() > 0) {
// Find the launch target task
- Task launchTargetTask = null;
+ Task launchTargetTask = mStack.getLaunchTarget();
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
- for (int i = taskViewCount - 1; i >= 0; i--) {
- TaskView tv = taskViews.get(i);
- Task task = tv.getTask();
- if (task.isLaunchTarget) {
- launchTargetTask = task;
- break;
- }
- }
// Animate all the task views into view
for (int i = taskViewCount - 1; i >= 0; i--) {
@@ -817,7 +830,8 @@
ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) &&
launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
ctx.updateListener = mRequestUpdateClippingListener;
- mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null);
+ mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
+ ctx.currentTaskTransform, null);
tv.startEnterRecentsAnimation(ctx);
}
@@ -898,6 +912,12 @@
@Override
protected void dispatchDraw(Canvas canvas) {
mLayersDisabled = false;
+
+ // Draw the freeform workspace background
+ if (mFreeformWorkspaceBackground.getAlpha() > 0) {
+ mFreeformWorkspaceBackground.draw(canvas);
+ }
+
super.dispatchDraw(canvas);
}
@@ -919,42 +939,44 @@
@Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
Task newFrontMostTask) {
- // Remove the view associated with this task, we can't rely on updateTransforms
- // to work here because the task is no longer in the list
- TaskView tv = getChildViewForTask(removedTask);
- if (tv != null) {
- mViewPool.returnViewToPool(tv);
+ if (!removedTask.isFreeformTask()) {
+ // Remove the view associated with this task, we can't rely on updateTransforms
+ // to work here because the task is no longer in the list
+ TaskView tv = getChildViewForTask(removedTask);
+ if (tv != null) {
+ mViewPool.returnViewToPool(tv);
+ }
+
+ // Get the stack scroll of the task to anchor to (since we are removing something, the front
+ // most task will be our anchor task)
+ Task anchorTask = null;
+ float prevAnchorTaskScroll = 0;
+ boolean pullStackForward = stack.getTaskCount() > 0;
+ if (pullStackForward) {
+ anchorTask = mStack.getFrontMostTask();
+ prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
+ }
+
+ // Update the min/max scroll and animate other task views into their new positions
+ updateMinMaxScroll(true);
+
+ if (wasFrontMostTask) {
+ // Since the max scroll progress is offset from the bottom of the stack, just scroll
+ // to ensure that the new front most task is now fully visible
+ mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
+ } else if (pullStackForward) {
+ // Otherwise, offset the scroll by half the movement of the anchor task to allow the
+ // tasks behind the removed task to move forward, and the tasks in front to move back
+ float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
+ mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
+ - prevAnchorTaskScroll) / 2);
+ mStackScroller.boundScroll();
+ }
+
+ // Animate all the tasks into place
+ requestSynchronizeStackViewsWithModel(200);
}
- // Get the stack scroll of the task to anchor to (since we are removing something, the front
- // most task will be our anchor task)
- Task anchorTask = null;
- float prevAnchorTaskScroll = 0;
- boolean pullStackForward = stack.getTaskCount() > 0;
- if (pullStackForward) {
- anchorTask = mStack.getFrontMostTask();
- prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
- }
-
- // Update the min/max scroll and animate other task views into their new positions
- updateMinMaxScroll(true);
-
- if (wasFrontMostTask) {
- // Since the max scroll progress is offset from the bottom of the stack, just scroll
- // to ensure that the new front most task is now fully visible
- mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP);
- } else if (pullStackForward) {
- // Otherwise, offset the scroll by half the movement of the anchor task to allow the
- // tasks behind the removed task to move forward, and the tasks in front to move back
- float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask);
- mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll
- - prevAnchorTaskScroll) / 2);
- mStackScroller.boundScroll();
- }
-
- // Animate all the tasks into place
- requestSynchronizeStackViewsWithModel(200);
-
// Update the new front most task
if (newFrontMostTask != null) {
TaskView frontTv = getChildViewForTask(newFrontMostTask);
@@ -1103,7 +1125,6 @@
int insertIndex = -1;
int taskIndex = mStack.indexOfTask(task);
if (taskIndex != -1) {
-
List<TaskView> taskViews = getTaskViews();
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
@@ -1252,7 +1273,9 @@
// the next task
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
- setFocusedTask(taskIndex - 1, true /* scrollToTask */, launchState.launchedWithAltTab);
+ setFocusedTask(taskIndex - 1,
+ !mStack.getTasks().get(taskIndex - 1).isFreeformTask() /* scrollToTask */,
+ launchState.launchedWithAltTab);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index 15fcab4..6b92aed 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -39,7 +39,7 @@
}
Context mContext;
- TaskStackViewLayoutAlgorithm mLayoutAlgorithm;
+ TaskStackLayoutAlgorithm mLayoutAlgorithm;
TaskStackViewScrollerCallbacks mCb;
float mStackScrollP;
@@ -52,7 +52,7 @@
Interpolator mLinearOutSlowInInterpolator;
- public TaskStackViewScroller(Context context, TaskStackViewLayoutAlgorithm layoutAlgorithm) {
+ public TaskStackViewScroller(Context context, TaskStackLayoutAlgorithm layoutAlgorithm) {
mContext = context;
mScroller = new OverScroller(context);
mLayoutAlgorithm = layoutAlgorithm;
@@ -121,7 +121,8 @@
/** Returns the bounded stack scroll */
float getBoundedStackScroll(float scroll) {
- return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll));
+ return Math.max(mLayoutAlgorithm.mMinScrollP,
+ Math.min(mLayoutAlgorithm.getPreferredMaxScrollPosition(scroll), scroll));
}
/** Returns the amount that the absolute value of how much the scroll is out of bounds. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index 08889c5..9e6fb7b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -16,8 +16,10 @@
package com.android.systemui.recents.views;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
+import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.VelocityTracker;
@@ -30,6 +32,8 @@
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.statusbar.FlingAnimationUtils;
import java.util.List;
@@ -37,7 +41,7 @@
class TaskStackViewTouchHandler implements SwipeHelper.Callback {
private static final String TAG = "TaskStackViewTouchHandler";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static int INACTIVE_POINTER_ID = -1;
@@ -45,6 +49,8 @@
TaskStackView mSv;
TaskStackViewScroller mScroller;
VelocityTracker mVelocityTracker;
+ FlingAnimationUtils mFlingAnimUtils;
+ ValueAnimator mScrollFlingAnimator;
boolean mIsScrolling;
float mDownScrollP;
@@ -74,6 +80,7 @@
mWindowTouchSlop = configuration.getScaledWindowTouchSlop();
mSv = sv;
mScroller = scroller;
+ mFlingAnimUtils = new FlingAnimationUtils(context, 0.2f);
float densityScale = res.getDisplayMetrics().density;
mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_stack_overscroll);
@@ -140,7 +147,7 @@
return false;
}
- TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
+ final TaskStackLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm;
int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
@@ -154,6 +161,7 @@
// Stop the current scroll if it is still flinging
mScroller.stopScroller();
mScroller.stopBoundScrollAnimation();
+ Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator);
// Initialize the velocity tracker
initOrResetVelocityTracker();
@@ -189,6 +197,9 @@
float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
float curScrollP = mDownScrollP + deltaP;
mScroller.setStackScroll(curScrollP);
+ if (DEBUG) {
+ Log.d(TAG, "scroll: " + curScrollP);
+ }
}
mVelocityTracker.addMovement(ev);
@@ -211,21 +222,53 @@
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int y = (int) ev.getY(activePointerIndex);
int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
+ float curScrollP = mScroller.getStackScroll();
if (mIsScrolling) {
- if (mScroller.isScrollOutOfBounds()) {
- // Animate the scroll back into bounds
+ boolean hasFreeformTasks = mSv.mStack.hasFreeformTasks();
+ if (hasFreeformTasks && velocity > 0 &&
+ curScrollP > layoutAlgorithm.mStackEndScrollP) {
+ // Snap to workspace
+ float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+ layoutAlgorithm.mPreferredStackEndScrollP);
+ mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
+ mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
+ (Integer) animation.getAnimatedValue());
+ float scroll = mDownScrollP + deltaP;
+ mScroller.setStackScroll(scroll);
+ }
+ });
+ mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
+ mScrollFlingAnimator.start();
+ } else if (hasFreeformTasks && velocity < 0 &&
+ curScrollP > (layoutAlgorithm.mStackEndScrollP -
+ layoutAlgorithm.mTaskHalfHeightPOffset)) {
+ // Snap to stack
+ float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+ layoutAlgorithm.mMaxScrollP);
+ mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY);
+ mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float deltaP = layoutAlgorithm.getDeltaPForY(mDownY,
+ (Integer) animation.getAnimatedValue());
+ float scroll = mDownScrollP + deltaP;
+ mScroller.setStackScroll(scroll);
+ }
+ });
+ mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity);
+ mScrollFlingAnimator.start();
+ } else if (mScroller.isScrollOutOfBounds()) {
mScroller.animateBoundScroll();
} else if (Math.abs(velocity) > mMinimumVelocity) {
- float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y);
- float curScrollP = mDownScrollP + deltaP;
- float downToCurY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
- curScrollP);
- float downToMinY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
- layoutAlgorithm.mMaxScrollP);
- float downToMaxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+ float minY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
+ layoutAlgorithm.mPreferredStackEndScrollP);
+ float maxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP,
layoutAlgorithm.mMinScrollP);
- mScroller.fling(mDownScrollP, mDownY, (int) downToCurY, velocity,
- (int) downToMinY, (int) downToMaxY, mOverscrollSize);
+ mScroller.fling(mDownScrollP, mDownY, y, velocity, (int) minY, (int) maxY,
+ mOverscrollSize);
mSv.invalidate();
}
} else if (mActiveTaskView == null) {
@@ -239,10 +282,6 @@
break;
}
case MotionEvent.ACTION_CANCEL: {
- if (mScroller.isScrollOutOfBounds()) {
- // Animate the scroll back into bounds
- mScroller.animateBoundScroll();
- }
mActivePointerId = INACTIVE_POINTER_ID;
mIsScrolling = false;
recycleVelocityTracker();
@@ -308,9 +347,6 @@
@Override
public boolean canChildBeDismissed(View v) {
- if (v instanceof TaskView) {
- return !((TaskView) v).getTask().isFreeformTask();
- }
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index aaacc6c..57cb599 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -552,7 +552,11 @@
* view.
*/
boolean shouldClipViewInStack() {
- return mClipViewInStack && (getVisibility() == View.VISIBLE);
+ // Never clip for freeform tasks or if invisible
+ if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) {
+ return false;
+ }
+ return mClipViewInStack;
}
/** Sets whether this view should be clipped, or clipped against. */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index f6353f8..649199e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -255,6 +255,10 @@
mApplicationIcon.setImageDrawable(null);
mApplicationIcon.setOnClickListener(null);
mMoveTaskButton.setOnClickListener(null);
+
+ // Stop any focus animations
+ Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
+ mBackground.jumpToCurrentState();
}
/** Updates the resize task bar button. */
@@ -370,8 +374,9 @@
boolean isRunning = false;
if (mFocusAnimator != null) {
isRunning = mFocusAnimator.isRunning();
- Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
}
+ Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
+ mBackground.jumpToCurrentState();
if (focused) {
// If we are not animating the visible state, just return
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 1cf7a70f..673a30b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -125,10 +125,6 @@
return mAudio;
}
- public ZenModeConfig getZenModeConfig() {
- return mNoMan.getZenModeConfig();
- }
-
public void dismiss() {
mCallbacks.onDismissRequested(Events.DISMISS_REASON_VOLUME_CONTROLLER);
}
@@ -348,7 +344,6 @@
updateRingerModeExternalW(mAudio.getRingerMode());
updateZenModeW();
updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
- updateZenModeConfigW();
mCallbacks.onStateChanged(mState);
}
@@ -401,13 +396,6 @@
return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
}
- private boolean updateZenModeConfigW() {
- final ZenModeConfig zenModeConfig = getZenModeConfig();
- if (Objects.equals(mState.zenModeConfig, zenModeConfig)) return false;
- mState.zenModeConfig = zenModeConfig;
- return true;
- }
-
private boolean updateEffectsSuppressorW(ComponentName effectsSuppressor) {
if (Objects.equals(mState.effectsSuppressor, effectsSuppressor)) return false;
mState.effectsSuppressor = effectsSuppressor;
@@ -748,9 +736,6 @@
if (ZEN_MODE_URI.equals(uri)) {
changed = updateZenModeW();
}
- if (ZEN_MODE_CONFIG_URI.equals(uri)) {
- changed = updateZenModeConfigW();
- }
if (changed) {
mCallbacks.onStateChanged(mState);
}
@@ -947,7 +932,6 @@
public int zenMode;
public ComponentName effectsSuppressor;
public String effectsSuppressorName;
- public ZenModeConfig zenModeConfig;
public int activeStream = NO_ACTIVE_STREAM;
public State copy() {
@@ -960,7 +944,6 @@
rt.zenMode = zenMode;
if (effectsSuppressor != null) rt.effectsSuppressor = effectsSuppressor.clone();
rt.effectsSuppressorName = effectsSuppressorName;
- if (zenModeConfig != null) rt.zenModeConfig = zenModeConfig.copy();
rt.activeStream = activeStream;
return rt;
}
@@ -989,7 +972,6 @@
sep(sb, indent); sb.append("zenMode:").append(zenMode);
sep(sb, indent); sb.append("effectsSuppressor:").append(effectsSuppressor);
sep(sb, indent); sb.append("effectsSuppressorName:").append(effectsSuppressorName);
- sep(sb, indent); sb.append("zenModeConfig:").append(zenModeConfig);
sep(sb, indent); sb.append("activeStream:").append(activeStream);
if (indent > 0) sep(sb, indent);
return sb.append('}').toString();
@@ -1005,11 +987,6 @@
sb.append(',');
}
}
-
- public Condition getManualExitCondition() {
- return zenModeConfig != null && zenModeConfig.manualRule != null
- ? zenModeConfig.manualRule.condition : null;
- }
}
public interface Callbacks {
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index ed6fc00..a5ddc12 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -20,7 +20,10 @@
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.AlarmManager;
+import android.app.AppOpsManager;
import android.app.BroadcastOptions;
+import android.app.IAlarmCompleteListener;
+import android.app.IAlarmListener;
import android.app.IAlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -94,12 +97,13 @@
static final boolean DEBUG_BATCH = localLOGV || false;
static final boolean DEBUG_VALIDATE = localLOGV || false;
static final boolean DEBUG_ALARM_CLOCK = localLOGV || false;
+ static final boolean DEBUG_LISTENER_CALLBACK = localLOGV || false;
static final boolean RECORD_ALARMS_IN_HISTORY = true;
static final boolean RECORD_DEVICE_IDLE_ALARMS = false;
static final int ALARM_EVENT = 1;
static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
- static final Intent mBackgroundIntent
+ private final Intent mBackgroundIntent
= new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder();
@@ -110,6 +114,8 @@
final LocalLog mLog = new LocalLog(TAG);
+ AppOpsManager mAppOps;
+
final Object mLock = new Object();
long mNativeData;
@@ -124,7 +130,7 @@
ClockReceiver mClockReceiver;
InteractiveStateReceiver mInteractiveStateReceiver;
private UninstallReceiver mUninstallReceiver;
- final ResultReceiver mResultReceiver = new ResultReceiver();
+ final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
PendingIntent mTimeTickSender;
PendingIntent mDateChangeSender;
Random mRandom;
@@ -185,6 +191,7 @@
private static final String KEY_ALLOW_WHILE_IDLE_LONG_TIME = "allow_while_idle_long_time";
private static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION
= "allow_while_idle_whitelist_duration";
+ private static final String KEY_LISTENER_TIMEOUT = "listener_timeout";
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
@@ -192,6 +199,8 @@
private static final long DEFAULT_ALLOW_WHILE_IDLE_LONG_TIME = 9*60*1000;
private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10*1000;
+ private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
+
// Minimum futurity of a new alarm
public long MIN_FUTURITY = DEFAULT_MIN_FUTURITY;
@@ -208,6 +217,9 @@
public long ALLOW_WHILE_IDLE_WHITELIST_DURATION
= DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
+ // Direct alarm listener callback timeout
+ public long LISTENER_TIMEOUT = DEFAULT_LISTENER_TIMEOUT;
+
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
private long mLastAllowWhileIdleWhitelistDuration = -1;
@@ -264,6 +276,8 @@
ALLOW_WHILE_IDLE_WHITELIST_DURATION = mParser.getLong(
KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION,
DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION);
+ LISTENER_TIMEOUT = mParser.getLong(KEY_LISTENER_TIMEOUT,
+ DEFAULT_LISTENER_TIMEOUT);
updateAllowWhileIdleMinTimeLocked();
updateAllowWhileIdleWhitelistDurationLocked();
@@ -281,6 +295,10 @@
TimeUtils.formatDuration(MIN_INTERVAL, pw);
pw.println();
+ pw.print(" "); pw.print(KEY_LISTENER_TIMEOUT); pw.print("=");
+ TimeUtils.formatDuration(LISTENER_TIMEOUT, pw);
+ pw.println();
+
pw.print(" "); pw.print(KEY_ALLOW_WHILE_IDLE_SHORT_TIME); pw.print("=");
TimeUtils.formatDuration(ALLOW_WHILE_IDLE_SHORT_TIME, pw);
pw.println();
@@ -388,14 +406,21 @@
return newStart;
}
- boolean remove(final PendingIntent operation) {
+ boolean remove(final PendingIntent operation, final IAlarmListener listener) {
+ if (operation == null && listener == null) {
+ if (localLOGV) {
+ Slog.w(TAG, "requested remove() of null operation",
+ new RuntimeException("here"));
+ }
+ return false;
+ }
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
int newFlags = 0;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
- if (alarm.operation.equals(operation)) {
+ if (alarm.matches(operation, listener)) {
alarms.remove(i);
didRemove = true;
if (alarm.alarmClock != null) {
@@ -422,13 +447,20 @@
}
boolean remove(final String packageName) {
+ if (packageName == null) {
+ if (localLOGV) {
+ Slog.w(TAG, "requested remove() of null packageName",
+ new RuntimeException("here"));
+ }
+ return false;
+ }
boolean didRemove = false;
long newStart = 0; // recalculate endpoints as we go
long newEnd = Long.MAX_VALUE;
int newFlags = 0;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
- if (alarm.operation.getTargetPackage().equals(packageName)) {
+ if (alarm.matches(packageName)) {
alarms.remove(i);
didRemove = true;
if (alarm.alarmClock != null) {
@@ -460,7 +492,7 @@
long newEnd = Long.MAX_VALUE;
for (int i = 0; i < alarms.size(); ) {
Alarm alarm = alarms.get(i);
- if (UserHandle.getUserId(alarm.operation.getCreatorUid()) == userHandle) {
+ if (UserHandle.getUserId(alarm.creatorUid) == userHandle) {
alarms.remove(i);
didRemove = true;
if (alarm.alarmClock != null) {
@@ -488,7 +520,7 @@
final int N = alarms.size();
for (int i = 0; i < N; i++) {
Alarm a = alarms.get(i);
- if (a.operation.getTargetPackage().equals(packageName)) {
+ if (a.matches(packageName)) {
return true;
}
}
@@ -565,7 +597,8 @@
Alarm a = alarms.get(i);
final int alarmPrio;
- if (Intent.ACTION_TIME_TICK.equals(a.operation.getIntent().getAction())) {
+ if (a.operation != null
+ && Intent.ACTION_TIME_TICK.equals(a.operation.getIntent().getAction())) {
alarmPrio = PRIO_TICK;
} else if (a.wakeup) {
alarmPrio = PRIO_WAKEUP;
@@ -574,10 +607,13 @@
}
PriorityClass packagePrio = a.priorityClass;
- if (packagePrio == null) packagePrio = mPriorities.get(a.operation.getCreatorPackage());
+ String alarmPackage = (a.operation != null)
+ ? a.operation.getCreatorPackage()
+ : a.packageName;
+ if (packagePrio == null) packagePrio = mPriorities.get(alarmPackage);
if (packagePrio == null) {
packagePrio = a.priorityClass = new PriorityClass(); // lowest prio & stale sequence
- mPriorities.put(a.operation.getCreatorPackage(), packagePrio);
+ mPriorities.put(alarmPackage, packagePrio);
}
a.priorityClass = packagePrio;
@@ -744,20 +780,27 @@
}
}
- static final class InFlight extends Intent {
+ static final class InFlight {
final PendingIntent mPendingIntent;
+ final IBinder mListener;
final WorkSource mWorkSource;
+ final int mUid;
final String mTag;
final BroadcastStats mBroadcastStats;
final FilterStats mFilterStats;
final int mAlarmType;
- InFlight(AlarmManagerService service, PendingIntent pendingIntent, WorkSource workSource,
- int alarmType, String tag, long nowELAPSED) {
+ InFlight(AlarmManagerService service, PendingIntent pendingIntent, IAlarmListener listener,
+ WorkSource workSource, int uid, String alarmPkg, int alarmType, String tag,
+ long nowELAPSED) {
mPendingIntent = pendingIntent;
+ mListener = listener != null ? listener.asBinder() : null;
mWorkSource = workSource;
+ mUid = uid;
mTag = tag;
- mBroadcastStats = service.getStatsLocked(pendingIntent);
+ mBroadcastStats = (pendingIntent != null)
+ ? service.getStatsLocked(pendingIntent)
+ : service.getStatsLocked(uid, alarmPkg);
FilterStats fs = mBroadcastStats.filterStats.get(mTag);
if (fs == null) {
fs = new FilterStats(mBroadcastStats, mTag);
@@ -853,6 +896,7 @@
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
mConstants.start(getContext().getContentResolver());
+ mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
}
}
@@ -905,15 +949,20 @@
return;
}
synchronized (mLock) {
- removeLocked(operation);
+ removeLocked(operation, null);
}
}
void setImpl(int type, long triggerAtTime, long windowLength, long interval,
- PendingIntent operation, int flags, WorkSource workSource,
- AlarmManager.AlarmClockInfo alarmClock, int callingUid) {
- if (operation == null) {
- Slog.w(TAG, "set/setRepeating ignored because there is no intent");
+ PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
+ int flags, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
+ int callingUid, String callingPackage) {
+ // must be *either* PendingIntent or AlarmReceiver, but not both
+ if ((operation == null && directReceiver == null)
+ || (operation != null && directReceiver != null)) {
+ Slog.w(TAG, "Alarms must either supply a PendingIntent or an AlarmReceiver");
+ // NB: previous releases failed silently here, so we are continuing to do the same
+ // rather than throw an IllegalArgumentException.
return;
}
@@ -971,17 +1020,19 @@
+ " interval=" + interval + " flags=0x" + Integer.toHexString(flags));
}
setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
- interval, operation, flags, true, workSource, alarmClock, callingUid);
+ interval, operation, directReceiver, listenerTag, flags, true, workSource,
+ alarmClock, callingUid, callingPackage);
}
}
private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
- long maxWhen, long interval, PendingIntent operation, int flags,
- boolean doValidate, WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock,
- int uid) {
+ long maxWhen, long interval, PendingIntent operation, IAlarmListener directReceiver,
+ String listenerTag, int flags, boolean doValidate, WorkSource workSource,
+ AlarmManager.AlarmClockInfo alarmClock, int callingUid, String callingPackage) {
Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
- operation, workSource, flags, alarmClock, uid);
- removeLocked(operation);
+ operation, directReceiver, listenerTag, workSource, flags, alarmClock,
+ callingUid, callingPackage);
+ removeLocked(operation, directReceiver);
setImplLocked(a, false, doValidate);
}
@@ -1109,10 +1160,31 @@
private final IBinder mService = new IAlarmManager.Stub() {
@Override
- public void set(int type, long triggerAtTime, long windowLength, long interval, int flags,
- PendingIntent operation, WorkSource workSource,
- AlarmManager.AlarmClockInfo alarmClock) {
+ public void set(String callingPackage,
+ int type, long triggerAtTime, long windowLength, long interval, int flags,
+ PendingIntent operation, IAlarmListener directReceiver, String listenerTag,
+ WorkSource workSource, AlarmManager.AlarmClockInfo alarmClock) {
final int callingUid = Binder.getCallingUid();
+
+ // make sure the caller is not lying about which package should be blamed for
+ // wakelock time spent in alarm delivery
+ mAppOps.checkPackage(callingUid, callingPackage);
+
+ // Repeating alarms must use PendingIntent, not direct listener
+ if (interval != 0) {
+ if (directReceiver != null) {
+ throw new IllegalArgumentException("Repeating alarms cannot use AlarmReceivers");
+ }
+ }
+
+ // direct-callback alarms must be wakeup alarms (otherwise they should just be
+ // posting work to a Handler)
+ if (directReceiver != null) {
+ if (type != RTC_WAKEUP && type != ELAPSED_REALTIME_WAKEUP) {
+ throw new IllegalArgumentException("Only wakeup alarms can use AlarmReceivers");
+ }
+ }
+
if (workSource != null) {
getContext().enforcePermission(
android.Manifest.permission.UPDATE_DEVICE_STATS,
@@ -1149,8 +1221,8 @@
flags |= AlarmManager.FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
}
- setImpl(type, triggerAtTime, windowLength, interval, operation,
- flags, workSource, alarmClock, callingUid);
+ setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
+ listenerTag, flags, workSource, alarmClock, callingUid, callingPackage);
}
@Override
@@ -1184,9 +1256,15 @@
}
@Override
- public void remove(PendingIntent operation) {
- removeImpl(operation);
+ public void remove(PendingIntent operation, IAlarmListener listener) {
+ if (operation == null && listener == null) {
+ Slog.w(TAG, "remove() with no intent or listener");
+ return;
+ }
+ synchronized (mLock) {
+ removeLocked(operation, listener);
+ }
}
@Override
@@ -1697,17 +1775,17 @@
}
}
- private void removeLocked(PendingIntent operation) {
+ private void removeLocked(PendingIntent operation, IAlarmListener directReceiver) {
boolean didRemove = false;
for (int i = mAlarmBatches.size() - 1; i >= 0; i--) {
Batch b = mAlarmBatches.get(i);
- didRemove |= b.remove(operation);
+ didRemove |= b.remove(operation, directReceiver);
if (b.size() == 0) {
mAlarmBatches.remove(i);
}
}
for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
- if (mPendingWhileIdleAlarms.get(i).operation.equals(operation)) {
+ if (mPendingWhileIdleAlarms.get(i).matches(operation, directReceiver)) {
// Don't set didRemove, since this doesn't impact the scheduled alarms.
mPendingWhileIdleAlarms.remove(i);
}
@@ -1718,11 +1796,11 @@
Slog.v(TAG, "remove(operation) changed bounds; rebatching");
}
boolean restorePending = false;
- if (mPendingIdleUntil != null && mPendingIdleUntil.operation.equals(operation)) {
+ if (mPendingIdleUntil != null && mPendingIdleUntil.matches(operation, directReceiver)) {
mPendingIdleUntil = null;
restorePending = true;
}
- if (mNextWakeFromIdle != null && mNextWakeFromIdle.operation.equals(operation)) {
+ if (mNextWakeFromIdle != null && mNextWakeFromIdle.matches(operation, directReceiver)) {
mNextWakeFromIdle = null;
}
rebatchAllAlarmsLocked(true);
@@ -1743,7 +1821,8 @@
}
}
for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
- if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+ final Alarm a = mPendingWhileIdleAlarms.get(i);
+ if (a.matches(packageName)) {
// Don't set didRemove, since this doesn't impact the scheduled alarms.
mPendingWhileIdleAlarms.remove(i);
}
@@ -1769,7 +1848,7 @@
}
}
for (int i = mPendingWhileIdleAlarms.size() - 1; i >= 0; i--) {
- if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).operation.getCreatorUid())
+ if (UserHandle.getUserId(mPendingWhileIdleAlarms.get(i).creatorUid)
== userHandle) {
// Don't set didRemove, since this doesn't impact the scheduled alarms.
mPendingWhileIdleAlarms.remove(i);
@@ -1825,7 +1904,8 @@
}
}
for (int i = 0; i < mPendingWhileIdleAlarms.size(); i++) {
- if (mPendingWhileIdleAlarms.get(i).operation.getTargetPackage().equals(packageName)) {
+ final Alarm a = mPendingWhileIdleAlarms.get(i);
+ if (a.matches(packageName)) {
return true;
}
}
@@ -1948,7 +2028,7 @@
triggerList.add(alarm);
if ((alarm.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
EventLogTags.writeDeviceIdleWakeFromIdle(mPendingIdleUntil != null ? 1 : 0,
- alarm.tag);
+ alarm.statsTag);
}
if (mPendingIdleUntil == alarm) {
mPendingIdleUntil = null;
@@ -1972,8 +2052,8 @@
final long nextElapsed = alarm.whenElapsed + delta;
setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
- alarm.repeatInterval, alarm.operation, alarm.flags, true,
- alarm.workSource, alarm.alarmClock, alarm.uid);
+ alarm.repeatInterval, alarm.operation, null, null, alarm.flags, true,
+ alarm.workSource, alarm.alarmClock, alarm.uid, alarm.packageName);
}
if (alarm.wakeup) {
@@ -2024,11 +2104,15 @@
public final long origWhen;
public final boolean wakeup;
public final PendingIntent operation;
- public final String tag;
+ public final IAlarmListener listener;
+ public final String listenerTag;
+ public final String statsTag;
public final WorkSource workSource;
public final int flags;
public final AlarmManager.AlarmClockInfo alarmClock;
public final int uid;
+ public final int creatorUid;
+ public final String packageName;
public int count;
public long when;
public long windowLength;
@@ -2038,8 +2122,9 @@
public PriorityClass priorityClass;
public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
- long _interval, PendingIntent _op, WorkSource _ws, int _flags,
- AlarmManager.AlarmClockInfo _info, int _uid) {
+ long _interval, PendingIntent _op, IAlarmListener _rec, String _listenerTag,
+ WorkSource _ws, int _flags, AlarmManager.AlarmClockInfo _info,
+ int _uid, String _pkgName) {
type = _type;
origWhen = _when;
wakeup = _type == AlarmManager.ELAPSED_REALTIME_WAKEUP
@@ -2050,16 +2135,42 @@
maxWhenElapsed = _maxWhen;
repeatInterval = _interval;
operation = _op;
- tag = makeTag(_op, _type);
+ listener = _rec;
+ listenerTag = _listenerTag;
+ statsTag = makeTag(_op, _listenerTag, _type);
workSource = _ws;
flags = _flags;
alarmClock = _info;
uid = _uid;
+ packageName = _pkgName;
+
+ creatorUid = (operation != null) ? operation.getCreatorUid() : uid;
}
- public static String makeTag(PendingIntent pi, int type) {
- return pi.getTag(type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP
- ? "*walarm*:" : "*alarm*:");
+ public static String makeTag(PendingIntent pi, String tag, int type) {
+ final String alarmString = type == ELAPSED_REALTIME_WAKEUP || type == RTC_WAKEUP
+ ? "*walarm*:" : "*alarm*:";
+ return (pi != null) ? pi.getTag(alarmString) : (alarmString + tag);
+ }
+
+ public WakeupEvent makeWakeupEvent(long nowRTC) {
+ return new WakeupEvent(nowRTC, creatorUid,
+ (operation != null)
+ ? operation.getIntent().getAction()
+ : ("<listener>:" + listenerTag));
+ }
+
+ // Returns true if either matches
+ public boolean matches(PendingIntent pi, IAlarmListener rec) {
+ return (operation != null)
+ ? operation.equals(pi)
+ : listener.asBinder().equals(rec.asBinder());
+ }
+
+ public boolean matches(String packageName) {
+ return (operation != null)
+ ? packageName.equals(operation.getTargetPackage())
+ : packageName.equals(this.packageName);
}
@Override
@@ -2072,7 +2183,11 @@
sb.append(" when ");
sb.append(when);
sb.append(" ");
- sb.append(operation.getTargetPackage());
+ if (operation != null) {
+ sb.append(operation.getTargetPackage());
+ } else {
+ sb.append(listener.asBinder().toString());
+ }
sb.append('}');
return sb.toString();
}
@@ -2080,14 +2195,15 @@
public void dump(PrintWriter pw, String prefix, long nowRTC, long nowELAPSED,
SimpleDateFormat sdf) {
final boolean isRtc = (type == RTC || type == RTC_WAKEUP);
- pw.print(prefix); pw.print("tag="); pw.println(tag);
+ pw.print(prefix); pw.print("tag="); pw.println(statsTag);
pw.print(prefix); pw.print("type="); pw.print(type);
pw.print(" whenElapsed="); TimeUtils.formatDuration(whenElapsed,
nowELAPSED, pw);
+ pw.print(" when=");
if (isRtc) {
- pw.print(" when="); pw.print(sdf.format(new Date(when)));
+ pw.print(sdf.format(new Date(when)));
} else {
- pw.print(" when="); TimeUtils.formatDuration(when, nowELAPSED, pw);
+ TimeUtils.formatDuration(when, nowELAPSED, pw);
}
pw.println();
pw.print(prefix); pw.print("window="); TimeUtils.formatDuration(windowLength, pw);
@@ -2101,6 +2217,9 @@
pw.print(prefix); pw.print(" showIntent="); pw.println(alarmClock.getShowIntent());
}
pw.print(prefix); pw.print("operation="); pw.println(operation);
+ if (listener != null) {
+ pw.print(prefix); pw.print("listener="); pw.println(listener.asBinder());
+ }
}
}
@@ -2115,10 +2234,7 @@
final int numAlarms = b.alarms.size();
for (int nextAlarm = 0; nextAlarm < numAlarms; nextAlarm++) {
Alarm a = b.alarms.get(nextAlarm);
- WakeupEvent e = new WakeupEvent(nowRTC,
- a.operation.getCreatorUid(),
- a.operation.getIntent().getAction());
- mRecentWakeups.add(e);
+ mRecentWakeups.add(a.makeWakeupEvent(nowRTC));
}
}
}
@@ -2181,80 +2297,14 @@
if (alarm.workSource != null && alarm.workSource.size() > 0) {
for (int wi=0; wi<alarm.workSource.size(); wi++) {
ActivityManagerNative.noteAlarmStart(
- alarm.operation, alarm.workSource.get(wi), alarm.tag);
+ alarm.operation, alarm.workSource.get(wi), alarm.statsTag);
}
} else {
ActivityManagerNative.noteAlarmStart(
- alarm.operation, -1, alarm.tag);
+ alarm.operation, alarm.uid, alarm.statsTag);
}
}
- alarm.operation.send(getContext(), 0,
- mBackgroundIntent.putExtra(
- Intent.EXTRA_ALARM_COUNT, alarm.count),
- mResultReceiver, mHandler, null, allowWhileIdle ? mIdleOptions : null);
-
- // we have an active broadcast so stay awake.
- if (mBroadcastRefCount == 0) {
- setWakelockWorkSource(alarm.operation, alarm.workSource,
- alarm.type, alarm.tag, true);
- mWakeLock.acquire();
- }
- final InFlight inflight = new InFlight(AlarmManagerService.this,
- alarm.operation, alarm.workSource, alarm.type, alarm.tag, nowELAPSED);
- mInFlight.add(inflight);
- mBroadcastRefCount++;
-
- if (allowWhileIdle) {
- // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
- mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED);
- if (RECORD_DEVICE_IDLE_ALARMS) {
- IdleDispatchEntry ent = new IdleDispatchEntry();
- ent.uid = alarm.uid;
- ent.pkg = alarm.operation.getCreatorPackage();
- ent.tag = alarm.operation.getTag("");
- ent.op = "DELIVER";
- ent.elapsedRealtime = nowELAPSED;
- mAllowWhileIdleDispatches.add(ent);
- }
- }
-
- final BroadcastStats bs = inflight.mBroadcastStats;
- bs.count++;
- if (bs.nesting == 0) {
- bs.nesting = 1;
- bs.startTime = nowELAPSED;
- } else {
- bs.nesting++;
- }
- final FilterStats fs = inflight.mFilterStats;
- fs.count++;
- if (fs.nesting == 0) {
- fs.nesting = 1;
- fs.startTime = nowELAPSED;
- } else {
- fs.nesting++;
- }
- if (alarm.type == ELAPSED_REALTIME_WAKEUP
- || alarm.type == RTC_WAKEUP) {
- bs.numWakeup++;
- fs.numWakeup++;
- if (alarm.workSource != null && alarm.workSource.size() > 0) {
- for (int wi=0; wi<alarm.workSource.size(); wi++) {
- ActivityManagerNative.noteWakeupAlarm(
- alarm.operation, alarm.workSource.get(wi),
- alarm.workSource.getName(wi), alarm.tag);
- }
- } else {
- ActivityManagerNative.noteWakeupAlarm(
- alarm.operation, -1, null, alarm.tag);
- }
- }
- } catch (PendingIntent.CanceledException e) {
- if (alarm.repeatInterval > 0) {
- // This IntentSender is no longer valid, but this
- // is a repeating alarm, so toss the hoser.
- removeImpl(alarm.operation);
- }
+ mDeliveryTracker.deliverLocked(alarm, nowELAPSED, allowWhileIdle);
} catch (RuntimeException e) {
Slog.w(TAG, "Failure sending alarm.", e);
}
@@ -2384,9 +2434,10 @@
* Attribute blame for a WakeLock.
* @param pi PendingIntent to attribute blame to if ws is null.
* @param ws WorkSource to attribute blame.
+ * @param knownUid attribution uid; < 0 if we need to derive it from the PendingIntent sender
*/
void setWakelockWorkSource(PendingIntent pi, WorkSource ws, int type, String tag,
- boolean first) {
+ int knownUid, boolean first) {
try {
final boolean unimportant = pi == mTimeTickSender;
mWakeLock.setUnimportantForLogging(unimportant);
@@ -2401,8 +2452,9 @@
return;
}
- final int uid = ActivityManagerNative.getDefault()
- .getUidForIntentSender(pi.getTarget());
+ final int uid = (knownUid >= 0)
+ ? knownUid
+ : ActivityManagerNative.getDefault().getUidForIntentSender(pi.getTarget());
if (uid >= 0) {
mWakeLock.setWorkSource(new WorkSource(uid));
return;
@@ -2419,35 +2471,49 @@
public static final int MINUTE_CHANGE_EVENT = 2;
public static final int DATE_CHANGE_EVENT = 3;
public static final int SEND_NEXT_ALARM_CLOCK_CHANGED = 4;
+ public static final int LISTENER_TIMEOUT = 5;
public AlarmHandler() {
}
public void handleMessage(Message msg) {
- if (msg.what == ALARM_EVENT) {
- ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
- synchronized (mLock) {
- final long nowRTC = System.currentTimeMillis();
- final long nowELAPSED = SystemClock.elapsedRealtime();
- triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
- updateNextAlarmClockLocked();
- }
+ switch (msg.what) {
+ case ALARM_EVENT: {
+ ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
+ synchronized (mLock) {
+ final long nowRTC = System.currentTimeMillis();
+ final long nowELAPSED = SystemClock.elapsedRealtime();
+ triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
+ updateNextAlarmClockLocked();
+ }
- // now trigger the alarms without the lock held
- for (int i=0; i<triggerList.size(); i++) {
- Alarm alarm = triggerList.get(i);
- try {
- alarm.operation.send();
- } catch (PendingIntent.CanceledException e) {
- if (alarm.repeatInterval > 0) {
- // This IntentSender is no longer valid, but this
- // is a repeating alarm, so toss the hoser.
- removeImpl(alarm.operation);
+ // now trigger the alarms without the lock held
+ for (int i=0; i<triggerList.size(); i++) {
+ Alarm alarm = triggerList.get(i);
+ try {
+ alarm.operation.send();
+ } catch (PendingIntent.CanceledException e) {
+ if (alarm.repeatInterval > 0) {
+ // This IntentSender is no longer valid, but this
+ // is a repeating alarm, so toss the hoser.
+ removeImpl(alarm.operation);
+ }
}
}
+ break;
}
- } else if (msg.what == SEND_NEXT_ALARM_CLOCK_CHANGED) {
- sendNextAlarmClockChanged();
+
+ case SEND_NEXT_ALARM_CLOCK_CHANGED:
+ sendNextAlarmClockChanged();
+ break;
+
+ case LISTENER_TIMEOUT:
+ mDeliveryTracker.alarmTimedOut((IBinder) msg.obj);
+ break;
+
+ default:
+ // nope, just ignore it
+ break;
}
}
}
@@ -2489,8 +2555,8 @@
final WorkSource workSource = null; // Let system take blame for time tick events.
setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
- 0, mTimeTickSender, AlarmManager.FLAG_STANDALONE, workSource, null,
- Process.myUid());
+ 0, mTimeTickSender, null, null, AlarmManager.FLAG_STANDALONE, workSource,
+ null, Process.myUid(), "android");
}
public void scheduleDateChangedEvent() {
@@ -2503,8 +2569,9 @@
calendar.add(Calendar.DAY_OF_MONTH, 1);
final WorkSource workSource = null; // Let system take blame for date change events.
- setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender,
- AlarmManager.FLAG_STANDALONE, workSource, null, Process.myUid());
+ setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, null, null,
+ AlarmManager.FLAG_STANDALONE, workSource, null,
+ Process.myUid(), "android");
}
}
@@ -2602,80 +2669,260 @@
private final BroadcastStats getStatsLocked(PendingIntent pi) {
String pkg = pi.getCreatorPackage();
int uid = pi.getCreatorUid();
+ return getStatsLocked(uid, pkg);
+ }
+
+ private final BroadcastStats getStatsLocked(int uid, String pkgName) {
ArrayMap<String, BroadcastStats> uidStats = mBroadcastStats.get(uid);
if (uidStats == null) {
uidStats = new ArrayMap<String, BroadcastStats>();
mBroadcastStats.put(uid, uidStats);
}
- BroadcastStats bs = uidStats.get(pkg);
+ BroadcastStats bs = uidStats.get(pkgName);
if (bs == null) {
- bs = new BroadcastStats(uid, pkg);
- uidStats.put(pkg, bs);
+ bs = new BroadcastStats(uid, pkgName);
+ uidStats.put(pkgName, bs);
}
return bs;
}
- class ResultReceiver implements PendingIntent.OnFinished {
+ class DeliveryTracker extends IAlarmCompleteListener.Stub implements PendingIntent.OnFinished {
+ private InFlight removeLocked(PendingIntent pi, Intent intent) {
+ for (int i = 0; i < mInFlight.size(); i++) {
+ if (mInFlight.get(i).mPendingIntent == pi) {
+ return mInFlight.remove(i);
+ }
+ }
+ mLog.w("No in-flight alarm for " + pi + " " + intent);
+ return null;
+ }
+
+ private InFlight removeLocked(IBinder listener) {
+ for (int i = 0; i < mInFlight.size(); i++) {
+ if (mInFlight.get(i).mListener == listener) {
+ return mInFlight.remove(i);
+ }
+ }
+ mLog.w("No in-flight alarm for listener " + listener);
+ return null;
+ }
+
+ private void updateStatsLocked(InFlight inflight) {
+ final long nowELAPSED = SystemClock.elapsedRealtime();
+ BroadcastStats bs = inflight.mBroadcastStats;
+ bs.nesting--;
+ if (bs.nesting <= 0) {
+ bs.nesting = 0;
+ bs.aggregateTime += nowELAPSED - bs.startTime;
+ }
+ FilterStats fs = inflight.mFilterStats;
+ fs.nesting--;
+ if (fs.nesting <= 0) {
+ fs.nesting = 0;
+ fs.aggregateTime += nowELAPSED - fs.startTime;
+ }
+ if (RECORD_ALARMS_IN_HISTORY) {
+ if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) {
+ for (int wi=0; wi<inflight.mWorkSource.size(); wi++) {
+ ActivityManagerNative.noteAlarmFinish(
+ inflight.mPendingIntent, inflight.mWorkSource.get(wi), inflight.mTag);
+ }
+ } else {
+ ActivityManagerNative.noteAlarmFinish(
+ inflight.mPendingIntent, inflight.mUid, inflight.mTag);
+ }
+ }
+ }
+
+ private void updateTrackingLocked(InFlight inflight) {
+ if (inflight != null) {
+ updateStatsLocked(inflight);
+ }
+ mBroadcastRefCount--;
+ if (mBroadcastRefCount == 0) {
+ mWakeLock.release();
+ if (mInFlight.size() > 0) {
+ mLog.w("Finished all dispatches with " + mInFlight.size()
+ + " remaining inflights");
+ for (int i=0; i<mInFlight.size(); i++) {
+ mLog.w(" Remaining #" + i + ": " + mInFlight.get(i));
+ }
+ mInFlight.clear();
+ }
+ } else {
+ // the next of our alarms is now in flight. reattribute the wakelock.
+ if (mInFlight.size() > 0) {
+ InFlight inFlight = mInFlight.get(0);
+ setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
+ inFlight.mAlarmType, inFlight.mTag, -1, false);
+ } else {
+ // should never happen
+ mLog.w("Alarm wakelock still held but sent queue empty");
+ mWakeLock.setWorkSource(null);
+ }
+ }
+ }
+
+ /**
+ * Callback that arrives when a direct-call alarm reports that delivery has finished
+ */
+ @Override
+ public void alarmComplete(IBinder who) {
+ if (who == null) {
+ Slog.w(TAG, "Invalid alarmComplete: uid=" + Binder.getCallingUid()
+ + " pid=" + Binder.getCallingPid());
+ return;
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ mHandler.removeMessages(AlarmHandler.LISTENER_TIMEOUT, who);
+ InFlight inflight = removeLocked(who);
+ if (inflight != null) {
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "alarmComplete() from " + who);
+ }
+ updateTrackingLocked(inflight);
+ } else {
+ // Delivery timed out, and the timeout handling already took care of
+ // updating our tracking here, so we needn't do anything further.
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "Late alarmComplete() from " + who);
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * Callback that arrives when a PendingIntent alarm has finished delivery
+ */
+ @Override
public void onSendFinished(PendingIntent pi, Intent intent, int resultCode,
String resultData, Bundle resultExtras) {
synchronized (mLock) {
- InFlight inflight = null;
- for (int i=0; i<mInFlight.size(); i++) {
- if (mInFlight.get(i).mPendingIntent == pi) {
- inflight = mInFlight.remove(i);
- break;
- }
- }
+ updateTrackingLocked(removeLocked(pi, intent));
+ }
+ }
+
+ /**
+ * Timeout of a direct-call alarm delivery
+ */
+ public void alarmTimedOut(IBinder who) {
+ synchronized (mLock) {
+ InFlight inflight = removeLocked(who);
if (inflight != null) {
- final long nowELAPSED = SystemClock.elapsedRealtime();
- BroadcastStats bs = inflight.mBroadcastStats;
- bs.nesting--;
- if (bs.nesting <= 0) {
- bs.nesting = 0;
- bs.aggregateTime += nowELAPSED - bs.startTime;
+ // TODO: implement ANR policy for the target
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "Alarm listener " + who + " timed out in delivery");
}
- FilterStats fs = inflight.mFilterStats;
- fs.nesting--;
- if (fs.nesting <= 0) {
- fs.nesting = 0;
- fs.aggregateTime += nowELAPSED - fs.startTime;
- }
- if (RECORD_ALARMS_IN_HISTORY) {
- if (inflight.mWorkSource != null && inflight.mWorkSource.size() > 0) {
- for (int wi=0; wi<inflight.mWorkSource.size(); wi++) {
- ActivityManagerNative.noteAlarmFinish(
- pi, inflight.mWorkSource.get(wi), inflight.mTag);
- }
- } else {
- ActivityManagerNative.noteAlarmFinish(
- pi, -1, inflight.mTag);
- }
- }
+ updateTrackingLocked(inflight);
} else {
- mLog.w("No in-flight alarm for " + pi + " " + intent);
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "Spurious timeout of listener " + who);
+ }
}
- mBroadcastRefCount--;
- if (mBroadcastRefCount == 0) {
- mWakeLock.release();
- if (mInFlight.size() > 0) {
- mLog.w("Finished all broadcasts with " + mInFlight.size()
- + " remaining inflights");
- for (int i=0; i<mInFlight.size(); i++) {
- mLog.w(" Remaining #" + i + ": " + mInFlight.get(i));
- }
- mInFlight.clear();
+ }
+ }
+
+ /**
+ * Deliver an alarm and set up the post-delivery handling appropriately
+ */
+ public void deliverLocked(Alarm alarm, long nowELAPSED, boolean allowWhileIdle) {
+ if (alarm.operation != null) {
+ // PendingIntent alarm
+ try {
+ alarm.operation.send(getContext(), 0,
+ mBackgroundIntent.putExtra(
+ Intent.EXTRA_ALARM_COUNT, alarm.count),
+ mDeliveryTracker, mHandler, null,
+ allowWhileIdle ? mIdleOptions : null);
+ } catch (PendingIntent.CanceledException e) {
+ if (alarm.repeatInterval > 0) {
+ // This IntentSender is no longer valid, but this
+ // is a repeating alarm, so toss it
+ removeImpl(alarm.operation);
+ }
+ }
+ } else {
+ // Direct listener callback alarm
+ try {
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.v(TAG, "Alarm to uid=" + alarm.uid
+ + " listener=" + alarm.listener.asBinder());
+ }
+ alarm.listener.doAlarm(this);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(AlarmHandler.LISTENER_TIMEOUT,
+ alarm.listener.asBinder()),
+ mConstants.LISTENER_TIMEOUT);
+ } catch (Exception e) {
+ if (DEBUG_LISTENER_CALLBACK) {
+ Slog.i(TAG, "Alarm undeliverable to listener "
+ + alarm.listener.asBinder(), e);
+ }
+ }
+ }
+
+ // The alarm is now in flight; now arrange wakelock and stats tracking
+ if (mBroadcastRefCount == 0) {
+ setWakelockWorkSource(alarm.operation, alarm.workSource,
+ alarm.type, alarm.statsTag, (alarm.operation == null) ? alarm.uid : -1,
+ true);
+ mWakeLock.acquire();
+ }
+ final InFlight inflight = new InFlight(AlarmManagerService.this,
+ alarm.operation, alarm.listener, alarm.workSource, alarm.uid,
+ alarm.packageName, alarm.type, alarm.statsTag, nowELAPSED);
+ mInFlight.add(inflight);
+ mBroadcastRefCount++;
+
+ if (allowWhileIdle) {
+ // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
+ mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED);
+ if (RECORD_DEVICE_IDLE_ALARMS) {
+ IdleDispatchEntry ent = new IdleDispatchEntry();
+ ent.uid = alarm.uid;
+ ent.pkg = alarm.packageName;
+ ent.tag = alarm.statsTag;
+ ent.op = "DELIVER";
+ ent.elapsedRealtime = nowELAPSED;
+ mAllowWhileIdleDispatches.add(ent);
+ }
+ }
+
+ final BroadcastStats bs = inflight.mBroadcastStats;
+ bs.count++;
+ if (bs.nesting == 0) {
+ bs.nesting = 1;
+ bs.startTime = nowELAPSED;
+ } else {
+ bs.nesting++;
+ }
+ final FilterStats fs = inflight.mFilterStats;
+ fs.count++;
+ if (fs.nesting == 0) {
+ fs.nesting = 1;
+ fs.startTime = nowELAPSED;
+ } else {
+ fs.nesting++;
+ }
+ if (alarm.type == ELAPSED_REALTIME_WAKEUP
+ || alarm.type == RTC_WAKEUP) {
+ bs.numWakeup++;
+ fs.numWakeup++;
+ if (alarm.workSource != null && alarm.workSource.size() > 0) {
+ for (int wi=0; wi<alarm.workSource.size(); wi++) {
+ ActivityManagerNative.noteWakeupAlarm(
+ alarm.operation, alarm.workSource.get(wi),
+ alarm.workSource.getName(wi), alarm.statsTag);
}
} else {
- // the next of our alarms is now in flight. reattribute the wakelock.
- if (mInFlight.size() > 0) {
- InFlight inFlight = mInFlight.get(0);
- setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource,
- inFlight.mAlarmType, inFlight.mTag, false);
- } else {
- // should never happen
- mLog.w("Alarm wakelock still held but sent queue empty");
- mWakeLock.setWorkSource(null);
- }
+ ActivityManagerNative.noteWakeupAlarm(
+ alarm.operation, -1, alarm.packageName, alarm.statsTag);
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 29544f3..5aab804 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -11364,7 +11364,7 @@
}
public void noteWakeupAlarm(IIntentSender sender, int sourceUid, String sourcePkg, String tag) {
- if (!(sender instanceof PendingIntentRecord)) {
+ if (sender != null && !(sender instanceof PendingIntentRecord)) {
return;
}
final PendingIntentRecord rec = (PendingIntentRecord)sender;
@@ -11373,7 +11373,12 @@
if (mBatteryStatsService.isOnBattery()) {
mBatteryStatsService.enforceCallingPermission();
int MY_UID = Binder.getCallingUid();
- int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ final int uid;
+ if (sender == null) {
+ uid = sourceUid;
+ } else {
+ uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ }
BatteryStatsImpl.Uid.Pkg pkg =
stats.getPackageStatsLocked(sourceUid >= 0 ? sourceUid : uid,
sourcePkg != null ? sourcePkg : rec.key.packageName);
@@ -11383,7 +11388,7 @@
}
public void noteAlarmStart(IIntentSender sender, int sourceUid, String tag) {
- if (!(sender instanceof PendingIntentRecord)) {
+ if (sender != null && !(sender instanceof PendingIntentRecord)) {
return;
}
final PendingIntentRecord rec = (PendingIntentRecord)sender;
@@ -11391,13 +11396,18 @@
synchronized (stats) {
mBatteryStatsService.enforceCallingPermission();
int MY_UID = Binder.getCallingUid();
- int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ final int uid;
+ if (sender == null) {
+ uid = sourceUid;
+ } else {
+ uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ }
mBatteryStatsService.noteAlarmStart(tag, sourceUid >= 0 ? sourceUid : uid);
}
}
public void noteAlarmFinish(IIntentSender sender, int sourceUid, String tag) {
- if (!(sender instanceof PendingIntentRecord)) {
+ if (sender != null && !(sender instanceof PendingIntentRecord)) {
return;
}
final PendingIntentRecord rec = (PendingIntentRecord)sender;
@@ -11405,7 +11415,12 @@
synchronized (stats) {
mBatteryStatsService.enforceCallingPermission();
int MY_UID = Binder.getCallingUid();
- int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ final int uid;
+ if (sender == null) {
+ uid = sourceUid;
+ } else {
+ uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+ }
mBatteryStatsService.noteAlarmFinish(tag, sourceUid >= 0 ? sourceUid : uid);
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c4b57f1..66e731a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -109,6 +109,7 @@
import com.android.internal.util.XmlUtils;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
+import com.android.server.SystemService;
import com.android.server.pm.UserManagerService;
import org.xmlpull.v1.XmlPullParserException;
@@ -279,7 +280,7 @@
0, // STREAM_MUSIC
0, // STREAM_ALARM
0, // STREAM_NOTIFICATION
- 1, // STREAM_BLUETOOTH_SCO
+ 0, // STREAM_BLUETOOTH_SCO
0, // STREAM_SYSTEM_ENFORCED
0, // STREAM_DTMF
0 // STREAM_TTS
@@ -564,6 +565,27 @@
return "card=" + card + ";device=" + device + ";";
}
+ public static final class Lifecycle extends SystemService {
+ private AudioService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ mService = new AudioService(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.AUDIO_SERVICE, mService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ mService.systemReady();
+ }
+ }
+ }
+
///////////////////////////////////////////////////////////////////////////
// Construction
///////////////////////////////////////////////////////////////////////////
@@ -781,7 +803,8 @@
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
VolumeStreamState streamState = mStreamStates[streamType];
- AudioSystem.initStreamVolume(streamType, 0, (streamState.mIndexMax + 5) / 10);
+ AudioSystem.initStreamVolume(
+ streamType, streamState.mIndexMin / 10, streamState.mIndexMax / 10);
streamState.applyAllVolumes();
}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 19d8538..9441d88 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
@@ -29,6 +30,7 @@
import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionListener;
import android.service.notification.IConditionProvider;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -79,7 +81,7 @@
final Config c = new Config();
c.caption = "condition provider";
c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
- c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
+ c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES;
c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
c.clientLabel = R.string.condition_provider_service_binding_label;
@@ -280,6 +282,26 @@
}
}
+ @Override
+ protected ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName,
+ int userId) {
+ final ContentResolver cr = mContext.getContentResolver();
+ String settingValue = Settings.Secure.getStringForUser(
+ cr,
+ settingName,
+ userId);
+ if (TextUtils.isEmpty(settingValue))
+ return null;
+ String[] packages = settingValue.split(ENABLED_SERVICES_SEPARATOR);
+ ArraySet<ComponentName> result = new ArraySet<>(packages.length);
+ for (int i = 0; i < packages.length; i++) {
+ if (!TextUtils.isEmpty(packages[i])) {
+ result.addAll(queryPackageForServices(packages[i], userId));
+ }
+ }
+ return result;
+ }
+
public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
synchronized (mMutex) {
final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index a54a61a..d2a264d 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -70,7 +70,7 @@
protected final String TAG = getClass().getSimpleName();
protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final String ENABLED_SERVICES_SEPARATOR = ":";
+ protected static final String ENABLED_SERVICES_SEPARATOR = ":";
protected final Context mContext;
protected final Object mMutex;
@@ -279,7 +279,8 @@
}
- private ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName, int userId) {
+ protected ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName,
+ int userId) {
final ContentResolver cr = mContext.getContentResolver();
String settingValue = Settings.Secure.getStringForUser(
cr,
@@ -319,7 +320,6 @@
userId);
}
-
/**
* Remove access for any services that no longer exist.
*/
@@ -332,18 +332,15 @@
rebuildRestoredPackages();
}
- private void updateSettingsAccordingToInstalledServices(int userId) {
- boolean restoredChanged = false;
- boolean currentChanged = false;
- Set<ComponentName> restored =
- loadComponentNamesFromSetting(restoredSettingName(mConfig), userId);
- Set<ComponentName> current =
- loadComponentNamesFromSetting(mConfig.secureSettingName, userId);
+ protected Set<ComponentName> queryPackageForServices(String packageName, int userId) {
Set<ComponentName> installed = new ArraySet<>();
-
final PackageManager pm = mContext.getPackageManager();
+ Intent queryIntent = new Intent(mConfig.serviceInterface);
+ if (!TextUtils.isEmpty(packageName)) {
+ queryIntent.setPackage(packageName);
+ }
List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
- new Intent(mConfig.serviceInterface),
+ queryIntent,
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
userId);
if (DEBUG)
@@ -363,6 +360,18 @@
}
installed.add(component);
}
+ return installed;
+ }
+
+ private void updateSettingsAccordingToInstalledServices(int userId) {
+ boolean restoredChanged = false;
+ boolean currentChanged = false;
+ Set<ComponentName> restored =
+ loadComponentNamesFromSetting(restoredSettingName(mConfig), userId);
+ Set<ComponentName> current =
+ loadComponentNamesFromSetting(mConfig.secureSettingName, userId);
+ // Load all services for all packages.
+ Set<ComponentName> installed = queryPackageForServices(null, userId);
ArraySet<ComponentName> retained = new ArraySet<>();
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 361bbf9..b84811f 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1579,12 +1579,6 @@
}
@Override
- public boolean setZenModeConfig(ZenModeConfig config, String reason) {
- checkCallerIsSystem();
- return mZenModeHelper.setConfig(config, reason);
- }
-
- @Override
public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException {
enforceSystemOrSystemUIOrVolume("INotificationManager.setZenMode");
final long identity = Binder.clearCallingIdentity();
@@ -1669,12 +1663,6 @@
});
}
- @Override
- public void requestZenModeConditions(IConditionListener callback, int relevance) {
- enforceSystemOrSystemUIOrVolume("INotificationManager.requestZenModeConditions");
- mZenModeHelper.requestZenModeConditions(callback, relevance);
- }
-
private void enforceSystemOrSystemUIOrVolume(String message) {
if (mAudioManagerInternal != null) {
final int vcuid = mAudioManagerInternal.getVolumeControllerUid();
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 76c6443..a1f8c41 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -121,8 +121,11 @@
public boolean matchesCallFilter(UserHandle userHandle, Bundle extras,
ValidateNotificationPeople validator, int contactsTimeoutMs, float timeoutAffinity) {
- return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConfig, userHandle, extras,
- validator, contactsTimeoutMs, timeoutAffinity);
+ synchronized (mConfig) {
+ return ZenModeFiltering.matchesCallFilter(mContext, mZenMode, mConfig, userHandle,
+ extras,
+ validator, contactsTimeoutMs, timeoutAffinity);
+ }
}
public boolean isCall(NotificationRecord record) {
@@ -130,7 +133,9 @@
}
public boolean shouldIntercept(NotificationRecord record) {
- return mFiltering.shouldIntercept(mZenMode, mConfig, record);
+ synchronized (mConfig) {
+ return mFiltering.shouldIntercept(mZenMode, mConfig, record);
+ }
}
public void addCallback(Callback callback) {
@@ -175,10 +180,6 @@
mConfigs.remove(user);
}
- public void requestZenModeConditions(IConditionListener callback, int relevance) {
- mConditions.requestConditions(callback, relevance);
- }
-
public int getZenModeListenerInterruptionFilter() {
return NotificationManager.zenModeToInterruptionFilter(mZenMode);
}
@@ -203,18 +204,23 @@
public List<AutomaticZenRule> getAutomaticZenRules() {
List<AutomaticZenRule> rules = new ArrayList<>();
- if (mConfig == null) return rules;
- for(ZenRule rule : mConfig.automaticRules.values()) {
- if (canManageAutomaticZenRule(rule)) {
- rules.add(createAutomaticZenRule(rule));
+ synchronized (mConfig) {
+ if (mConfig == null) return rules;
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (canManageAutomaticZenRule(rule)) {
+ rules.add(createAutomaticZenRule(rule));
+ }
}
}
return rules;
}
public AutomaticZenRule getAutomaticZenRule(String id) {
- if (mConfig == null) return null;
- ZenRule rule = mConfig.automaticRules.get(id);
+ ZenRule rule;
+ synchronized (mConfig) {
+ if (mConfig == null) return null;
+ rule = mConfig.automaticRules.get(id);
+ }
if (rule == null) return null;
if (canManageAutomaticZenRule(rule)) {
return createAutomaticZenRule(rule);
@@ -223,14 +229,18 @@
}
public AutomaticZenRule addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
- if (mConfig == null) return null;
- if (DEBUG) {
- Log.d(TAG, "addAutomaticZenRule zenRule= " + automaticZenRule + " reason=" +reason);
+ ZenModeConfig newConfig;
+ synchronized (mConfig) {
+ if (mConfig == null) return null;
+ if (DEBUG) {
+ Log.d(TAG,
+ "addAutomaticZenRule zenRule= " + automaticZenRule + " reason=" + reason);
+ }
+ if (!TextUtils.isEmpty(automaticZenRule.getId())) {
+ throw new IllegalArgumentException("Rule already exists");
+ }
+ newConfig = mConfig.copy();
}
- if (!TextUtils.isEmpty(automaticZenRule.getId())) {
- throw new IllegalArgumentException("Rule already exists");
- }
- final ZenModeConfig newConfig = mConfig.copy();
ZenRule rule = new ZenRule();
populateZenRule(automaticZenRule, rule, true);
newConfig.automaticRules.put(rule.id, rule);
@@ -242,12 +252,15 @@
}
public boolean updateAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
- if (mConfig == null) return false;
- if (DEBUG) {
- Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
- + " reason=" + reason);
+ ZenModeConfig newConfig;
+ synchronized (mConfig) {
+ if (mConfig == null) return false;
+ if (DEBUG) {
+ Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
+ + " reason=" + reason);
+ }
+ newConfig = mConfig.copy();
}
- final ZenModeConfig newConfig = mConfig.copy();
final String ruleId = automaticZenRule.getId();
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
if (ruleId == null) {
@@ -265,8 +278,11 @@
}
public boolean removeAutomaticZenRule(String id, String reason) {
- if (mConfig == null) return false;
- final ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig newConfig;
+ synchronized (mConfig) {
+ if (mConfig == null) return false;
+ newConfig = mConfig.copy();
+ }
ZenRule rule = newConfig.automaticRules.get(id);
if (rule == null) return false;
if (canManageAutomaticZenRule(rule)) {
@@ -328,12 +344,15 @@
private void setManualZenMode(int zenMode, Uri conditionId, String reason,
boolean setRingerMode) {
- if (mConfig == null) return;
- if (!Global.isValidZenMode(zenMode)) return;
- if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
- + " conditionId=" + conditionId + " reason=" + reason
- + " setRingerMode=" + setRingerMode);
- final ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig newConfig;
+ synchronized (mConfig) {
+ if (mConfig == null) return;
+ if (!Global.isValidZenMode(zenMode)) return;
+ if (DEBUG) Log.d(TAG, "setManualZenMode " + Global.zenModeToString(zenMode)
+ + " conditionId=" + conditionId + " reason=" + reason
+ + " setRingerMode=" + setRingerMode);
+ newConfig = mConfig.copy();
+ }
if (zenMode == Global.ZEN_MODE_OFF) {
newConfig.manualRule = null;
for (ZenRule automaticRule : newConfig.automaticRules.values()) {
@@ -360,7 +379,9 @@
dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i));
}
pw.print(prefix); pw.print("mUser="); pw.println(mUser);
- dump(pw, prefix, "mConfig", mConfig);
+ synchronized (mConfig) {
+ dump(pw, prefix, "mConfig", mConfig);
+ }
pw.print(prefix); pw.print("mEffectsSuppressed="); pw.println(mEffectsSuppressed);
mFiltering.dump(pw, prefix);
mConditions.dump(pw, prefix);
@@ -437,7 +458,9 @@
}
public ZenModeConfig getConfig() {
- return mConfig;
+ synchronized (mConfig) {
+ return mConfig.copy();
+ }
}
public boolean setConfig(ZenModeConfig config, String reason) {
@@ -462,19 +485,21 @@
return true;
}
mConditions.evaluateConfig(config, false /*processSubscriptions*/); // may modify config
- mConfigs.put(config.user, config);
- if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
- ZenLog.traceConfig(reason, mConfig, config);
- final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
- getNotificationPolicy(config));
- mConfig = config;
- if (config.equals(mConfig)) {
- dispatchOnConfigChanged();
+ synchronized (mConfig) {
+ mConfigs.put(config.user, config);
+ if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
+ ZenLog.traceConfig(reason, mConfig, config);
+ final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
+ getNotificationPolicy(config));
+ mConfig = config;
+ if (config.equals(mConfig)) {
+ dispatchOnConfigChanged();
+ }
+ if (policyChanged) {
+ dispatchOnPolicyChanged();
+ }
}
- if (policyChanged){
- dispatchOnPolicyChanged();
- }
- final String val = Integer.toString(mConfig.hashCode());
+ final String val = Integer.toString(config.hashCode());
Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val);
if (!evaluateZenMode(reason, setRingerMode)) {
applyRestrictions(); // evaluateZenMode will also apply restrictions if changed
@@ -529,17 +554,19 @@
}
private int computeZenMode() {
- if (mConfig == null) return Global.ZEN_MODE_OFF;
- if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
- int zen = Global.ZEN_MODE_OFF;
- for (ZenRule automaticRule : mConfig.automaticRules.values()) {
- if (automaticRule.isAutomaticActive()) {
- if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
- zen = automaticRule.zenMode;
+ synchronized (mConfig) {
+ if (mConfig == null) return Global.ZEN_MODE_OFF;
+ if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
+ int zen = Global.ZEN_MODE_OFF;
+ for (ZenRule automaticRule : mConfig.automaticRules.values()) {
+ if (automaticRule.isAutomaticActive()) {
+ if (zenSeverity(automaticRule.zenMode) > zenSeverity(zen)) {
+ zen = automaticRule.zenMode;
+ }
}
}
+ return zen;
}
- return zen;
}
private void applyRestrictions() {
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index 89b2a47..01acdef 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -414,12 +414,9 @@
return NO_ERROR;
}
if (Surface::isValid(connection.mSurface)) {
- connection.mSurface.clear();
- }
- if (connection.mSurface != NULL) {
connection.mSurface->setSidebandStream(NULL);
- connection.mSurface.clear();
}
+ connection.mSurface.clear();
if (connection.mThread != NULL) {
connection.mThread->shutdown();
connection.mThread.clear();
@@ -616,6 +613,9 @@
return BAD_VALUE;
}
sp<Surface> surface(android_view_Surface_getSurface(env, jsurface));
+ if (!Surface::isValid(surface)) {
+ return BAD_VALUE;
+ }
return tvInputHal->addOrUpdateStream(deviceId, streamId, surface);
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1ec1a46..e32af5c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -438,7 +438,6 @@
InputManagerService inputManager = null;
TelephonyRegistry telephonyRegistry = null;
ConsumerIrService consumerIr = null;
- AudioService audioService = null;
MmsServiceBroker mmsService = null;
EntropyMixer entropyMixer = null;
@@ -857,12 +856,7 @@
}
traceBeginAndSlog("StartAudioService");
- try {
- audioService = new AudioService(context);
- ServiceManager.addService(Context.AUDIO_SERVICE, audioService);
- } catch (Throwable e) {
- reportWtf("starting Audio Service", e);
- }
+ mSystemServiceManager.startService(AudioService.Lifecycle.class);
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
if (!disableNonCoreServices) {
@@ -1163,7 +1157,6 @@
final InputManagerService inputManagerF = inputManager;
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
final MediaRouterService mediaRouterF = mediaRouter;
- final AudioService audioServiceF = audioService;
final MmsServiceBroker mmsServiceF = mmsService;
// We now tell the activity manager it is okay to run third party
@@ -1234,13 +1227,7 @@
reportWtf("making Connectivity Service ready", e);
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "MakeAudioServiceReady");
- try {
- if (audioServiceF != null) audioServiceF.systemReady();
- } catch (Throwable e) {
- reportWtf("Notifying AudioService running", e);
- }
- Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+
Watchdog.getInstance().start();
// It is now okay to let the various system services start their
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 90b4f43..c12f978 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -876,11 +876,12 @@
}
private void expectSystemReady() throws Exception {
- mAlarmManager.remove(isA(PendingIntent.class));
+ mAlarmManager.remove(isA(PendingIntent.class), null);
expectLastCall().anyTimes();
- mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
- anyInt(), isA(PendingIntent.class), isA(WorkSource.class),
+ mAlarmManager.set(getContext().getPackageName(),
+ eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
+ anyInt(), isA(PendingIntent.class), null, null, isA(WorkSource.class),
isA(AlarmManager.AlarmClockInfo.class));
expectLastCall().atLeastOnce();
diff --git a/telephony/java/com/android/ims/ImsCallProfile.java b/telephony/java/com/android/ims/ImsCallProfile.java
index f263b4d..861a379 100644
--- a/telephony/java/com/android/ims/ImsCallProfile.java
+++ b/telephony/java/com/android/ims/ImsCallProfile.java
@@ -178,6 +178,7 @@
* Codec: Codec info.
* DisplayText: Display text for the call.
* AdditionalCallInfo: Additional call info.
+ * CallRadioTech: The radio tech on which the call is placed.
*/
public static final String EXTRA_OI = "oi";
public static final String EXTRA_CNA = "cna";
@@ -187,6 +188,7 @@
public static final String EXTRA_CODEC = "Codec";
public static final String EXTRA_DISPLAY_TEXT = "DisplayText";
public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
+ public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
public int mServiceType;
public int mCallType;
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
index becf99b..cc5e981 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/JavaClassGenerator_test.cpp
@@ -105,11 +105,9 @@
.addSimple(u"@android:id/one", ResourceId(0x01020000))
.addSimple(u"@android:id/two", ResourceId(0x01020001))
.addSimple(u"@android:id/three", ResourceId(0x01020002))
+ .setSymbolState(u"@android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
+ .setSymbolState(u"@android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
.build();
- ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/one"), {}, {},
- SymbolState::kPublic, &diag));
- ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:id/two"), {}, {},
- SymbolState::kPrivate, &diag));
JavaClassGeneratorOptions options;
options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index bfef9d0..44710eb 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -186,6 +186,7 @@
Source source;
ResourceId id;
SymbolState symbolState = SymbolState::kUndefined;
+ std::u16string comment;
std::unique_ptr<Value> value;
std::list<ParsedResource> childResources;
};
@@ -194,7 +195,11 @@
static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config,
IDiagnostics* diag, ParsedResource* res) {
if (res->symbolState != SymbolState::kUndefined) {
- if (!table->setSymbolState(res->name, res->id, res->source, res->symbolState, diag)) {
+ Symbol symbol;
+ symbol.state = res->symbolState;
+ symbol.source = res->source;
+ symbol.comment = res->comment;
+ if (!table->setSymbolState(res->name, res->id, symbol, diag)) {
return false;
}
}
@@ -203,7 +208,11 @@
return true;
}
- if (!table->addResource(res->name, res->id, config, res->source, std::move(res->value), diag)) {
+ // Attach the comment, source and config to the value.
+ res->value->setComment(std::move(res->comment));
+ res->value->setSource(std::move(res->source));
+
+ if (!table->addResource(res->name, res->id, config, std::move(res->value), diag)) {
return false;
}
@@ -275,6 +284,7 @@
ParsedResource parsedResource;
parsedResource.name.entry = maybeName.value().toString();
parsedResource.source = mSource.withLine(parser->getLineNumber());
+ parsedResource.comment = std::move(comment);
bool result = true;
if (elementName == u"id") {
@@ -368,8 +378,8 @@
* an Item. If allowRawValue is false, nullptr is returned in this
* case.
*/
-std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, uint32_t typeMask,
- bool allowRawValue) {
+std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint32_t typeMask,
+ const bool allowRawValue) {
const size_t beginXmlLine = parser->getLineNumber();
std::u16string rawValue;
@@ -386,8 +396,9 @@
auto onCreateReference = [&](const ResourceName& name) {
// name.package can be empty here, as it will assume the package name of the table.
- mTable->addResource(name, {}, mSource.withLine(beginXmlLine), util::make_unique<Id>(),
- mDiag);
+ std::unique_ptr<Id> id = util::make_unique<Id>();
+ id->setSource(mSource.withLine(beginXmlLine));
+ mTable->addResource(name, {}, std::move(id), mDiag);
};
// Process the raw value.
@@ -411,11 +422,12 @@
mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig }));
}
- // We can't parse this so return a RawString if we are allowed.
if (allowRawValue) {
+ // We can't parse this so return a RawString if we are allowed.
return util::make_unique<RawString>(
mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig }));
}
+
return {};
}
@@ -683,8 +695,8 @@
}
return Attribute::Symbol{
- Reference(ResourceName{ {}, ResourceType::kId, maybeName.value().toString() }),
- val.data };
+ Reference(ResourceName({}, ResourceType::kId, maybeName.value().toString())),
+ val.data };
}
static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 34c68d7..2d5a29d 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -65,12 +65,13 @@
StyleString* outStyleString);
/*
- * Parses the XML subtree and converts it to an Item. The type of Item that can be
- * parsed is denoted by the `typeMask`. If `allowRawValue` is true and the subtree
- * can not be parsed as a regular Item, then a RawString is returned. Otherwise
- * this returns nullptr.
+ * Parses the XML subtree and returns an Item.
+ * The type of Item that can be parsed is denoted by the `typeMask`.
+ * If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a
+ * RawString is returned. Otherwise this returns false;
*/
- std::unique_ptr<Item> parseXml(XmlPullParser* parser, uint32_t typeMask, bool allowRawValue);
+ std::unique_ptr<Item> parseXml(XmlPullParser* parser, const uint32_t typeMask,
+ const bool allowRawValue);
bool parseResources(XmlPullParser* parser);
bool parseString(XmlPullParser* parser, ParsedResource* outResource);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index a7e9d39..af6bf67 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -379,18 +379,39 @@
}
TEST_F(ResourceParserTest, ParseCommentsWithResource) {
- std::string input = "<!-- This is a comment -->\n"
+ std::string input = "<!--This is a comment-->\n"
"<string name=\"foo\">Hi</string>";
ASSERT_TRUE(testParse(input));
- Maybe<ResourceTable::SearchResult> result = mTable.findResource(
- test::parseNameOrDie(u"@string/foo"));
- AAPT_ASSERT_TRUE(result);
+ String* value = test::getValue<String>(&mTable, u"@string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->getComment(), u"This is a comment");
+}
- ResourceEntry* entry = result.value().entry;
- ASSERT_NE(entry, nullptr);
- ASSERT_FALSE(entry->values.empty());
- EXPECT_EQ(entry->values.front().comment, u"This is a comment");
+TEST_F(ResourceParserTest, DoNotCombineMultipleComments) {
+ std::string input = "<!--One-->\n"
+ "<!--Two-->\n"
+ "<string name=\"foo\">Hi</string>";
+
+ ASSERT_TRUE(testParse(input));
+
+ String* value = test::getValue<String>(&mTable, u"@string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->getComment(), u"Two");
+}
+
+TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) {
+ std::string input = "<!--One-->\n"
+ "<string name=\"foo\">\n"
+ " Hi\n"
+ "<!--Two-->\n"
+ "</string>";
+
+ ASSERT_TRUE(testParse(input));
+
+ String* value = test::getValue<String>(&mTable, u"@string/foo");
+ ASSERT_NE(nullptr, value);
+ EXPECT_EQ(value->getComment(), u"One");
}
/*
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 84674e8..fa4b109 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -19,6 +19,8 @@
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
+
+#include "util/Comparators.h"
#include "util/Util.h"
#include <algorithm>
@@ -29,10 +31,6 @@
namespace aapt {
-static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) {
- return lhs.config < rhs;
-}
-
static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
return lhs->type < rhs;
}
@@ -191,52 +189,50 @@
static constexpr const char16_t* kValidNameMangledChars = u"._-$";
bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
- const Source& source, std::unique_ptr<Value> value,
- IDiagnostics* diag) {
- return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars,
- diag);
+ std::unique_ptr<Value> value, IDiagnostics* diag) {
+ return addResourceImpl(name, {}, config, std::move(value), kValidNameChars, diag);
}
bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
- const ConfigDescription& config, const Source& source,
- std::unique_ptr<Value> value, IDiagnostics* diag) {
- return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars, diag);
+ const ConfigDescription& config, std::unique_ptr<Value> value,
+ IDiagnostics* diag) {
+ return addResourceImpl(name, resId, config, std::move(value), kValidNameChars, diag);
}
bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
const Source& source, const StringPiece16& path,
IDiagnostics* diag) {
- return addResourceImpl(name, ResourceId{}, config, source,
- util::make_unique<FileReference>(stringPool.makeRef(path)),
- kValidNameChars, diag);
+ std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
+ stringPool.makeRef(path));
+ fileRef->setSource(source);
+ return addResourceImpl(name, ResourceId{}, config, std::move(fileRef), kValidNameChars, diag);
}
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
const ConfigDescription& config,
- const Source& source,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
- return addResourceImpl(name, ResourceId{}, config, source, std::move(value),
- kValidNameMangledChars, diag);
+ return addResourceImpl(name, ResourceId{}, config, std::move(value), kValidNameMangledChars,
+ diag);
}
bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
const ResourceId id,
const ConfigDescription& config,
- const Source& source,
std::unique_ptr<Value> value,
IDiagnostics* diag) {
- return addResourceImpl(name, id, config, source, std::move(value),
- kValidNameMangledChars, diag);
+ return addResourceImpl(name, id, config, std::move(value), kValidNameMangledChars, diag);
}
bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
- const ConfigDescription& config, const Source& source,
- std::unique_ptr<Value> value, const char16_t* validChars,
- IDiagnostics* diag) {
+ const ConfigDescription& config, std::unique_ptr<Value> value,
+ const char16_t* validChars, IDiagnostics* diag) {
+ assert(value && "value can't be nullptr");
+ assert(diag && "diagnostics can't be nullptr");
+
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
if (badCharIter != name.entry.end()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(value->getSource())
<< "resource '"
<< name
<< "' has invalid entry name '"
@@ -249,7 +245,7 @@
ResourceTablePackage* package = findOrCreatePackage(name.package);
if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(value->getSource())
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -263,7 +259,7 @@
ResourceTableType* type = package->findOrCreateType(name.type);
if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(value->getSource())
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -277,7 +273,7 @@
ResourceEntry* entry = type->findOrCreateEntry(name.entry);
if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(value->getSource())
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -288,20 +284,20 @@
}
const auto endIter = entry->values.end();
- auto iter = std::lower_bound(entry->values.begin(), endIter, config, compareConfigs);
+ auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThan);
if (iter == endIter || iter->config != config) {
// This resource did not exist before, add it.
- entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
+ entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
} else {
int collisionResult = resolveValueCollision(iter->value.get(), value.get());
if (collisionResult > 0) {
// Take the incoming value.
- *iter = ResourceConfigValue{ config, source, {}, std::move(value) };
+ iter->value = std::move(value);
} else if (collisionResult == 0) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(value->getSource())
<< "duplicate value for resource '" << name << "' "
- << "with config '" << iter->config << "'");
- diag->error(DiagMessage(iter->source)
+ << "with config '" << config << "'");
+ diag->error(DiagMessage(iter->value->getSource())
<< "resource previously defined here");
return false;
}
@@ -316,27 +312,29 @@
}
bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId resId,
- const Source& source, SymbolState state, IDiagnostics* diag) {
- return setSymbolStateImpl(name, resId, source, state, kValidNameChars, diag);
+ const Symbol& symbol, IDiagnostics* diag) {
+ return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag);
}
-bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
- const Source& source, SymbolState state,
- IDiagnostics* diag) {
- return setSymbolStateImpl(name, resId, source, state, kValidNameMangledChars, diag);
+bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name,
+ const ResourceId resId,
+ const Symbol& symbol, IDiagnostics* diag) {
+ return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag);
}
bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
- const Source& source, SymbolState state,
- const char16_t* validChars, IDiagnostics* diag) {
- if (state == SymbolState::kUndefined) {
+ const Symbol& symbol, const char16_t* validChars,
+ IDiagnostics* diag) {
+ assert(diag && "diagnostics can't be nullptr");
+
+ if (symbol.state == SymbolState::kUndefined) {
// Nothing to do.
return true;
}
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
if (badCharIter != name.entry.end()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(symbol.source)
<< "resource '"
<< name
<< "' has invalid entry name '"
@@ -349,7 +347,7 @@
ResourceTablePackage* package = findOrCreatePackage(name.package);
if (resId.isValid() && package->id && package->id.value() != resId.packageId()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(symbol.source)
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -363,7 +361,7 @@
ResourceTableType* type = package->findOrCreateType(name.type);
if (resId.isValid() && type->id && type->id.value() != resId.typeId()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(symbol.source)
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -377,7 +375,7 @@
ResourceEntry* entry = type->findOrCreateEntry(name.entry);
if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) {
- diag->error(DiagMessage(source)
+ diag->error(DiagMessage(symbol.source)
<< "trying to add resource '"
<< name
<< "' with ID "
@@ -388,15 +386,14 @@
}
// Only mark the type state as public, it doesn't care about being private.
- if (state == SymbolState::kPublic) {
+ if (symbol.state == SymbolState::kPublic) {
type->symbolStatus.state = SymbolState::kPublic;
}
// Downgrading to a private symbol from a public one is not allowed.
if (entry->symbolStatus.state != SymbolState::kPublic) {
- if (entry->symbolStatus.state != state) {
- entry->symbolStatus.state = state;
- entry->symbolStatus.source = source;
+ if (entry->symbolStatus.state != symbol.state) {
+ entry->symbolStatus = std::move(symbol);
}
}
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index be90936..980504b 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -47,12 +47,10 @@
};
/**
- * The resource value for a specific configuration.
+ * Represents a value defined for a given configuration.
*/
struct ResourceConfigValue {
ConfigDescription config;
- Source source;
- std::u16string comment;
std::unique_ptr<Value> value;
};
@@ -158,12 +156,11 @@
static int resolveValueCollision(Value* existing, Value* incoming);
bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
- const Source& source, std::unique_ptr<Value> value,
- IDiagnostics* diag);
+ std::unique_ptr<Value> value, IDiagnostics* diag);
bool addResource(const ResourceNameRef& name, const ResourceId resId,
- const ConfigDescription& config, const Source& source,
- std::unique_ptr<Value> value, IDiagnostics* diag);
+ const ConfigDescription& config, std::unique_ptr<Value> value,
+ IDiagnostics* diag);
bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
const Source& source, const StringPiece16& path, IDiagnostics* diag);
@@ -174,18 +171,18 @@
* names.
*/
bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
- const Source& source, std::unique_ptr<Value> value,
- IDiagnostics* diag);
+ std::unique_ptr<Value> value, IDiagnostics* diag);
bool addResourceAllowMangled(const ResourceNameRef& name, const ResourceId id,
- const ConfigDescription& config,
- const Source& source, std::unique_ptr<Value> value,
+ const ConfigDescription& config, std::unique_ptr<Value> value,
IDiagnostics* diag);
- bool setSymbolState(const ResourceNameRef& name, const ResourceId resId, const Source& source,
- SymbolState state, IDiagnostics* diag);
+ bool setSymbolState(const ResourceNameRef& name, const ResourceId resId,
+ const Symbol& symbol, IDiagnostics* diag);
+
bool setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
- const Source& source, SymbolState state, IDiagnostics* diag);
+ const Symbol& symbol, IDiagnostics* diag);
+
struct SearchResult {
ResourceTablePackage* package;
ResourceTableType* type;
@@ -224,13 +221,11 @@
private:
ResourceTablePackage* findOrCreatePackage(const StringPiece16& name);
- bool addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
- const ConfigDescription& config, const Source& source,
- std::unique_ptr<Value> value, const char16_t* validChars,
- IDiagnostics* diag);
- bool setSymbolStateImpl(const ResourceNameRef& name, const ResourceId resId,
- const Source& source, SymbolState state, const char16_t* validChars,
- IDiagnostics* diag);
+ bool addResourceImpl(const ResourceNameRef& name, ResourceId resId,
+ const ConfigDescription& config, std::unique_ptr<Value> value,
+ const char16_t* validChars, IDiagnostics* diag);
+ bool setSymbolStateImpl(const ResourceNameRef& name, ResourceId resId,
+ const Symbol& symbol, const char16_t* validChars, IDiagnostics* diag);
};
} // namespace aapt
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 2055a80..42508fe 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -19,7 +19,7 @@
#include "ResourceValues.h"
#include "util/Util.h"
-#include "test/Common.h"
+#include "test/Builders.h"
#include <algorithm>
#include <gtest/gtest.h>
@@ -42,22 +42,26 @@
ResourceTable table;
EXPECT_FALSE(table.addResource(
- ResourceNameRef{ u"android", ResourceType::kId, u"hey,there" },
- {}, Source{ "test.xml", 21 },
- util::make_unique<Id>(), &mDiagnostics));
+ ResourceNameRef(u"android", ResourceType::kId, u"hey,there"),
+ ConfigDescription{},
+ test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
+ &mDiagnostics));
EXPECT_FALSE(table.addResource(
- ResourceNameRef{ u"android", ResourceType::kId, u"hey:there" },
- {}, Source{ "test.xml", 21 },
- util::make_unique<Id>(), &mDiagnostics));
+ ResourceNameRef(u"android", ResourceType::kId, u"hey:there"),
+ ConfigDescription{},
+ test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
+ &mDiagnostics));
}
TEST_F(ResourceTableTest, AddOneResource) {
ResourceTable table;
- EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"), {},
- Source{ "test/path/file.xml", 23 },
- util::make_unique<Id>(), &mDiagnostics));
+ EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"),
+ ConfigDescription{},
+ test::ValueBuilder<Id>()
+ .setSource("test/path/file.xml", 23u).build(),
+ &mDiagnostics));
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
}
@@ -71,23 +75,29 @@
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:attr/layout_width"),
- config, Source{ "test/path/file.xml", 10 },
- util::make_unique<Id>(), &mDiagnostics));
+ config,
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
+ &mDiagnostics));
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:attr/id"),
- config, Source{ "test/path/file.xml", 12 },
- util::make_unique<Id>(), &mDiagnostics));
+ config,
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
+ &mDiagnostics));
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:string/ok"),
- config, Source{ "test/path/file.xml", 14 },
- util::make_unique<Id>(), &mDiagnostics));
+ config,
+ test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
+ &mDiagnostics));
EXPECT_TRUE(table.addResource(
test::parseNameOrDie(u"@android:string/ok"),
- languageConfig, Source{ "test/path/file.xml", 20 },
- util::make_unique<BinaryPrimitive>(android::Res_value{}), &mDiagnostics));
+ languageConfig,
+ test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
+ .setSource("test/path/file.xml", 20u)
+ .build(),
+ &mDiagnostics));
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/layout_width"));
ASSERT_NE(nullptr, test::getValue<Id>(&table, u"@android:attr/id"));
@@ -99,14 +109,14 @@
TEST_F(ResourceTableTest, OverrideWeakResourceValue) {
ResourceTable table;
- ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {},
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
util::make_unique<Attribute>(true), &mDiagnostics));
Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
ASSERT_NE(nullptr, attr);
EXPECT_TRUE(attr->isWeak());
- ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), {}, {},
+ ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
util::make_unique<Attribute>(false), &mDiagnostics));
attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index ecc5cd2..f312d75 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -15,11 +15,12 @@
*/
#include "Resource.h"
-#include "flatten/ResourceTypeExtensions.h"
#include "ResourceValues.h"
-#include "util/Util.h"
#include "ValueVisitor.h"
+#include "util/Util.h"
+#include "flatten/ResourceTypeExtensions.h"
+
#include <androidfw/ResourceTypes.h>
#include <limits>
@@ -35,18 +36,10 @@
visitor->visit(static_cast<Derived*>(this));
}
-bool Value::isItem() const {
- return false;
-}
-
bool Value::isWeak() const {
return false;
}
-bool Item::isItem() const {
- return true;
-}
-
RawString::RawString(const StringPool::Ref& ref) : value(ref) {
}
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 0dae091..2629153 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -41,17 +41,42 @@
virtual ~Value() = default;
/**
- * Whether or not this is an Item.
- */
- virtual bool isItem() const;
-
- /**
* Whether this value is weak and can be overridden without
* warning or error. Default for base class is false.
*/
virtual bool isWeak() const;
/**
+ * Returns the source where this value was defined.
+ */
+ const Source& getSource() const {
+ return mSource;
+ }
+
+ void setSource(const Source& source) {
+ mSource = source;
+ }
+
+ void setSource(Source&& source) {
+ mSource = std::move(source);
+ }
+
+ /**
+ * Returns the comment that was associated with this resource.
+ */
+ StringPiece16 getComment() const {
+ return mComment;
+ }
+
+ void setComment(const StringPiece16& str) {
+ mComment = str.toString();
+ }
+
+ void setComment(std::u16string&& str) {
+ mComment = std::move(str);
+ }
+
+ /**
* Calls the appropriate overload of ValueVisitor.
*/
virtual void accept(RawValueVisitor* visitor) = 0;
@@ -65,6 +90,10 @@
* Human readable printout of this value.
*/
virtual void print(std::ostream* out) const = 0;
+
+private:
+ Source mSource;
+ std::u16string mComment;
};
/**
@@ -80,11 +109,6 @@
*/
struct Item : public Value {
/**
- * An Item is, of course, an Item.
- */
- virtual bool isItem() const override;
-
- /**
* Clone the Item.
*/
virtual Item* clone(StringPool* newPool) const override = 0;
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index ee058aa..94042e3 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -115,6 +115,18 @@
};
/**
+ * Specialization that checks if the value is an Item.
+ */
+template <>
+struct DynCastVisitor<Item> : public RawValueVisitor {
+ Item* value = nullptr;
+
+ void visitItem(Item* item) override {
+ value = item;
+ }
+};
+
+/**
* Returns a valid pointer to T if the Value is of subtype T.
* Otherwise, returns nullptr.
*/
diff --git a/tools/aapt2/XmlPullParser.cpp b/tools/aapt2/XmlPullParser.cpp
index 1b9499d..cff935c 100644
--- a/tools/aapt2/XmlPullParser.cpp
+++ b/tools/aapt2/XmlPullParser.cpp
@@ -97,7 +97,7 @@
}
const std::u16string& XmlPullParser::getComment() const {
- return mEventQueue.front().comment;
+ return mEventQueue.front().data1;
}
size_t XmlPullParser::getLineNumber() const {
diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/XmlPullParser.h
index f7d7a03..a0ce21d 100644
--- a/tools/aapt2/XmlPullParser.h
+++ b/tools/aapt2/XmlPullParser.h
@@ -158,7 +158,6 @@
size_t depth;
std::u16string data1;
std::u16string data2;
- std::u16string comment;
std::vector<Attribute> attributes;
};
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 095552a..47fa2a6 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -292,7 +292,7 @@
SymbolWriter* mSymbols;
StringPool* mSourcePool;
- template <typename T>
+ template <typename T, bool IsItem>
T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
static_assert(std::is_same<ResTable_entry, T>::value ||
std::is_same<ResTable_entry_ext, T>::value,
@@ -308,7 +308,7 @@
outEntry->flags |= ResTable_entry::FLAG_WEAK;
}
- if (!entry->value->isItem()) {
+ if (!IsItem) {
outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
}
@@ -329,8 +329,8 @@
}
bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
- if (entry->value->isItem()) {
- writeEntry<ResTable_entry>(entry, buffer);
+ if (Item* item = valueCast<Item>(entry->value)) {
+ writeEntry<ResTable_entry, true>(entry, buffer);
if (Reference* ref = valueCast<Reference>(entry->value)) {
if (!ref->id) {
assert(ref->name && "reference must have at least a name");
@@ -339,12 +339,12 @@
}
}
Res_value* outValue = buffer->nextBlock<Res_value>();
- bool result = static_cast<Item*>(entry->value)->flatten(outValue);
+ bool result = item->flatten(outValue);
assert(result && "flatten failed");
outValue->size = util::hostToDevice16(sizeof(*outValue));
} else {
const size_t beforeEntry = buffer->size();
- ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext>(entry, buffer);
+ ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
MapFlattenVisitor visitor(mSymbols, entry, buffer);
entry->value->accept(&visitor);
outEntry->count = util::hostToDevice32(visitor.mEntryCount);
@@ -551,17 +551,27 @@
// configuration available. Here we reverse this to match the binary table.
std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
for (ResourceEntry* entry : sortedEntries) {
- const size_t keyIndex = mKeyPool.makeRef(entry->name).getIndex();
+ const uint32_t keyIndex = (uint32_t) mKeyPool.makeRef(entry->name).getIndex();
// Group values by configuration.
for (auto& configValue : entry->values) {
- configToEntryListMap[configValue.config].push_back(FlatEntry{
- entry, configValue.value.get(), (uint32_t) keyIndex,
- (uint32_t)(mSourcePool->makeRef(util::utf8ToUtf16(
- configValue.source.path)).getIndex()),
- (uint32_t)(configValue.source.line
- ? configValue.source.line.value() : 0)
- });
+ Value* value = configValue.value.get();
+
+ const StringPool::Ref sourceRef = mSourcePool->makeRef(
+ util::utf8ToUtf16(value->getSource().path));
+
+ uint32_t lineNumber = 0;
+ if (value->getSource().line) {
+ lineNumber = value->getSource().line.value();
+ }
+
+ configToEntryListMap[configValue.config]
+ .push_back(FlatEntry{
+ entry,
+ value,
+ keyIndex,
+ (uint32_t) sourceRef.getIndex(),
+ lineNumber });
}
}
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
index 0ccafc2..11fcc5d 100644
--- a/tools/aapt2/link/AutoVersioner.cpp
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -20,21 +20,18 @@
#include "ValueVisitor.h"
#include "link/Linkers.h"
+#include "util/Comparators.h"
#include <algorithm>
#include <cassert>
namespace aapt {
-static bool cmpConfigValue(const ResourceConfigValue& lhs, const ConfigDescription& config) {
- return lhs.config < config;
-}
-
bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config,
const int sdkVersionToGenerate) {
assert(sdkVersionToGenerate > config.sdkVersion);
const auto endIter = entry->values.end();
- auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmpConfigValue);
+ auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThan);
// The source config came from this list, so it should be here.
assert(iter != entry->values.end());
@@ -107,21 +104,16 @@
// We found attributes from a higher SDK level. Check that
// there is no other defined resource for the version we want to
// generate.
- if (shouldGenerateVersionedResource(entry.get(), configValue.config,
+ if (shouldGenerateVersionedResource(entry.get(),
+ configValue.config,
minSdkStripped.value())) {
// Let's create a new Style for this versioned resource.
ConfigDescription newConfig(configValue.config);
newConfig.sdkVersion = minSdkStripped.value();
- ResourceConfigValue newValue = {
- newConfig,
- configValue.source,
- configValue.comment,
- std::unique_ptr<Value>(configValue.value->clone(
- &table->stringPool))
- };
-
- Style* newStyle = static_cast<Style*>(newValue.value.get());
+ std::unique_ptr<Style> newStyle(style->clone(&table->stringPool));
+ newStyle->setComment(style->getComment());
+ newStyle->setSource(style->getSource());
// Move the previously stripped attributes into this style.
newStyle->entries.insert(newStyle->entries.end(),
@@ -130,9 +122,13 @@
// Insert the new Resource into the correct place.
auto iter = std::lower_bound(entry->values.begin(),
- entry->values.end(), newConfig,
- cmpConfigValue);
- entry->values.insert(iter, std::move(newValue));
+ entry->values.end(),
+ newConfig,
+ cmp::lessThan);
+
+ entry->values.insert(
+ iter,
+ ResourceConfigValue{ newConfig, std::move(newStyle) });
}
}
}
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index b84f2e0..ad701de 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -266,13 +266,14 @@
for (const auto& type : package->types) {
for (const auto& entry : type->entries) {
for (const auto& configValue : entry->values) {
- mContext.getDiagnostics()->error(DiagMessage(configValue.source)
- << "defined resource '"
- << ResourceNameRef(package->name,
- type->type,
- entry->name)
- << "' for external package '"
- << package->name << "'");
+ mContext.getDiagnostics()->error(
+ DiagMessage(configValue.value->getSource())
+ << "defined resource '"
+ << ResourceNameRef(package->name,
+ type->type,
+ entry->name)
+ << "' for external package '"
+ << package->name << "'");
error = true;
}
}
@@ -472,10 +473,11 @@
Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
exportedSymbol.name);
- if (!mergedTable.addResource(
+ std::unique_ptr<Id> id = util::make_unique<Id>();
+ id->setSource(f.source.withLine(exportedSymbol.line));
+ if (!mergedTable.addResourceAllowMangled(
mangledName ? mangledName.value() : exportedSymbol.name,
- {}, {}, f.source.withLine(exportedSymbol.line),
- util::make_unique<Id>(), mContext.getDiagnostics())) {
+ {}, std::move(id), mContext.getDiagnostics())) {
error = true;
}
}
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index a2f8d19..dbe0c92 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -30,13 +30,9 @@
.addSimple(u"@android:attr/privateA")
.addSimple(u"@android:attr/publicB")
.addSimple(u"@android:attr/privateB")
+ .setSymbolState(u"@android:attr/publicA", ResourceId(0x01010000), SymbolState::kPublic)
+ .setSymbolState(u"@android:attr/publicB", ResourceId(0x01010000), SymbolState::kPublic)
.build();
- ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicA"),
- ResourceId(0x01010000), {}, SymbolState::kPublic,
- context->getDiagnostics()));
- ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@android:attr/publicB"),
- ResourceId(0x01010002), {}, SymbolState::kPublic,
- context->getDiagnostics()));
PrivateAttributeMover mover;
ASSERT_TRUE(mover.consume(context.get(), table.get()));
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index b4fb996..8c924b5 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -206,13 +206,13 @@
if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
// The actual type of this item is incompatible with the attribute.
- DiagMessage msg;
+ DiagMessage msg(style->getSource());
buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get());
mContext->getDiagnostics()->error(msg);
mError = true;
}
} else {
- DiagMessage msg;
+ DiagMessage msg(style->getSource());
msg << "style attribute '";
if (entry.key.name) {
msg << entry.key.name.value().package << ":" << entry.key.name.value().entry;
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index db52546..636c2ba 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -19,6 +19,7 @@
#include "ValueVisitor.h"
#include "link/TableMerger.h"
+#include "util/Comparators.h"
#include "util/Util.h"
#include <cassert>
@@ -120,27 +121,24 @@
}
for (ResourceConfigValue& srcValue : srcEntry->values) {
- auto cmp = [](const ResourceConfigValue& a,
- const ConfigDescription& b) -> bool {
- return a.config < b;
- };
-
auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(),
- srcValue.config, cmp);
+ srcValue.config, cmp::lessThan);
if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
const int collisionResult = ResourceTable::resolveValueCollision(
iter->value.get(), srcValue.value.get());
if (collisionResult == 0) {
// Error!
- ResourceNameRef resourceName =
- { srcPackage->name, srcType->type, srcEntry->name };
- mContext->getDiagnostics()->error(DiagMessage(srcValue.source)
+ ResourceNameRef resourceName(srcPackage->name,
+ srcType->type,
+ srcEntry->name);
+
+ mContext->getDiagnostics()->error(DiagMessage(srcValue.value->getSource())
<< "resource '" << resourceName
<< "' has a conflicting value for "
<< "configuration ("
<< srcValue.config << ")");
- mContext->getDiagnostics()->note(DiagMessage(iter->source)
+ mContext->getDiagnostics()->note(DiagMessage(iter->value->getSource())
<< "originally defined here");
error = true;
continue;
@@ -150,16 +148,12 @@
}
} else {
- // Insert a new value.
- iter = dstEntry->values.insert(iter,
- ResourceConfigValue{ srcValue.config });
+ // Insert a place holder value. We will fill it in below.
+ iter = dstEntry->values.insert(iter, ResourceConfigValue{ srcValue.config });
}
- iter->source = std::move(srcValue.source);
- iter->comment = std::move(srcValue.comment);
if (manglePackage) {
- iter->value = cloneAndMangle(srcTable, srcPackage->name,
- srcValue.value.get());
+ iter->value = cloneAndMangle(srcTable, srcPackage->name, srcValue.value.get());
} else {
iter->value = clone(srcValue.value.get());
}
@@ -179,7 +173,11 @@
std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString());
std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString();
mFilesToMerge.push(FileToMerge{ table, *f->path, newPath });
- return util::make_unique<FileReference>(mMasterTable->stringPool.makeRef(newPath));
+ std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
+ mMasterTable->stringPool.makeRef(newPath));
+ fileRef->setComment(f->getComment());
+ fileRef->setSource(f->getSource());
+ return std::move(fileRef);
}
}
return clone(value);
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index c96b080..7309396 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -16,9 +16,11 @@
#include "ConfigDescription.h"
#include "Resource.h"
-#include "util/Util.h"
+#include "ValueVisitor.h"
#include "process/SymbolTable.h"
+#include "util/Comparators.h"
+#include "util/Util.h"
#include <androidfw/AssetManager.h>
#include <androidfw/ResourceTypes.h>
@@ -34,7 +36,7 @@
if (!result) {
if (name.type == ResourceType::kAttr) {
// Recurse and try looking up a private attribute.
- return findByName(ResourceName{ name.package, ResourceType::kAttrPrivate, name.entry });
+ return findByName(ResourceName(name.package, ResourceType::kAttrPrivate, name.entry));
}
return {};
}
@@ -48,28 +50,26 @@
}
std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>();
- symbol->id = ResourceId{
- sr.package->id.value(), sr.type->id.value(), sr.entry->id.value() };
+ symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
- auto lt = [](ResourceConfigValue& lhs, const ConfigDescription& rhs) -> bool {
- return lhs.config < rhs;
- };
-
const ConfigDescription kDefaultConfig;
auto iter = std::lower_bound(sr.entry->values.begin(), sr.entry->values.end(),
- kDefaultConfig, lt);
+ kDefaultConfig, cmp::lessThan);
if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
// This resource has an Attribute.
- symbol->attribute = util::make_unique<Attribute>(
- *static_cast<Attribute*>(iter->value.get()));
+ if (Attribute* attr = valueCast<Attribute>(iter->value.get())) {
+ symbol->attribute = std::unique_ptr<Attribute>(attr->clone(nullptr));
+ } else {
+ return {};
+ }
}
}
if (name.type == ResourceType::kAttrPrivate) {
// Masquerade this entry as kAttr.
- mCache.put(ResourceName{ name.package, ResourceType::kAttr, name.entry }, symbol);
+ mCache.put(ResourceName(name.package, ResourceType::kAttr, name.entry), symbol);
} else {
mCache.put(name, symbol);
}
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 0383c44..1b510e7 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -43,7 +43,7 @@
return *this;
}
- ResourceTableBuilder& addSimple(const StringPiece16& name, ResourceId id = {}) {
+ ResourceTableBuilder& addSimple(const StringPiece16& name, const ResourceId id = {}) {
return addValue(name, id, util::make_unique<Id>());
}
@@ -51,7 +51,7 @@
return addReference(name, {}, ref);
}
- ResourceTableBuilder& addReference(const StringPiece16& name, ResourceId id,
+ ResourceTableBuilder& addReference(const StringPiece16& name, const ResourceId id,
const StringPiece16& ref) {
return addValue(name, id, util::make_unique<Reference>(parseNameOrDie(ref)));
}
@@ -60,7 +60,7 @@
return addString(name, {}, str);
}
- ResourceTableBuilder& addString(const StringPiece16& name, ResourceId id,
+ ResourceTableBuilder& addString(const StringPiece16& name, const ResourceId id,
const StringPiece16& str) {
return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str)));
}
@@ -69,31 +69,43 @@
return addFileReference(name, {}, path);
}
- ResourceTableBuilder& addFileReference(const StringPiece16& name, ResourceId id,
+ ResourceTableBuilder& addFileReference(const StringPiece16& name, const ResourceId id,
const StringPiece16& path) {
return addValue(name, id,
util::make_unique<FileReference>(mTable->stringPool.makeRef(path)));
}
- ResourceTableBuilder& addValue(const StringPiece16& name, std::unique_ptr<Value> value) {
+ ResourceTableBuilder& addValue(const StringPiece16& name,
+ std::unique_ptr<Value> value) {
return addValue(name, {}, std::move(value));
}
- ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
+ ResourceTableBuilder& addValue(const StringPiece16& name, const ResourceId id,
std::unique_ptr<Value> value) {
return addValue(name, id, {}, std::move(value));
}
- ResourceTableBuilder& addValue(const StringPiece16& name, ResourceId id,
- const ConfigDescription& config, std::unique_ptr<Value> value) {
+ ResourceTableBuilder& addValue(const StringPiece16& name, const ResourceId id,
+ const ConfigDescription& config,
+ std::unique_ptr<Value> value) {
ResourceName resName = parseNameOrDie(name);
- bool result = mTable->addResourceAllowMangled(resName, id, config, {}, std::move(value),
+ bool result = mTable->addResourceAllowMangled(resName, id, config, std::move(value),
&mDiagnostics);
assert(result);
return *this;
}
+ ResourceTableBuilder& setSymbolState(const StringPiece16& name, ResourceId id,
+ SymbolState state) {
+ ResourceName resName = parseNameOrDie(name);
+ Symbol symbol;
+ symbol.state = state;
+ bool result = mTable->setSymbolStateAllowMangled(resName, id, symbol, &mDiagnostics);
+ assert(result);
+ return *this;
+ }
+
std::unique_ptr<ResourceTable> build() {
return std::move(mTable);
}
@@ -106,6 +118,32 @@
return reference;
}
+template <typename T>
+class ValueBuilder {
+private:
+ std::unique_ptr<Value> mValue;
+
+public:
+ template <typename... Args>
+ ValueBuilder(Args&&... args) : mValue(new T{ std::forward<Args>(args)... }) {
+ }
+
+ template <typename... Args>
+ ValueBuilder& setSource(Args&&... args) {
+ mValue->setSource(Source{ std::forward<Args>(args)... });
+ return *this;
+ }
+
+ ValueBuilder& setComment(const StringPiece16& str) {
+ mValue->setComment(str);
+ return *this;
+ }
+
+ std::unique_ptr<Value> build() {
+ return std::move(mValue);
+ }
+};
+
class AttributeBuilder {
private:
std::unique_ptr<Attribute> mAttr;
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index c7a715e..314c1e8 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -422,26 +422,24 @@
const ResourceName name(package->name, *parsedType,
util::getString(mKeyPool, entry->key.index).toString());
- Source source;
+ Symbol symbol;
if (mSourcePool.getError() == NO_ERROR) {
- source.path = util::utf16ToUtf8(util::getString(
+ symbol.source.path = util::utf16ToUtf8(util::getString(
mSourcePool, util::deviceToHost32(entry->source.index)));
- source.line = util::deviceToHost32(entry->sourceLine);
+ symbol.source.line = util::deviceToHost32(entry->sourceLine);
}
- SymbolState state = SymbolState::kUndefined;
switch (util::deviceToHost16(entry->state)) {
case Public_entry::kPrivate:
- state = SymbolState::kPrivate;
+ symbol.state = SymbolState::kPrivate;
break;
case Public_entry::kPublic:
- state = SymbolState::kPublic;
+ symbol.state = SymbolState::kPublic;
break;
}
- if (!mTable->setSymbolStateAllowMangled(name, resId, source, state,
- mContext->getDiagnostics())) {
+ if (!mTable->setSymbolStateAllowMangled(name, resId, symbol, mContext->getDiagnostics())) {
return false;
}
@@ -570,14 +568,17 @@
source.line = util::deviceToHost32(sourceBlock->line);
}
- if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue),
+ resourceValue->setSource(source);
+ if (!mTable->addResourceAllowMangled(name, config, std::move(resourceValue),
mContext->getDiagnostics())) {
return false;
}
if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
- if (!mTable->setSymbolStateAllowMangled(name, resId, mSource.withLine(0),
- SymbolState::kPublic,
+ Symbol symbol;
+ symbol.state = SymbolState::kPublic;
+ symbol.source = mSource.withLine(0);
+ if (!mTable->setSymbolStateAllowMangled(name, resId, symbol,
mContext->getDiagnostics())) {
return false;
}
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 4dbef5d..02c4081 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -66,26 +66,30 @@
bool parseTypeSpec(const android::ResChunk_header* chunk);
bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
- std::unique_ptr<Item> parseValue(const ResourceNameRef& name,
- const ConfigDescription& config, const android::Res_value* value, uint16_t flags);
+ std::unique_ptr<Item> parseValue(const ResourceNameRef& name, const ConfigDescription& config,
+ const android::Res_value* value, uint16_t flags);
std::unique_ptr<Value> parseMapEntry(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
- std::unique_ptr<Style> parseStyle(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ std::unique_ptr<Style> parseStyle(const ResourceNameRef& name, const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
std::unique_ptr<Attribute> parseAttr(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
- std::unique_ptr<Array> parseArray(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ std::unique_ptr<Array> parseArray(const ResourceNameRef& name, const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
std::unique_ptr<Styleable> parseStyleable(const ResourceNameRef& name,
- const ConfigDescription& config, const android::ResTable_map_entry* map);
+ const ConfigDescription& config,
+ const android::ResTable_map_entry* map);
IAaptContext* mContext;
ResourceTable* mTable;
diff --git a/tools/aapt2/util/Comparators.h b/tools/aapt2/util/Comparators.h
new file mode 100644
index 0000000..652018e
--- /dev/null
+++ b/tools/aapt2/util/Comparators.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_UTIL_COMPARATORS_H
+#define AAPT_UTIL_COMPARATORS_H
+
+namespace aapt {
+namespace cmp {
+
+inline bool lessThan(const ResourceConfigValue& a, const ConfigDescription& b) {
+ return a.config < b;
+}
+
+} // namespace cmp
+} // namespace aapt
+
+#endif /* AAPT_UTIL_COMPARATORS_H */