Merge "Adds sourcePackageName field to JobStatus"
diff --git a/Android.mk b/Android.mk
index 960fb3c..282b2af 100644
--- a/Android.mk
+++ b/Android.mk
@@ -209,6 +209,7 @@
core/java/android/os/IBatteryPropertiesRegistrar.aidl \
core/java/android/os/ICancellationSignal.aidl \
core/java/android/os/IDeviceIdleController.aidl \
+ core/java/android/os/IMaintenanceActivityListener.aidl \
core/java/android/os/IMessenger.aidl \
core/java/android/os/INetworkActivityListener.aidl \
core/java/android/os/INetworkManagementService.aidl \
diff --git a/api/current.txt b/api/current.txt
index 6d67fb9..7ff1e6e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -19402,9 +19402,10 @@
method public abstract void onAudioFocusChange(int);
}
- public class AudioRecord {
+ public class AudioRecord implements android.media.AudioRouting {
ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
- method public void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
+ method public deprecated void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
+ method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public int getAudioFormat();
method public int getAudioSessionId();
method public int getAudioSource();
@@ -19428,7 +19429,8 @@
method public int read(java.nio.ByteBuffer, int);
method public int read(java.nio.ByteBuffer, int, int);
method public void release();
- method public void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+ method public deprecated void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+ method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
method public int setNotificationMarkerPosition(int);
method public int setPositionNotificationPeriod(int);
method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
@@ -19466,17 +19468,29 @@
method public abstract void onRoutingChanged(android.media.AudioRecord);
}
+ public abstract interface AudioRouting {
+ method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
+ method public abstract android.media.AudioDeviceInfo getPreferredDevice();
+ method public abstract void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
+ method public abstract boolean setPreferredDevice(android.media.AudioDeviceInfo);
+ }
+
+ public static abstract interface AudioRouting.OnRoutingChangedListener {
+ method public abstract void onRoutingChanged(android.media.AudioRouting);
+ }
+
public final class AudioTimestamp {
ctor public AudioTimestamp();
field public long framePosition;
field public long nanoTime;
}
- public class AudioTrack {
+ public class AudioTrack implements android.media.AudioRouting {
ctor public AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException;
- method public void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
+ method public deprecated void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
+ method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public int attachAuxEffect(int);
method public void flush();
method public int getAudioFormat();
@@ -19506,7 +19520,8 @@
method public void play() throws java.lang.IllegalStateException;
method public void release();
method public int reloadStaticData();
- method public void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+ method public deprecated void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+ method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
method public int setAuxEffectSendLevel(float);
method public int setLoopPoints(int, int, int);
method public int setNotificationMarkerPosition(int);
@@ -19559,8 +19574,8 @@
method public abstract void onPeriodicNotification(android.media.AudioTrack);
}
- public static abstract interface AudioTrack.OnRoutingChangedListener {
- method public abstract void onRoutingChanged(android.media.AudioTrack);
+ public static abstract deprecated interface AudioTrack.OnRoutingChangedListener {
+ method public abstract deprecated void onRoutingChanged(android.media.AudioTrack);
}
public class CamcorderProfile {
@@ -33620,6 +33635,7 @@
method public abstract void onUnsubscribe(android.net.Uri);
field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
+ field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
}
@@ -38131,9 +38147,11 @@
public class LocaleSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
ctor public LocaleSpan(java.util.Locale);
+ ctor public LocaleSpan(android.util.LocaleList);
ctor public LocaleSpan(android.os.Parcel);
method public int describeContents();
method public java.util.Locale getLocale();
+ method public android.util.LocaleList getLocales();
method public int getSpanTypeId();
method public void updateDrawState(android.text.TextPaint);
method public void updateMeasureState(android.text.TextPaint);
diff --git a/api/system-current.txt b/api/system-current.txt
index d4efb8c..1379c8e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -20706,10 +20706,11 @@
method public abstract void onAudioFocusChange(int);
}
- public class AudioRecord {
+ public class AudioRecord implements android.media.AudioRouting {
ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException;
- method public void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
+ method public deprecated void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
+ method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public int getAudioFormat();
method public int getAudioSessionId();
method public int getAudioSource();
@@ -20733,7 +20734,8 @@
method public int read(java.nio.ByteBuffer, int);
method public int read(java.nio.ByteBuffer, int, int);
method public void release();
- method public void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+ method public deprecated void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+ method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
method public int setNotificationMarkerPosition(int);
method public int setPositionNotificationPeriod(int);
method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
@@ -20773,17 +20775,29 @@
method public abstract void onRoutingChanged(android.media.AudioRecord);
}
+ public abstract interface AudioRouting {
+ method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
+ method public abstract android.media.AudioDeviceInfo getPreferredDevice();
+ method public abstract void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
+ method public abstract boolean setPreferredDevice(android.media.AudioDeviceInfo);
+ }
+
+ public static abstract interface AudioRouting.OnRoutingChangedListener {
+ method public abstract void onRoutingChanged(android.media.AudioRouting);
+ }
+
public final class AudioTimestamp {
ctor public AudioTimestamp();
field public long framePosition;
field public long nanoTime;
}
- public class AudioTrack {
+ public class AudioTrack implements android.media.AudioRouting {
ctor public AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException;
- method public void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
+ method public deprecated void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
+ method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public int attachAuxEffect(int);
method public void flush();
method public int getAudioFormat();
@@ -20813,7 +20827,8 @@
method public void play() throws java.lang.IllegalStateException;
method public void release();
method public int reloadStaticData();
- method public void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+ method public deprecated void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+ method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
method public int setAuxEffectSendLevel(float);
method public int setLoopPoints(int, int, int);
method public int setNotificationMarkerPosition(int);
@@ -20866,8 +20881,8 @@
method public abstract void onPeriodicNotification(android.media.AudioTrack);
}
- public static abstract interface AudioTrack.OnRoutingChangedListener {
- method public abstract void onRoutingChanged(android.media.AudioTrack);
+ public static abstract deprecated interface AudioTrack.OnRoutingChangedListener {
+ method public abstract deprecated void onRoutingChanged(android.media.AudioTrack);
}
public class CamcorderProfile {
@@ -35766,6 +35781,7 @@
method public abstract void onUnsubscribe(android.net.Uri);
field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
+ field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
}
@@ -37564,6 +37580,35 @@
method public abstract void onVideoQualityChanged(int);
}
+ public class ParcelableCallAnalytics implements android.os.Parcelable {
+ ctor public ParcelableCallAnalytics(long, long, int, boolean, boolean, int, int, boolean, java.lang.String, boolean);
+ ctor public ParcelableCallAnalytics(android.os.Parcel);
+ method public int describeContents();
+ method public long getCallDurationMillis();
+ method public int getCallTechnologies();
+ method public int getCallTerminationCode();
+ method public int getCallType();
+ method public java.lang.String getConnectionService();
+ method public long getStartTimeMillis();
+ method public boolean isAdditionalCall();
+ method public boolean isCreatedFromExistingConnection();
+ method public boolean isEmergencyCall();
+ method public boolean isInterrupted();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CALLTYPE_INCOMING = 1; // 0x1
+ field public static final int CALLTYPE_OUTGOING = 2; // 0x2
+ field public static final int CALLTYPE_UNKNOWN = 0; // 0x0
+ field public static final int CDMA_PHONE = 1; // 0x1
+ field public static final android.os.Parcelable.Creator<android.telecom.ParcelableCallAnalytics> CREATOR;
+ field public static final int GSM_PHONE = 2; // 0x2
+ field public static final int IMS_PHONE = 4; // 0x4
+ field public static final long MILLIS_IN_1_SECOND = 1000L; // 0x3e8L
+ field public static final long MILLIS_IN_5_MINUTES = 300000L; // 0x493e0L
+ field public static final int SIP_PHONE = 8; // 0x8
+ field public static final int STILL_CONNECTED = -1; // 0xffffffff
+ field public static final int THIRD_PARTY_PHONE = 16; // 0x10
+ }
+
public final deprecated class Phone {
method public final void addListener(android.telecom.Phone.Listener);
method public final boolean canAddCall();
@@ -37781,6 +37826,7 @@
method public void cancelMissedCallsNotification();
method public deprecated void clearAccounts();
method public void clearPhoneAccounts();
+ method public java.util.List<android.telecom.ParcelableCallAnalytics> dumpAnalytics();
method public void enablePhoneAccount(android.telecom.PhoneAccountHandle, boolean);
method public boolean endCall();
method public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle);
@@ -40490,9 +40536,11 @@
public class LocaleSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
ctor public LocaleSpan(java.util.Locale);
+ ctor public LocaleSpan(android.util.LocaleList);
ctor public LocaleSpan(android.os.Parcel);
method public int describeContents();
method public java.util.Locale getLocale();
+ method public android.util.LocaleList getLocales();
method public int getSpanTypeId();
method public void updateDrawState(android.text.TextPaint);
method public void updateMeasureState(android.text.TextPaint);
diff --git a/api/test-current.txt b/api/test-current.txt
index 57c735b..0603478 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -19410,9 +19410,10 @@
method public abstract void onAudioFocusChange(int);
}
- public class AudioRecord {
+ public class AudioRecord implements android.media.AudioRouting {
ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException;
- method public void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
+ method public deprecated void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler);
+ method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public int getAudioFormat();
method public int getAudioSessionId();
method public int getAudioSource();
@@ -19436,7 +19437,8 @@
method public int read(java.nio.ByteBuffer, int);
method public int read(java.nio.ByteBuffer, int, int);
method public void release();
- method public void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+ method public deprecated void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+ method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
method public int setNotificationMarkerPosition(int);
method public int setPositionNotificationPeriod(int);
method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
@@ -19474,17 +19476,29 @@
method public abstract void onRoutingChanged(android.media.AudioRecord);
}
+ public abstract interface AudioRouting {
+ method public abstract void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
+ method public abstract android.media.AudioDeviceInfo getPreferredDevice();
+ method public abstract void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
+ method public abstract boolean setPreferredDevice(android.media.AudioDeviceInfo);
+ }
+
+ public static abstract interface AudioRouting.OnRoutingChangedListener {
+ method public abstract void onRoutingChanged(android.media.AudioRouting);
+ }
+
public final class AudioTimestamp {
ctor public AudioTimestamp();
field public long framePosition;
field public long nanoTime;
}
- public class AudioTrack {
+ public class AudioTrack implements android.media.AudioRouting {
ctor public AudioTrack(int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioTrack(int, int, int, int, int, int, int) throws java.lang.IllegalArgumentException;
ctor public AudioTrack(android.media.AudioAttributes, android.media.AudioFormat, int, int, int) throws java.lang.IllegalArgumentException;
- method public void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
+ method public deprecated void addOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener, android.os.Handler);
+ method public void addOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method public int attachAuxEffect(int);
method public void flush();
method public int getAudioFormat();
@@ -19514,7 +19528,8 @@
method public void play() throws java.lang.IllegalStateException;
method public void release();
method public int reloadStaticData();
- method public void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+ method public deprecated void removeOnRoutingChangedListener(android.media.AudioTrack.OnRoutingChangedListener);
+ method public void removeOnRoutingListener(android.media.AudioRouting.OnRoutingChangedListener);
method public int setAuxEffectSendLevel(float);
method public int setLoopPoints(int, int, int);
method public int setNotificationMarkerPosition(int);
@@ -19567,8 +19582,8 @@
method public abstract void onPeriodicNotification(android.media.AudioTrack);
}
- public static abstract interface AudioTrack.OnRoutingChangedListener {
- method public abstract void onRoutingChanged(android.media.AudioTrack);
+ public static abstract deprecated interface AudioTrack.OnRoutingChangedListener {
+ method public abstract deprecated void onRoutingChanged(android.media.AudioTrack);
}
public class CamcorderProfile {
@@ -33634,6 +33649,7 @@
method public abstract void onUnsubscribe(android.net.Uri);
field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
field public static final java.lang.String META_DATA_CONFIGURATION_ACTIVITY = "android.service.zen.automatic.configurationActivity";
+ field public static final java.lang.String META_DATA_RULE_INSTANCE_LIMIT = "android.service.zen.automatic.ruleInstanceLimit";
field public static final java.lang.String META_DATA_RULE_TYPE = "android.service.zen.automatic.ruleType";
field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.ConditionProviderService";
}
@@ -38147,9 +38163,11 @@
public class LocaleSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
ctor public LocaleSpan(java.util.Locale);
+ ctor public LocaleSpan(android.util.LocaleList);
ctor public LocaleSpan(android.os.Parcel);
method public int describeContents();
method public java.util.Locale getLocale();
+ method public android.util.LocaleList getLocales();
method public int getSpanTypeId();
method public void updateDrawState(android.text.TextPaint);
method public void updateMeasureState(android.text.TextPaint);
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 94b4e7f..63b6825 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2798,7 +2798,8 @@
case MOVE_TASKS_TO_FULLSCREEN_STACK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
final int stackId = data.readInt();
- moveTasksToFullscreenStack(stackId);
+ final boolean onTop = data.readInt() == 1;
+ moveTasksToFullscreenStack(stackId, onTop);
reply.writeNoException();
return true;
}
@@ -6581,11 +6582,12 @@
}
@Override
- public void moveTasksToFullscreenStack(int fromStackId) throws RemoteException {
+ public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeInt(fromStackId);
+ data.writeInt(onTop ? 1 : 0);
mRemote.transact(MOVE_TASKS_TO_FULLSCREEN_STACK_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4e55c89..2f2fbbe 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4362,6 +4362,9 @@
if (!tmp.onlyLocalRequest) {
try {
ActivityManagerNative.getDefault().activityRelaunched(r.token);
+ if (r.window != null) {
+ r.window.reportActivityRelaunched();
+ }
} catch (RemoteException e) {
// If the system process has died, it's game over for everyone.
}
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index 8d7f347..bbf1607 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -23,7 +23,6 @@
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
-import android.text.format.DateUtils;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
@@ -49,19 +48,16 @@
private static final String DAY = "day";
private final DatePicker mDatePicker;
- private final Calendar mCalendar;
private OnDateSetListener mDateSetListener;
- private boolean mTitleNeedsUpdate = true;
-
/**
* Creates a new date picker dialog for the current date using the parent
* context's default date picker dialog theme.
*
* @param context the parent context
*/
- public DatePickerDialog(Context context) {
+ public DatePickerDialog(@NonNull Context context) {
this(context, 0);
}
@@ -73,7 +69,7 @@
* this dialog, or {@code 0} to use the parent
* {@code context}'s default alert dialog theme
*/
- public DatePickerDialog(Context context, @StyleRes int themeResId) {
+ public DatePickerDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, resolveDialogTheme(context, themeResId));
final Context themeContext = getContext();
@@ -85,11 +81,10 @@
setButton(BUTTON_NEGATIVE, themeContext.getString(R.string.cancel), this);
setButtonPanelLayoutHint(LAYOUT_HINT_SIDE);
- mCalendar = Calendar.getInstance();
-
- final int year = mCalendar.get(Calendar.YEAR);
- final int monthOfYear = mCalendar.get(Calendar.MONTH);
- final int dayOfMonth = mCalendar.get(Calendar.DAY_OF_MONTH);
+ final Calendar calendar = Calendar.getInstance();
+ final int year = calendar.get(Calendar.YEAR);
+ final int monthOfYear = calendar.get(Calendar.MONTH);
+ final int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
mDatePicker.init(year, monthOfYear, dayOfMonth, this);
mDatePicker.setValidationCallback(mValidationCallback);
@@ -107,7 +102,7 @@
* @param dayOfMonth the initially selected day of month (1-31, depending
* on month)
*/
- public DatePickerDialog(@Nullable Context context, @Nullable OnDateSetListener listener,
+ public DatePickerDialog(@NonNull Context context, @Nullable OnDateSetListener listener,
int year, int month, int dayOfMonth) {
this(context, 0, listener, year, month, dayOfMonth);
}
@@ -135,7 +130,7 @@
mDatePicker.updateDate(year, month, dayOfMonth);
}
- static int resolveDialogTheme(@NonNull Context context, @StyleRes int themeResId) {
+ static @StyleRes int resolveDialogTheme(@NonNull Context context, @StyleRes int themeResId) {
if (themeResId == 0) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.datePickerDialogTheme, outValue, true);
@@ -146,9 +141,8 @@
}
@Override
- public void onDateChanged(DatePicker view, int year, int month, int dayOfMonth) {
+ public void onDateChanged(@NonNull DatePicker view, int year, int month, int dayOfMonth) {
mDatePicker.init(year, month, dayOfMonth, this);
- updateTitle(year, month, dayOfMonth);
}
/**
@@ -161,7 +155,7 @@
}
@Override
- public void onClick(DialogInterface dialog, int which) {
+ public void onClick(@NonNull DialogInterface dialog, int which) {
switch (which) {
case BUTTON_POSITIVE:
if (mDateSetListener != null) {
@@ -200,29 +194,6 @@
mDatePicker.updateDate(year, month, dayOfMonth);
}
- private void updateTitle(int year, int month, int dayOfMonth) {
- if (!mDatePicker.getCalendarViewShown()) {
- mCalendar.set(Calendar.YEAR, year);
- mCalendar.set(Calendar.MONTH, month);
- mCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
-
- final String title = DateUtils.formatDateTime(mContext,
- mCalendar.getTimeInMillis(),
- DateUtils.FORMAT_SHOW_DATE
- | DateUtils.FORMAT_SHOW_WEEKDAY
- | DateUtils.FORMAT_SHOW_YEAR
- | DateUtils.FORMAT_ABBREV_MONTH
- | DateUtils.FORMAT_ABBREV_WEEKDAY);
- setTitle(title);
-
- mTitleNeedsUpdate = true;
- } else if (mTitleNeedsUpdate) {
- setTitle(R.string.date_picker_dialog_title);
-
- mTitleNeedsUpdate = false;
- }
- }
-
@Override
public Bundle onSaveInstanceState() {
final Bundle state = super.onSaveInstanceState();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 10885f2..5bb2cf5 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -577,7 +577,7 @@
public void suppressResizeConfigChanges(boolean suppress) throws RemoteException;
- public void moveTasksToFullscreenStack(int fromStackId) throws RemoteException;
+ public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) throws RemoteException;
public int getAppStartMode(int uid, String packageName) throws RemoteException;
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 633f699..368b8ef 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -103,6 +103,7 @@
boolean updateAutomaticZenRule(in AutomaticZenRule automaticZenRule);
boolean removeAutomaticZenRule(String id);
boolean removeAutomaticZenRules(String packageName);
+ int getRuleInstanceCount(in ComponentName owner);
byte[] getBackupPayload(int user);
void applyRestore(in byte[] payload, int user);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 9a3c820..faf5b11 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -380,6 +380,18 @@
}
/**
+ * @hide
+ */
+ public int getRuleInstanceCount(ComponentName owner) {
+ INotificationManager service = getService();
+ try {
+ return service.getRuleInstanceCount(owner);
+ } catch (RemoteException e) {
+ }
+ return 0;
+ }
+
+ /**
* Returns AutomaticZenRules owned by the caller.
*
* <p>
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e7f886d..c627436 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2537,6 +2537,16 @@
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_MEDIA_BUTTON = "android.intent.action.MEDIA_BUTTON";
+ /**
+ * Broadcast Action: The "Picture-in-picture (PIP) Button" was pressed.
+ * Includes a single extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
+ * caused the broadcast.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PICTURE_IN_PICTURE_BUTTON =
+ "android.intent.action.PICTURE_IN_PICTURE_BUTTON";
+
/**
* Broadcast Action: The "Camera Button" was pressed. Includes a single
* extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index f611991..90a1198 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -294,6 +294,8 @@
void restoreDefaultApps(in byte[] backup, int userId);
byte[] getIntentFilterVerificationBackup(int userId);
void restoreIntentFilterVerification(in byte[] backup, int userId);
+ byte[] getPermissionGrantBackup(int userId);
+ void restorePermissionGrants(in byte[] backup, int userId);
/**
* Report the set of 'Home' activity candidates, plus (if any) which of them
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index e875864..6935174 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -534,6 +534,24 @@
public static final String STRING_TYPE_WRIST_TILT_GESTURE = "android.sensor.wrist_tilt_gesture";
/**
+ * The current orientation of the device.
+ * <p>
+ * See {@link android.hardware.SensorEvent#values SensorEvent.values} for more details.
+ *
+ * @hide Expected to be used internally for auto-rotate and speaker rotation.
+ *
+ */
+ public static final int TYPE_DEVICE_ORIENTATION = 27;
+
+ /**
+ * A constant string describing a device orientation sensor type.
+ *
+ * @hide
+ * @see #TYPE_DEVICE_ORIENTATION
+ */
+ public static final String STRING_TYPE_DEVICE_ORIENTATION = "android.sensor.device_orientation";
+
+ /**
* A constant describing all sensor types.
*/
public static final int TYPE_ALL = -1;
@@ -618,6 +636,7 @@
1, // SENSOR_TYPE_GLANCE_GESTURE
1, // SENSOR_TYPE_PICK_UP_GESTURE
1, // SENSOR_TYPE_WRIST_TILT_GESTURE
+ 1, // SENSOR_TYPE_DEVICE_ORIENTATION
};
/**
@@ -939,6 +958,9 @@
case TYPE_TEMPERATURE:
mStringType = STRING_TYPE_TEMPERATURE;
return true;
+ case TYPE_DEVICE_ORIENTATION:
+ mStringType = STRING_TYPE_DEVICE_ORIENTATION;
+ return true;
default:
return false;
}
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 906c2a19..9937b2c 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -483,6 +483,20 @@
* on it. In earlier versions, this used to be always 3 which has changed now. </p>
*
* @see GeomagneticField
+ *
+ * <h4> {@link android.hardware.Sensor#TYPE_DEVICE_ORIENTATION
+ * Sensor.TYPE_DEVICE_ORIENTATION}:</h4>
+ * The current device orientation will be available in values[0]. The only
+ * available values are:
+ * <ul>
+ * <li> 0: device is in default orientation (Y axis is vertical and points up)
+ * <li> 1: device is rotated 90 degrees counter-clockwise from default
+ * orientation (X axis is vertical and points up)
+ * <li> 2: device is rotated 180 degrees from default orientation (Y axis is
+ * vertical and points down)
+ * <li> 3: device is rotated 90 degrees clockwise from default orientation (X axis
+ * is vertical and points down)
+ * </ul>
*/
public final float[] values;
diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index 7a1a6a2..838279b 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -16,6 +16,7 @@
package android.os;
+import android.os.IMaintenanceActivityListener;
import android.os.UserHandle;
/** @hide */
@@ -37,4 +38,6 @@
void exitIdle(String reason);
void downloadServiceActive(IBinder token);
void downloadServiceInactive();
+ boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener);
+ void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener);
}
diff --git a/core/java/android/os/IMaintenanceActivityListener.aidl b/core/java/android/os/IMaintenanceActivityListener.aidl
new file mode 100644
index 0000000..6a2581f
--- /dev/null
+++ b/core/java/android/os/IMaintenanceActivityListener.aidl
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/** @hide */
+oneway interface IMaintenanceActivityListener {
+ void onMaintenanceActivityChanged(boolean active);
+}
diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java
index 83efe80..ff9c0df 100644
--- a/core/java/android/print/PrinterId.java
+++ b/core/java/android/print/PrinterId.java
@@ -20,16 +20,17 @@
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
-import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
/**
* This class represents the unique id of a printer.
*/
public final class PrinterId implements Parcelable {
- private final ComponentName mServiceName;
+ private final @NonNull ComponentName mServiceName;
- private final String mLocalId;
+ private final @NonNull String mLocalId;
/**
* Creates a new instance.
@@ -39,14 +40,14 @@
*
* @hide
*/
- public PrinterId(ComponentName serviceName, String localId) {
+ public PrinterId(@NonNull ComponentName serviceName, @NonNull String localId) {
mServiceName = serviceName;
mLocalId = localId;
}
- private PrinterId(Parcel parcel) {
- mServiceName = parcel.readParcelable(null);
- mLocalId = parcel.readString();
+ private PrinterId(@NonNull Parcel parcel) {
+ mServiceName = Preconditions.checkNotNull((ComponentName) parcel.readParcelable(null));
+ mLocalId = Preconditions.checkNotNull(parcel.readString());
}
/**
@@ -56,7 +57,7 @@
*
* @hide
*/
- public ComponentName getServiceName() {
+ public @NonNull ComponentName getServiceName() {
return mServiceName;
}
@@ -93,14 +94,10 @@
return false;
}
PrinterId other = (PrinterId) object;
- if (mServiceName == null) {
- if (other.mServiceName != null) {
- return false;
- }
- } else if (!mServiceName.equals(other.mServiceName)) {
+ if (!mServiceName.equals(other.mServiceName)) {
return false;
}
- if (!TextUtils.equals(mLocalId, other.mLocalId)) {
+ if (!mLocalId.equals(other.mLocalId)) {
return false;
}
return true;
@@ -110,8 +107,7 @@
public int hashCode() {
final int prime = 31;
int hashCode = 1;
- hashCode = prime * hashCode + ((mServiceName != null)
- ? mServiceName.hashCode() : 1);
+ hashCode = prime * hashCode + mServiceName.hashCode();
hashCode = prime * hashCode + mLocalId.hashCode();
return hashCode;
}
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index d0037b7..3104492 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -30,6 +30,8 @@
import android.print.PrinterId;
import android.util.Log;
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -346,6 +348,7 @@
*/
public final PrinterId generatePrinterId(String localId) {
throwIfNotCalledOnMainThread();
+ localId = Preconditions.checkNotNull(localId, "localId cannot be null");
return new PrinterId(new ComponentName(getPackageName(),
getClass().getName()), localId);
}
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index 88bd283..eff09d6 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -85,6 +85,13 @@
"android.service.zen.automatic.configurationActivity";
/**
+ * The name of the {@code meta-data} tag containing the maximum number of rule instances that
+ * can be created for this rule type. Omit or enter a value <= 0 to allow unlimited instances.
+ */
+ public static final String META_DATA_RULE_INSTANCE_LIMIT =
+ "android.service.zen.automatic.ruleInstanceLimit";
+
+ /**
* A String rule id extra passed to {@link #META_DATA_CONFIGURATION_ACTIVITY}.
*/
public static final String EXTRA_RULE_ID = "android.content.automatic.ruleId";
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index d146e5e..cd19607 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -167,6 +167,7 @@
final Rect mDispatchedOutsets = new Rect();
final Rect mFinalSystemInsets = new Rect();
final Rect mFinalStableInsets = new Rect();
+ final Rect mBackdropFrame = new Rect();
final Configuration mConfiguration = new Configuration();
final WindowManager.LayoutParams mLayout
@@ -675,8 +676,8 @@
final int relayoutResult = mSession.relayout(
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
View.VISIBLE, 0, mWinFrame, mOverscanInsets, mContentInsets,
- mVisibleInsets, mStableInsets, mOutsets, mConfiguration,
- mSurfaceHolder.mSurface);
+ mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
+ mConfiguration, mSurfaceHolder.mSurface);
if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface
+ ", frame=" + mWinFrame);
diff --git a/core/java/android/text/style/LocaleSpan.java b/core/java/android/text/style/LocaleSpan.java
index d286231..117de774 100644
--- a/core/java/android/text/style/LocaleSpan.java
+++ b/core/java/android/text/style/LocaleSpan.java
@@ -16,30 +16,56 @@
package android.text.style;
+import com.android.internal.util.Preconditions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Paint;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextPaint;
import android.text.TextUtils;
+import android.util.LocaleList;
+
import java.util.Locale;
/**
* Changes the {@link Locale} of the text to which the span is attached.
*/
public class LocaleSpan extends MetricAffectingSpan implements ParcelableSpan {
- private final Locale mLocale;
+ @NonNull
+ private final LocaleList mLocales;
/**
- * Creates a LocaleSpan.
- * @param locale The {@link Locale} of the text to which the span is
- * attached.
+ * Creates a {@link LocaleSpan} from a well-formed {@link Locale}. Note that only
+ * {@link Locale} objects that can be created by {@link Locale#forLanguageTag(String)} are
+ * supported.
+ *
+ * <p><b>Caveat:</b> Do not specify any {@link Locale} object that cannot be created by
+ * {@link Locale#forLanguageTag(String)}. {@code new Locale(" a ", " b c", " d")} is an
+ * example of such a malformed {@link Locale} object.</p>
+ *
+ * @param locale The {@link Locale} of the text to which the span is attached.
+ *
+ * @see #LocaleSpan(LocaleList)
*/
- public LocaleSpan(Locale locale) {
- mLocale = locale;
+ public LocaleSpan(@Nullable Locale locale) {
+ mLocales = new LocaleList(locale);
}
- public LocaleSpan(Parcel src) {
- mLocale = new Locale(src.readString(), src.readString(), src.readString());
+ /**
+ * Creates a {@link LocaleSpan} from {@link LocaleList}.
+ *
+ * @param locales The {@link LocaleList} of the text to which the span is attached.
+ * @throws NullPointerException if {@code locales} is null
+ */
+ public LocaleSpan(@NonNull LocaleList locales) {
+ Preconditions.checkNotNull(locales, "locales cannot be null");
+ mLocales = locales;
+ }
+
+ public LocaleSpan(Parcel source) {
+ mLocales = LocaleList.CREATOR.createFromParcel(source);
}
@Override
@@ -64,31 +90,40 @@
/** @hide */
public void writeToParcelInternal(Parcel dest, int flags) {
- dest.writeString(mLocale.getLanguage());
- dest.writeString(mLocale.getCountry());
- dest.writeString(mLocale.getVariant());
+ mLocales.writeToParcel(dest, flags);
}
/**
- * Returns the {@link Locale}.
+ * @return The {@link Locale} for this span. If multiple locales are associated with this
+ * span, only the first locale is returned. {@code null} if no {@link Locale} is specified.
*
- * @return The {@link Locale} for this span.
+ * @see LocaleList#getPrimary()
+ * @see #getLocales()
*/
+ @Nullable
public Locale getLocale() {
- return mLocale;
+ return mLocales.getPrimary();
+ }
+
+ /**
+ * @return The entire list of locales that are associated with this span.
+ */
+ @NonNull
+ public LocaleList getLocales() {
+ return mLocales;
}
@Override
public void updateDrawState(TextPaint ds) {
- apply(ds, mLocale);
+ apply(ds, mLocales);
}
@Override
public void updateMeasureState(TextPaint paint) {
- apply(paint, mLocale);
+ apply(paint, mLocales);
}
- private static void apply(Paint paint, Locale locale) {
- paint.setTextLocale(locale);
+ private static void apply(@NonNull Paint paint, @NonNull LocaleList locales) {
+ paint.setTextLocales(locales);
}
}
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index b3cd8c11..bea36c0 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -81,6 +81,8 @@
* so complex relayout of the window should not happen based on them.
* @param outOutsets Rect in which is placed the dead area of the screen that we would like to
* treat as real display. Example of such area is a chin in some models of wearable devices.
+ * @param outBackdropFrame Rect which is used draw the resizing background during a resize
+ * operation.
* @param outConfiguration New configuration of window, if it is now
* becoming visible and the global configuration has changed since it
* was last displayed.
@@ -93,7 +95,8 @@
int requestedWidth, int requestedHeight, int viewVisibility,
int flags, out Rect outFrame, out Rect outOverscanInsets,
out Rect outContentInsets, out Rect outVisibleInsets, out Rect outStableInsets,
- out Rect outOutsets, out Configuration outConfig, out Surface outSurface);
+ out Rect outOutsets, out Rect outBackdropFrame, out Configuration outConfig,
+ out Surface outSurface);
/**
* Position a window relative to it's parent (attached) window without triggering
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index f4fa980..0981e69 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -107,6 +107,7 @@
final Rect mContentInsets = new Rect();
final Rect mStableInsets = new Rect();
final Rect mOutsets = new Rect();
+ final Rect mBackdropFrame = new Rect();
final Configuration mConfiguration = new Configuration();
static final int KEEP_SCREEN_ON_MSG = 1;
@@ -529,8 +530,8 @@
visible ? VISIBLE : GONE,
WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
mWinFrame, mOverscanInsets, mContentInsets,
- mVisibleInsets, mStableInsets, mOutsets, mConfiguration,
- mNewSurface);
+ mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
+ mConfiguration, mNewSurface);
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
reportDrawNeeded = true;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a14f0dc..9c19bf1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -17,6 +17,10 @@
package android.view;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
import android.Manifest;
import android.animation.LayoutTransition;
@@ -141,6 +145,9 @@
*/
static final int MAX_TRACKBALL_DELAY = 250;
+ private static final int RESIZE_MODE_FREEFORM = 0;
+ private static final int RESIZE_MODE_DOCKED_DIVIDER = 1;
+
static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList();
@@ -225,8 +232,10 @@
boolean mIsAnimating;
private boolean mDragResizing;
+ private int mResizeMode;
private int mCanvasOffsetX;
private int mCanvasOffsetY;
+ private boolean mActivityRelaunched;
CompatibilityInfo.Translator mTranslator;
@@ -1337,6 +1346,17 @@
host.dispatchApplyWindowInsets(getWindowInsets(true /* forceConstruct */));
}
+ private static boolean shouldUseDisplaySize(final WindowManager.LayoutParams lp) {
+ return lp.type == TYPE_STATUS_BAR_PANEL
+ || lp.type == TYPE_INPUT_METHOD
+ || lp.type == TYPE_VOLUME_OVERLAY;
+ }
+
+ private int dipToPx(int dip) {
+ final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
+ return (int) (displayMetrics.density * dip + 0.5f);
+ }
+
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
@@ -1391,18 +1411,16 @@
mFullRedrawNeeded = true;
mLayoutRequested = true;
- if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
- || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
+ if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
- DisplayMetrics packageMetrics =
- mView.getContext().getResources().getDisplayMetrics();
- desiredWindowWidth = packageMetrics.widthPixels;
- desiredWindowHeight = packageMetrics.heightPixels;
+ Configuration config = mContext.getResources().getConfiguration();
+ desiredWindowWidth = dipToPx(config.screenWidthDp);
+ desiredWindowHeight = dipToPx(config.screenHeightDp);
}
// We used to use the following condition to choose 32 bits drawing caches:
@@ -1486,17 +1504,21 @@
if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
insetsChanged = true;
}
- if ((lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
- || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT)
- && (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
- || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
- || lp.type == WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)) {
+ if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
+ || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
- // NOTE -- system code, won't try to do compat mode.
- Point size = new Point();
- mDisplay.getRealSize(size);
- desiredWindowWidth = size.x;
- desiredWindowHeight = size.y;
+
+ if (shouldUseDisplaySize(lp)) {
+ // NOTE -- system code, won't try to do compat mode.
+ Point size = new Point();
+ mDisplay.getRealSize(size);
+ desiredWindowWidth = size.x;
+ desiredWindowHeight = size.y;
+ } else {
+ Configuration config = res.getConfiguration();
+ desiredWindowWidth = dipToPx(config.screenWidthDp);
+ desiredWindowHeight = dipToPx(config.screenHeightDp);
+ }
}
}
@@ -1576,12 +1598,17 @@
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
- windowShouldResize |= mDragResizing;
+ windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
// If the backdrop frame doesn't equal to a frame, we are starting a resize operation, so
// force it to be resized.
windowShouldResize |= !mPendingBackDropFrame.equals(mWinFrame);
+ // If the activity was just relaunched, it might have unfrozen the task bounds (while
+ // relaunching), so we need to force a call into window manager to pick up the latest
+ // bounds.
+ windowShouldResize |= mActivityRelaunched;
+
// Determine whether to compute insets.
// If there are no inset listeners remaining then we may still need to compute
// insets in case the old insets were non-empty and must be reset.
@@ -1770,11 +1797,17 @@
}
}
- final boolean dragResizing = (relayoutResult
- & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING) != 0;
+ final boolean freeformResizing = (relayoutResult
+ & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
+ final boolean dockedResizing = (relayoutResult
+ & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
+ final boolean dragResizing = freeformResizing || dockedResizing;
if (mDragResizing != dragResizing) {
if (dragResizing) {
startDragResizing(mPendingBackDropFrame);
+ mResizeMode = freeformResizing
+ ? RESIZE_MODE_FREEFORM
+ : RESIZE_MODE_DOCKED_DIVIDER;
} else {
// We shouldn't come here, but if we come we should end the resize.
endDragResizing();
@@ -1921,29 +1954,7 @@
// in the attach info. We translate only the window frame since on window move
// the window manager tells us only for the new frame but the insets are the
// same and we do not want to translate them more than once.
-
- // TODO: Well, we are checking whether the frame has changed similarly
- // to how this is done for the insets. This is however incorrect since
- // the insets and the frame are translated. For example, the old frame
- // was (1, 1 - 1, 1) and was translated to say (2, 2 - 2, 2), now the new
- // reported frame is (2, 2 - 2, 2) which implies no change but this is not
- // true since we are comparing a not translated value to a translated one.
- // This scenario is rare but we may want to fix that.
-
- final boolean windowMoved = (mAttachInfo.mWindowLeft != frame.left
- || mAttachInfo.mWindowTop != frame.top);
- if (windowMoved) {
- if (mTranslator != null) {
- mTranslator.translateRectInScreenToAppWinFrame(frame);
- }
- mAttachInfo.mWindowLeft = frame.left;
- mAttachInfo.mWindowTop = frame.top;
-
- // Update the light position for the new window offsets.
- if (mAttachInfo.mHardwareRenderer != null) {
- mAttachInfo.mHardwareRenderer.setLightCenter(mAttachInfo);
- }
- }
+ maybeHandleWindowMove(frame);
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
@@ -2058,6 +2069,7 @@
mFirst = false;
mWillDrawSoon = false;
mNewSurfaceNeeded = false;
+ mActivityRelaunched = false;
mViewVisibility = viewVisibility;
mHadWindowFocus = hasWindowFocus;
@@ -2107,6 +2119,31 @@
mIsInTraversal = false;
}
+ private void maybeHandleWindowMove(Rect frame) {
+
+ // TODO: Well, we are checking whether the frame has changed similarly
+ // to how this is done for the insets. This is however incorrect since
+ // the insets and the frame are translated. For example, the old frame
+ // was (1, 1 - 1, 1) and was translated to say (2, 2 - 2, 2), now the new
+ // reported frame is (2, 2 - 2, 2) which implies no change but this is not
+ // true since we are comparing a not translated value to a translated one.
+ // This scenario is rare but we may want to fix that.
+
+ final boolean windowMoved = mAttachInfo.mWindowLeft != frame.left
+ || mAttachInfo.mWindowTop != frame.top;
+ if (windowMoved) {
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWinFrame(frame);
+ }
+ mAttachInfo.mWindowLeft = frame.left;
+ mAttachInfo.mWindowTop = frame.top;
+
+ // Update the light position for the new window offsets.
+ if (mAttachInfo.mHardwareRenderer != null) {
+ mAttachInfo.mHardwareRenderer.setLightCenter(mAttachInfo);
+ }
+ }
+ }
private void handleOutOfResourcesException(Surface.OutOfResourcesException e) {
Log.e(mTag, "OutOfResourcesException initializing HW surface", e);
try {
@@ -3368,10 +3405,19 @@
mPendingBackDropFrame.set(mWinFrame);
- if (mView != null) {
- forceLayout(mView);
+ // Suppress layouts during resizing - a correct layout will happen when resizing
+ // is done, and this just increases system load.
+ boolean isDockedDivider = mWindowAttributes.type == TYPE_DOCK_DIVIDER;
+ boolean suppress = (mDragResizing && mResizeMode == RESIZE_MODE_DOCKED_DIVIDER)
+ || isDockedDivider;
+ if (!suppress) {
+ if (mView != null) {
+ forceLayout(mView);
+ }
+ requestLayout();
+ } else {
+ maybeHandleWindowMove(mWinFrame);
}
- requestLayout();
}
break;
case MSG_WINDOW_FOCUS_CHANGED: {
@@ -5484,7 +5530,8 @@
(int) (mView.getMeasuredHeight() * appScale + 0.5f),
viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
- mPendingStableInsets, mPendingOutsets, mPendingConfiguration, mSurface);
+ mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingConfiguration,
+ mSurface);
//Log.d(mTag, "<<<<<< BACK FROM relayout");
if (restore) {
params.restore();
@@ -7010,6 +7057,15 @@
}
/**
+ * Tells this instance that its corresponding activity has just relaunched. In this case, we
+ * need to force a relayout of the window to make sure we get the correct bounds from window
+ * manager.
+ */
+ public void reportActivityRelaunched() {
+ mActivityRelaunched = true;
+ }
+
+ /**
* Class for managing the accessibility interaction connection
* based on the global accessibility state.
*/
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index d89369b..dfe0cc7 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -2120,4 +2120,10 @@
* @hide
*/
public abstract void onMultiWindowModeChanged();
+
+ /**
+ * Called when the activity just relaunched.
+ * @hide
+ */
+ public abstract void reportActivityRelaunched();
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 8c68e92..1530b47 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -30,6 +30,7 @@
import android.util.ArraySet;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
+
import com.android.internal.util.FastPrintWriter;
import java.io.FileDescriptor;
@@ -70,16 +71,23 @@
public static final int RELAYOUT_RES_SURFACE_CHANGED = 0x4;
/**
+ * The window is being resized by dragging on the docked divider. The client should render
+ * at (0, 0) and extend its background to the background frame passed into
+ * {@link IWindow#resized}.
+ */
+ public static final int RELAYOUT_RES_DRAG_RESIZING_DOCKED = 0x8;
+
+ /**
* The window is being resized by dragging one of the window corners,
* in this case the surface would be fullscreen-sized. The client should
* render to the actual frame location (instead of (0,curScrollY)).
*/
- public static final int RELAYOUT_RES_DRAG_RESIZING = 0x8;
+ public static final int RELAYOUT_RES_DRAG_RESIZING_FREEFORM = 0x10;
/**
* The window manager has changed the size of the surface from the last call.
*/
- public static final int RELAYOUT_RES_SURFACE_RESIZED = 0x10;
+ public static final int RELAYOUT_RES_SURFACE_RESIZED = 0x20;
/**
* Flag for relayout: the client will be later giving
diff --git a/core/java/android/webkit/TokenBindingService.java b/core/java/android/webkit/TokenBindingService.java
new file mode 100644
index 0000000..a6d7b4a
--- /dev/null
+++ b/core/java/android/webkit/TokenBindingService.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.annotation.SystemApi;
+import android.net.Uri;
+
+import java.security.KeyPair;
+import java.security.spec.AlgorithmParameterSpec;
+
+/**
+ * Enables the token binding procotol, and provides access to the keys. See
+ * https://tools.ietf.org/html/draft-ietf-tokbind-protocol-03
+ *
+ * All methods are required to be called on the UI thread where WebView is
+ * attached to the View hierarchy.
+ * @hide
+ */
+public abstract class TokenBindingService {
+
+ public static final String KEY_ALGORITHM_RSA2048_PKCS_1_5 = "RSA2048_PKCS_1.5";
+ public static final String KEY_ALGORITHM_RSA2048_PSS = "RSA2048PSS";
+ public static final String KEY_ALGORITHM_ECDSAP256 = "ECDSAP256";
+
+ /**
+ * Returns the default TokenBinding service instance. At present there is
+ * only one token binding service instance for all WebView instances,
+ * however this restriction may be relaxed in the future.
+ *
+ * @return The default TokenBindingService instance.
+ */
+ public static TokenBindingService getInstance() {
+ return WebViewFactory.getProvider().getTokenBindingService();
+ }
+
+ /**
+ * Enables the token binding protocol. The token binding protocol
+ * has to be enabled before creating any WebViews.
+ *
+ * @throws IllegalStateException if a WebView was already created.
+ */
+ public abstract void enableTokenBinding();
+
+ /**
+ * Retrieves the key pair for a given origin from the internal
+ * TokenBinding key store asynchronously.
+ * Will create a key pair if one does not exist.
+ *
+ * @param origin The origin for the server.
+ * @param algorithm The algorithm for generating the token binding key.
+ * @param callback The callback that will be called when key is available.
+ * Cannot be null.
+ */
+ public abstract void getKey(Uri origin,
+ String algorithm,
+ ValueCallback<KeyPair> callback);
+ /**
+ * Deletes specified key (for use when associated cookie is cleared).
+ *
+ * @param origin The origin of the server.
+ * @param callback The callback that will be called when key is deleted. The
+ * callback parameter (Boolean) will indicate if operation is
+ * successful or if failed. The callback can be null.
+ */
+ public abstract void deleteKey(Uri origin,
+ ValueCallback<Boolean> callback);
+
+ /**
+ * Deletes all the keys (for use when cookies are cleared).
+ *
+ * @param callback The callback that will be called when keys are deleted.
+ * The callback parameter (Boolean) will indicate if operation is
+ * successful or if failed. The callback can be null.
+ */
+ public abstract void deleteAllKeys(ValueCallback<Boolean> callback);
+}
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 9105394..02c911f 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -103,6 +103,15 @@
CookieManager getCookieManager();
/**
+ * Gets the TokenBindingService instance for this WebView implementation. The
+ * implementation must return the same instance on subsequent calls.
+ *
+ * @return the TokenBindingService instance
+ * @hide
+ */
+ TokenBindingService getTokenBindingService();
+
+ /**
* Gets the singleton WebIconDatabase instance for this WebView implementation. The
* implementation must return the same instance on subsequent calls.
*
diff --git a/core/java/android/widget/DropDownListView.java b/core/java/android/widget/DropDownListView.java
index 2fb2101..02f7e7a 100644
--- a/core/java/android/widget/DropDownListView.java
+++ b/core/java/android/widget/DropDownListView.java
@@ -19,18 +19,10 @@
import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
+import android.annotation.NonNull;
import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.IntProperty;
import android.view.MotionEvent;
import android.view.View;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.widget.TextView;
-import android.widget.ListView;
-
/**
* Wrapper class for a ListView. This wrapper can hijack the focus to
@@ -41,26 +33,6 @@
* @hide
*/
public class DropDownListView extends ListView {
- /** Duration in milliseconds of the drag-to-open click animation. */
- private static final long CLICK_ANIM_DURATION = 150;
-
- /** Target alpha value for drag-to-open click animation. */
- private static final int CLICK_ANIM_ALPHA = 0x80;
-
- /** Wrapper around Drawable's <code>alpha</code> property. */
- private static final IntProperty<Drawable> DRAWABLE_ALPHA =
- new IntProperty<Drawable>("alpha") {
- @Override
- public void setValue(Drawable object, int value) {
- object.setAlpha(value);
- }
-
- @Override
- public Integer get(Drawable object) {
- return object.getAlpha();
- }
- };
-
/*
* WARNING: This is a workaround for a touch mode issue.
*
@@ -99,9 +71,6 @@
/** Whether to force drawing of the pressed state selector. */
private boolean mDrawsInPressedState;
- /** Current drag-to-open click animation, if any. */
- private Animator mClickAnimation;
-
/** Helper for drag-to-open auto scrolling. */
private AbsListViewAutoScroller mScrollHelper;
@@ -110,7 +79,7 @@
*
* @param context this view's context
*/
- public DropDownListView(Context context, boolean hijackFocus) {
+ public DropDownListView(@NonNull Context context, boolean hijackFocus) {
this(context, hijackFocus, com.android.internal.R.attr.dropDownListViewStyle);
}
@@ -119,7 +88,7 @@
*
* @param context this view's context
*/
- public DropDownListView(Context context, boolean hijackFocus, int defStyleAttr) {
+ public DropDownListView(@NonNull Context context, boolean hijackFocus, int defStyleAttr) {
super(context, null, defStyleAttr);
mHijackFocus = hijackFocus;
// TODO: Add an API to control this
@@ -132,7 +101,7 @@
}
@Override
- public boolean onHoverEvent(MotionEvent ev) {
+ public boolean onHoverEvent(@NonNull MotionEvent ev) {
// Allow the super class to handle hover state management first.
final boolean handled = super.onHoverEvent(ev);
@@ -169,7 +138,7 @@
* @param activePointerId id of the pointer that activated forwarding
* @return whether the event was handled
*/
- public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
+ public boolean onForwardedEvent(@NonNull MotionEvent event, int activePointerId) {
boolean handledEvent = true;
boolean clearPressedItem = false;
@@ -201,7 +170,8 @@
handledEvent = true;
if (actionMasked == MotionEvent.ACTION_UP) {
- clickPressedItem(child, position);
+ final long id = getItemIdAtPosition(position);
+ performItemClick(child, position, id);
}
break;
}
@@ -234,30 +204,6 @@
this.mListSelectionHidden = listSelectionHidden;
}
- /**
- * Starts an alpha animation on the selector. When the animation ends,
- * the list performs a click on the item.
- */
- private void clickPressedItem(final View child, final int position) {
- final long id = getItemIdAtPosition(position);
- final Animator anim = ObjectAnimator.ofInt(
- mSelector, DRAWABLE_ALPHA, 0xFF, CLICK_ANIM_ALPHA, 0xFF);
- anim.setDuration(CLICK_ANIM_DURATION);
- anim.setInterpolator(new AccelerateDecelerateInterpolator());
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- performItemClick(child, position, id);
- }
- });
- anim.start();
-
- if (mClickAnimation != null) {
- mClickAnimation.cancel();
- }
- mClickAnimation = anim;
- }
-
private void clearPressedItem() {
mDrawsInPressedState = false;
setPressed(false);
@@ -267,14 +213,9 @@
if (motionView != null) {
motionView.setPressed(false);
}
-
- if (mClickAnimation != null) {
- mClickAnimation.cancel();
- mClickAnimation = null;
- }
}
- private void setPressedItem(View child, int position, float x, float y) {
+ private void setPressedItem(@NonNull View child, int position, float x, float y) {
mDrawsInPressedState = true;
// Ordering is essential. First, update the container's pressed state.
@@ -311,11 +252,6 @@
// Refresh the drawable state to reflect the new pressed state,
// which will also update the selector state.
refreshDrawableState();
-
- if (mClickAnimation != null) {
- mClickAnimation.cancel();
- mClickAnimation = null;
- }
}
@Override
diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/core/java/com/android/internal/policy/DockedDividerUtils.java
index d06e2bb..f7f5f15 100644
--- a/core/java/com/android/internal/policy/DockedDividerUtils.java
+++ b/core/java/com/android/internal/policy/DockedDividerUtils.java
@@ -48,17 +48,17 @@
outRect.top = position + dividerSize;
break;
}
- if (outRect.left > outRect.right) {
- outRect.left = outRect.right;
+ if (outRect.left >= outRect.right) {
+ outRect.left = outRect.right - 1;
}
- if (outRect.top > outRect.bottom) {
- outRect.top = outRect.bottom;
+ if (outRect.top >= outRect.bottom) {
+ outRect.top = outRect.bottom - 1;
}
- if (outRect.right < outRect.left) {
- outRect.right = outRect.left;
+ if (outRect.right <= outRect.left) {
+ outRect.right = outRect.left + 1;
}
- if (outRect.bottom < outRect.top) {
- outRect.bottom = outRect.top;
+ if (outRect.bottom <= outRect.top) {
+ outRect.bottom = outRect.top + 1;
}
}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index b4c4ef5..4670cca 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -684,6 +684,13 @@
}
}
+ @Override
+ public void reportActivityRelaunched() {
+ if (mDecor != null && mDecor.getViewRootImpl() != null) {
+ mDecor.getViewRootImpl().reportActivityRelaunched();
+ }
+ }
+
private static void clearMenuViews(PanelFeatureState st) {
// This can be called on config changes, so we should make sure
// the views will be reconstructed based on the new orientation, etc.
diff --git a/core/java/com/android/server/backup/PermissionBackupHelper.java b/core/java/com/android/server/backup/PermissionBackupHelper.java
new file mode 100644
index 0000000..ff0e63d
--- /dev/null
+++ b/core/java/com/android/server/backup/PermissionBackupHelper.java
@@ -0,0 +1,78 @@
+/*
+ * 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.server.backup;
+
+import android.app.AppGlobals;
+import android.app.backup.BlobBackupHelper;
+import android.content.pm.IPackageManager;
+import android.os.UserHandle;
+import android.util.Slog;
+
+public class PermissionBackupHelper extends BlobBackupHelper {
+ private static final String TAG = "PermissionBackup";
+ private static final boolean DEBUG = false;
+
+ // current schema of the backup state blob
+ private static final int STATE_VERSION = 1;
+
+ // key under which the permission-grant state blob is committed to backup
+ private static final String KEY_PERMISSIONS = "permissions";
+
+ public PermissionBackupHelper() {
+ super(STATE_VERSION, KEY_PERMISSIONS);
+ }
+
+ @Override
+ protected byte[] getBackupPayload(String key) {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ if (DEBUG) {
+ Slog.d(TAG, "Handling backup of " + key);
+ }
+ try {
+ switch (key) {
+ case KEY_PERMISSIONS:
+ return pm.getPermissionGrantBackup(UserHandle.USER_SYSTEM);
+
+ default:
+ Slog.w(TAG, "Unexpected backup key " + key);
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to store payload " + key);
+ }
+ return null;
+ }
+
+ @Override
+ protected void applyRestoredPayload(String key, byte[] payload) {
+ IPackageManager pm = AppGlobals.getPackageManager();
+ if (DEBUG) {
+ Slog.d(TAG, "Handling restore of " + key);
+ }
+ try {
+ switch (key) {
+ case KEY_PERMISSIONS:
+ pm.restorePermissionGrants(payload, UserHandle.USER_SYSTEM);
+ break;
+
+ default:
+ Slog.w(TAG, "Unexpected restore key " + key);
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to restore key " + key);
+ }
+ }
+}
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index 234815f..3f76e13 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -46,6 +46,7 @@
private static final String SYNC_SETTINGS_HELPER = "account_sync_settings";
private static final String PREFERRED_HELPER = "preferred_activities";
private static final String NOTIFICATION_HELPER = "notifications";
+ private static final String PERMISSION_HELPER = "permissions";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -94,6 +95,7 @@
addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this));
addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper());
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
+ addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
super.onBackup(oldState, data, newState);
}
@@ -128,6 +130,7 @@
addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this));
addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper());
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
+ addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
try {
super.onRestore(data, appVersionCode, newState);
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 63f193d..40af22a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1257,6 +1257,7 @@
REG_JNI(register_android_os_Binder),
REG_JNI(register_android_os_Parcel),
REG_JNI(register_android_nio_utils),
+ REG_JNI(register_android_graphics_Canvas),
REG_JNI(register_android_graphics_Graphics),
REG_JNI(register_android_view_DisplayEventReceiver),
REG_JNI(register_android_view_RenderNode),
@@ -1289,7 +1290,6 @@
REG_JNI(register_android_graphics_BitmapRegionDecoder),
REG_JNI(register_android_graphics_Camera),
REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
- REG_JNI(register_android_graphics_Canvas),
REG_JNI(register_android_graphics_CanvasProperty),
REG_JNI(register_android_graphics_ColorFilter),
REG_JNI(register_android_graphics_DrawFilter),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index a805b6d..80ccb61 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -774,11 +774,14 @@
return ret;
}
-static void Bitmap_destructor(JNIEnv* env, jobject, jlong bitmapHandle) {
- LocalScopedBitmap bitmap(bitmapHandle);
+static void Bitmap_destruct(Bitmap* bitmap) {
bitmap->detachFromJava();
}
+static jlong Bitmap_getNativeFinalizer(JNIEnv*, jobject) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Bitmap_destruct));
+}
+
static jboolean Bitmap_recycle(JNIEnv* env, jobject, jlong bitmapHandle) {
LocalScopedBitmap bitmap(bitmapHandle);
bitmap->freePixels();
@@ -1357,7 +1360,7 @@
(void*)Bitmap_copy },
{ "nativeCopyAshmem", "(J)Landroid/graphics/Bitmap;",
(void*)Bitmap_copyAshmem },
- { "nativeDestructor", "(J)V", (void*)Bitmap_destructor },
+ { "nativeGetNativeFinalizer", "()J", (void*)Bitmap_getNativeFinalizer },
{ "nativeRecycle", "(J)Z", (void*)Bitmap_recycle },
{ "nativeReconfigure", "(JIIIIZ)V", (void*)Bitmap_reconfigure },
{ "nativeCompress", "(JIILjava/io/OutputStream;[B)Z",
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 0a25a0a..98f8ce3 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -76,9 +76,12 @@
AFTER, AT_OR_AFTER, BEFORE, AT_OR_BEFORE, AT
};
- static void finalizer(JNIEnv* env, jobject clazz, jlong objHandle) {
- Paint* obj = reinterpret_cast<Paint*>(objHandle);
- delete obj;
+ static void deletePaint(Paint* paint) {
+ delete paint;
+ }
+
+ static jlong getNativeFinalizer(JNIEnv*, jobject) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&deletePaint));
}
static jlong init(JNIEnv* env, jobject) {
@@ -863,7 +866,7 @@
}; // namespace PaintGlue
static const JNINativeMethod methods[] = {
- {"nFinalizer", "(J)V", (void*) PaintGlue::finalizer},
+ {"nGetNativeFinalizer", "()J", (void*) PaintGlue::getNativeFinalizer},
{"nInit","()J", (void*) PaintGlue::init},
{"nInitWithPaint","(J)J", (void*) PaintGlue::initWithPaint},
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index fda0ffa..2507e4d 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -60,6 +60,27 @@
// as all the data needed is contained within the newly created LocalMatrixShader.
SkASSERT(shaderHandle);
SkAutoTUnref<SkShader> currentShader(reinterpret_cast<SkShader*>(shaderHandle));
+
+ // Attempt to peel off an existing proxy shader and get the proxy's matrix. If
+ // the proxy existed and it's matrix equals the desired matrix then just return
+ // the proxy, otherwise replace it with a new proxy containing the desired matrix.
+ //
+ // refAsALocalMatrixShader(): if the shader contains a proxy then it unwraps the proxy
+ // returning both the underlying shader and the proxy's matrix.
+ // newWithLocalMatrix(): will return a proxy shader that wraps the provided shader and
+ // concats the provided local matrix with the shader's matrix.
+ //
+ // WARNING: This proxy replacement only behaves like a setter because the Java
+ // API enforces that all local matrices are set using this call and
+ // not passed to the constructor of the Shader.
+ SkMatrix proxyMatrix;
+ SkAutoTUnref<SkShader> baseShader(currentShader->refAsALocalMatrixShader(&proxyMatrix));
+ if (baseShader.get()) {
+ if (proxyMatrix == *matrix) {
+ return reinterpret_cast<jlong>(currentShader.detach());
+ }
+ return reinterpret_cast<jlong>(baseShader->newWithLocalMatrix(*matrix));
+ }
return reinterpret_cast<jlong>(currentShader->newWithLocalMatrix(*matrix));
}
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index e4e73a4..34877e0 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -37,8 +37,12 @@
return reinterpret_cast<Canvas*>(canvasHandle);
}
-static void finalizer(JNIEnv* env, jobject clazz, jlong canvasHandle) {
- delete get_canvas(canvasHandle);
+static void delete_canvas(Canvas* canvas) {
+ delete canvas;
+}
+
+static jlong getNativeFinalizer(JNIEnv* env, jobject clazz) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&delete_canvas));
}
// Native wrapper constructor used by Canvas(Bitmap)
@@ -710,7 +714,7 @@
}; // namespace CanvasJNI
static const JNINativeMethod gMethods[] = {
- {"finalizer", "(J)V", (void*) CanvasJNI::finalizer},
+ {"getNativeFinalizer", "()J", (void*) CanvasJNI::getNativeFinalizer},
{"initRaster", "(Landroid/graphics/Bitmap;)J", (void*) CanvasJNI::initRaster},
{"native_setBitmap", "!(JLandroid/graphics/Bitmap;)V", (void*) CanvasJNI::setBitmap},
{"native_isOpaque","!(J)Z", (void*) CanvasJNI::isOpaque},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b02de0e..665c417 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -220,6 +220,8 @@
<protected-broadcast android:name="android.intent.action.MEDIA_UNMOUNTABLE" />
<protected-broadcast android:name="android.intent.action.MEDIA_EJECT" />
+ <protected-broadcast android:name="android.intent.action.PICTURE_IN_PICTURE_BUTTON" />
+
<protected-broadcast android:name="android.net.conn.CAPTIVE_PORTAL" />
<protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<!-- @deprecated. Only {@link android.net.ConnectivityManager.CONNECTIVITY_ACTION} is sent. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 61da1e4..3f6a0c1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -981,6 +981,7 @@
0 - Nothing
1 - Recent apps view in SystemUI
2 - Launch assist intent
+ 3 - Start picture-in-picture (PIP) or launch PIP UI
This needs to match the constants in
policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
-->
@@ -2428,4 +2429,10 @@
<!-- List of comma separated package names for which we the system will not show crash, ANR,
etc. dialogs. -->
<string translatable="false" name="config_appsNotReportingCrashes"></string>
+
+ <!-- Inactivity threshold (in milliseconds) used in JobScheduler. JobScheduler will consider
+ the device to be "idle" after being inactive for this long. -->
+ <integer name="config_jobSchedulerInactivityIdleThreshold">4260000</integer>
+ <!-- The alarm window (in milliseconds) that JobScheduler uses to enter the idle state -->
+ <integer name="config_jobSchedulerIdleWindowSlop">300000</integer>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 32510397..e65ce5e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2258,6 +2258,9 @@
<java-symbol type="integer" name="config_defaultNightMode" />
+ <java-symbol type="integer" name="config_jobSchedulerInactivityIdleThreshold" />
+ <java-symbol type="integer" name="config_jobSchedulerIdleWindowSlop" />
+
<java-symbol type="style" name="Animation.ImmersiveModeConfirmation" />
<java-symbol type="integer" name="config_screen_magnification_multi_tap_adjustment" />
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index b5b1a25..169ef0b 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -25,14 +25,14 @@
import android.util.DisplayMetrics;
import android.util.Log;
-import dalvik.system.VMRuntime;
-
import java.io.OutputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
+import libcore.util.NativeAllocationRegistry;
+
public final class Bitmap implements Parcelable {
private static final String TAG = "Bitmap";
@@ -44,6 +44,10 @@
*/
public static final int DENSITY_NONE = 0;
+ // Estimated size of the Bitmap native allocation, not including
+ // pixel data.
+ private static final long NATIVE_ALLOCATION_SIZE = 32;
+
/**
* Backing buffer for the Bitmap.
*/
@@ -51,7 +55,6 @@
// Convenience for JNI access
private final long mNativePtr;
- private final BitmapFinalizer mFinalizer;
private final boolean mIsMutable;
@@ -125,9 +128,13 @@
}
mNativePtr = nativeBitmap;
- mFinalizer = new BitmapFinalizer(nativeBitmap);
- int nativeAllocationByteCount = (buffer == null ? getByteCount() : 0);
- mFinalizer.setNativeAllocationByteCount(nativeAllocationByteCount);
+ long nativeSize = NATIVE_ALLOCATION_SIZE;
+ if (buffer == null) {
+ nativeSize += getByteCount();
+ }
+ NativeAllocationRegistry registry = new NativeAllocationRegistry(
+ nativeGetNativeFinalizer(), nativeSize);
+ registry.registerNativeAllocation(this, nativeBitmap);
}
/**
@@ -253,7 +260,7 @@
throw new IllegalStateException("native-backed bitmaps may not be reconfigured");
}
- nativeReconfigure(mFinalizer.mNativeBitmap, width, height, config.nativeInt,
+ nativeReconfigure(mNativePtr, width, height, config.nativeInt,
mBuffer.length, mRequestPremultiplied);
mWidth = width;
mHeight = height;
@@ -330,8 +337,8 @@
* there are no more references to this bitmap.
*/
public void recycle() {
- if (!mRecycled && mFinalizer.mNativeBitmap != 0) {
- if (nativeRecycle(mFinalizer.mNativeBitmap)) {
+ if (!mRecycled && mNativePtr != 0) {
+ if (nativeRecycle(mNativePtr)) {
// return value indicates whether native pixel object was actually recycled.
// false indicates that it is still in use at the native level and these
// objects should not be collected now. They will be collected later when the
@@ -364,7 +371,7 @@
if (mRecycled) {
Log.w(TAG, "Called getGenerationId() on a recycle()'d bitmap! This is undefined behavior!");
}
- return nativeGenerationId(mFinalizer.mNativeBitmap);
+ return nativeGenerationId(mNativePtr);
}
/**
@@ -520,7 +527,7 @@
throw new RuntimeException("Buffer not large enough for pixels");
}
- nativeCopyPixelsToBuffer(mFinalizer.mNativeBitmap, dst);
+ nativeCopyPixelsToBuffer(mNativePtr, dst);
// now update the buffer's position
int position = dst.position();
@@ -560,7 +567,7 @@
throw new RuntimeException("Buffer not large enough for pixels");
}
- nativeCopyPixelsFromBuffer(mFinalizer.mNativeBitmap, src);
+ nativeCopyPixelsFromBuffer(mNativePtr, src);
// now update the buffer's position
int position = src.position();
@@ -582,7 +589,7 @@
*/
public Bitmap copy(Config config, boolean isMutable) {
checkRecycled("Can't copy a recycled bitmap");
- Bitmap b = nativeCopy(mFinalizer.mNativeBitmap, config.nativeInt, isMutable);
+ Bitmap b = nativeCopy(mNativePtr, config.nativeInt, isMutable);
if (b != null) {
b.setPremultiplied(mRequestPremultiplied);
b.mDensity = mDensity;
@@ -598,7 +605,7 @@
*/
public Bitmap createAshmemBitmap() {
checkRecycled("Can't copy a recycled bitmap");
- Bitmap b = nativeCopyAshmem(mFinalizer.mNativeBitmap);
+ Bitmap b = nativeCopyAshmem(mNativePtr);
if (b != null) {
b.setPremultiplied(mRequestPremultiplied);
b.mDensity = mDensity;
@@ -859,7 +866,7 @@
}
bm.setHasAlpha(hasAlpha);
if (config == Config.ARGB_8888 && !hasAlpha) {
- nativeErase(bm.mFinalizer.mNativeBitmap, 0xff000000);
+ nativeErase(bm.mNativePtr, 0xff000000);
}
// No need to initialize the bitmap to zeroes with other configs;
// it is backed by a VM byte array which is by definition preinitialized
@@ -1049,7 +1056,7 @@
throw new IllegalArgumentException("quality must be 0..100");
}
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "Bitmap.compress");
- boolean result = nativeCompress(mFinalizer.mNativeBitmap, format.nativeInt,
+ boolean result = nativeCompress(mNativePtr, format.nativeInt,
quality, stream, new byte[WORKING_COMPRESS_STORAGE]);
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return result;
@@ -1093,7 +1100,7 @@
if (mRecycled) {
Log.w(TAG, "Called isPremultiplied() on a recycle()'d bitmap! This is undefined behavior!");
}
- return nativeIsPremultiplied(mFinalizer.mNativeBitmap);
+ return nativeIsPremultiplied(mNativePtr);
}
/**
@@ -1119,7 +1126,7 @@
public final void setPremultiplied(boolean premultiplied) {
checkRecycled("setPremultiplied called on a recycled bitmap");
mRequestPremultiplied = premultiplied;
- nativeSetPremultiplied(mFinalizer.mNativeBitmap, premultiplied);
+ nativeSetPremultiplied(mNativePtr, premultiplied);
}
/** Returns the bitmap's width */
@@ -1220,7 +1227,7 @@
if (mRecycled) {
Log.w(TAG, "Called getRowBytes() on a recycle()'d bitmap! This is undefined behavior!");
}
- return nativeRowBytes(mFinalizer.mNativeBitmap);
+ return nativeRowBytes(mNativePtr);
}
/**
@@ -1266,7 +1273,7 @@
if (mRecycled) {
Log.w(TAG, "Called getConfig() on a recycle()'d bitmap! This is undefined behavior!");
}
- return Config.nativeToConfig(nativeConfig(mFinalizer.mNativeBitmap));
+ return Config.nativeToConfig(nativeConfig(mNativePtr));
}
/** Returns true if the bitmap's config supports per-pixel alpha, and
@@ -1281,7 +1288,7 @@
if (mRecycled) {
Log.w(TAG, "Called hasAlpha() on a recycle()'d bitmap! This is undefined behavior!");
}
- return nativeHasAlpha(mFinalizer.mNativeBitmap);
+ return nativeHasAlpha(mNativePtr);
}
/**
@@ -1296,7 +1303,7 @@
*/
public void setHasAlpha(boolean hasAlpha) {
checkRecycled("setHasAlpha called on a recycled bitmap");
- nativeSetHasAlpha(mFinalizer.mNativeBitmap, hasAlpha, mRequestPremultiplied);
+ nativeSetHasAlpha(mNativePtr, hasAlpha, mRequestPremultiplied);
}
/**
@@ -1320,7 +1327,7 @@
if (mRecycled) {
Log.w(TAG, "Called hasMipMap() on a recycle()'d bitmap! This is undefined behavior!");
}
- return nativeHasMipMap(mFinalizer.mNativeBitmap);
+ return nativeHasMipMap(mNativePtr);
}
/**
@@ -1345,7 +1352,7 @@
*/
public final void setHasMipMap(boolean hasMipMap) {
checkRecycled("setHasMipMap called on a recycled bitmap");
- nativeSetHasMipMap(mFinalizer.mNativeBitmap, hasMipMap);
+ nativeSetHasMipMap(mNativePtr, hasMipMap);
}
/**
@@ -1358,7 +1365,7 @@
if (!isMutable()) {
throw new IllegalStateException("cannot erase immutable bitmaps");
}
- nativeErase(mFinalizer.mNativeBitmap, c);
+ nativeErase(mNativePtr, c);
}
/**
@@ -1375,7 +1382,7 @@
public int getPixel(int x, int y) {
checkRecycled("Can't call getPixel() on a recycled bitmap");
checkPixelAccess(x, y);
- return nativeGetPixel(mFinalizer.mNativeBitmap, x, y);
+ return nativeGetPixel(mNativePtr, x, y);
}
/**
@@ -1408,7 +1415,7 @@
return; // nothing to do
}
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
- nativeGetPixels(mFinalizer.mNativeBitmap, pixels, offset, stride,
+ nativeGetPixels(mNativePtr, pixels, offset, stride,
x, y, width, height);
}
@@ -1489,7 +1496,7 @@
throw new IllegalStateException();
}
checkPixelAccess(x, y);
- nativeSetPixel(mFinalizer.mNativeBitmap, x, y, color);
+ nativeSetPixel(mNativePtr, x, y, color);
}
/**
@@ -1525,7 +1532,7 @@
return; // nothing to do
}
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
- nativeSetPixels(mFinalizer.mNativeBitmap, pixels, offset, stride,
+ nativeSetPixels(mNativePtr, pixels, offset, stride,
x, y, width, height);
}
@@ -1563,7 +1570,7 @@
*/
public void writeToParcel(Parcel p, int flags) {
checkRecycled("Can't parcel a recycled bitmap");
- if (!nativeWriteToParcel(mFinalizer.mNativeBitmap, mIsMutable, mDensity, p)) {
+ if (!nativeWriteToParcel(mNativePtr, mIsMutable, mDensity, p)) {
throw new RuntimeException("native writeToParcel failed");
}
}
@@ -1609,7 +1616,7 @@
public Bitmap extractAlpha(Paint paint, int[] offsetXY) {
checkRecycled("Can't extractAlpha on a recycled bitmap");
long nativePaint = paint != null ? paint.getNativeInstance() : 0;
- Bitmap bm = nativeExtractAlpha(mFinalizer.mNativeBitmap, nativePaint, offsetXY);
+ Bitmap bm = nativeExtractAlpha(mNativePtr, nativePaint, offsetXY);
if (bm == null) {
throw new RuntimeException("Failed to extractAlpha on Bitmap");
}
@@ -1629,7 +1636,7 @@
if (other.isRecycled()) {
throw new IllegalArgumentException("Can't compare to a recycled bitmap!");
}
- return nativeSameAs(mFinalizer.mNativeBitmap, other.mFinalizer.mNativeBitmap);
+ return nativeSameAs(mNativePtr, other.mNativePtr);
}
/**
@@ -1660,41 +1667,6 @@
return nativeRefPixelRef(mNativePtr);
}
- private static class BitmapFinalizer {
- private long mNativeBitmap;
-
- // Native memory allocated for the duration of the Bitmap,
- // if pixel data allocated into native memory, instead of java byte[]
- private int mNativeAllocationByteCount;
-
- BitmapFinalizer(long nativeBitmap) {
- mNativeBitmap = nativeBitmap;
- }
-
- public void setNativeAllocationByteCount(int nativeByteCount) {
- if (mNativeAllocationByteCount != 0) {
- VMRuntime.getRuntime().registerNativeFree(mNativeAllocationByteCount);
- }
- mNativeAllocationByteCount = nativeByteCount;
- if (mNativeAllocationByteCount != 0) {
- VMRuntime.getRuntime().registerNativeAllocation(mNativeAllocationByteCount);
- }
- }
-
- @Override
- public void finalize() {
- try {
- super.finalize();
- } catch (Throwable t) {
- // Ignore
- } finally {
- setNativeAllocationByteCount(0);
- nativeDestructor(mNativeBitmap);
- mNativeBitmap = 0;
- }
- }
- }
-
//////////// native methods
private static native Bitmap nativeCreate(int[] colors, int offset,
@@ -1703,7 +1675,7 @@
private static native Bitmap nativeCopy(long nativeSrcBitmap, int nativeConfig,
boolean isMutable);
private static native Bitmap nativeCopyAshmem(long nativeSrcBitmap);
- private static native void nativeDestructor(long nativeBitmap);
+ private static native long nativeGetNativeFinalizer();
private static native boolean nativeRecycle(long nativeBitmap);
private static native void nativeReconfigure(long nativeBitmap, int width, int height,
int config, int allocSize,
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index 1cc5346..d4f745d 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -31,6 +31,8 @@
import javax.microedition.khronos.opengles.GL;
+import libcore.util.NativeAllocationRegistry;
+
/**
* The Canvas class holds the "draw" calls. To draw something, you need
* 4 basic components: A Bitmap to hold the pixels, a Canvas to host
@@ -50,7 +52,7 @@
/**
* Should only be assigned in constructors (or setBitmap if software canvas),
- * freed in finalizer.
+ * freed by NativeAllocation.
* @hide
*/
protected long mNativeCanvasWrapper;
@@ -85,32 +87,15 @@
// (see SkCanvas.cpp, SkDraw.cpp)
private static final int MAXMIMUM_BITMAP_SIZE = 32766;
+ // The approximate size of the native allocation associated with
+ // a Canvas object.
+ private static final long NATIVE_ALLOCATION_SIZE = 525;
+
+ private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ getNativeFinalizer(), NATIVE_ALLOCATION_SIZE);
+
// This field is used to finalize the native Canvas properly
- private final CanvasFinalizer mFinalizer;
-
- private static final class CanvasFinalizer {
- private long mNativeCanvasWrapper;
-
- public CanvasFinalizer(long nativeCanvas) {
- mNativeCanvasWrapper = nativeCanvas;
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- dispose();
- } finally {
- super.finalize();
- }
- }
-
- public void dispose() {
- if (mNativeCanvasWrapper != 0) {
- finalizer(mNativeCanvasWrapper);
- mNativeCanvasWrapper = 0;
- }
- }
- }
+ private Runnable mFinalizer;
/**
* Construct an empty raster canvas. Use setBitmap() to specify a bitmap to
@@ -122,7 +107,7 @@
if (!isHardwareAccelerated()) {
// 0 means no native bitmap
mNativeCanvasWrapper = initRaster(null);
- mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
+ mFinalizer = sRegistry.registerNativeAllocation(this, mNativeCanvasWrapper);
} else {
mFinalizer = null;
}
@@ -143,7 +128,7 @@
}
throwIfCannotDraw(bitmap);
mNativeCanvasWrapper = initRaster(bitmap);
- mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
+ mFinalizer = sRegistry.registerNativeAllocation(this, mNativeCanvasWrapper);
mBitmap = bitmap;
mDensity = bitmap.mDensity;
}
@@ -154,7 +139,7 @@
throw new IllegalStateException();
}
mNativeCanvasWrapper = nativeCanvas;
- mFinalizer = new CanvasFinalizer(mNativeCanvasWrapper);
+ mFinalizer = sRegistry.registerNativeAllocation(this, mNativeCanvasWrapper);
mDensity = Bitmap.getDefaultDensity();
}
@@ -1980,7 +1965,11 @@
* @hide
*/
public void release() {
- mFinalizer.dispose();
+ mNativeCanvasWrapper = 0;
+ if (mFinalizer != null) {
+ mFinalizer.run();
+ mFinalizer = null;
+ }
}
/**
@@ -2148,5 +2137,5 @@
float hOffset,
float vOffset,
int flags, long nativePaint, long nativeTypeface);
- private static native void finalizer(long nativeCanvas);
+ private static native long getNativeFinalizer();
}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index c8c60c3..dfb8bb8 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -30,6 +30,8 @@
import java.util.HashMap;
import java.util.Locale;
+import libcore.util.NativeAllocationRegistry;
+
/**
* The Paint class holds the style and color information about how to draw
* geometries, text and bitmaps.
@@ -39,6 +41,12 @@
private long mNativePaint;
private long mNativeShader = 0;
+ // The approximate size of a native paint object.
+ private static final long NATIVE_PAINT_SIZE = 98;
+
+ private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+ nGetNativeFinalizer(), NATIVE_PAINT_SIZE);
+
/**
* @hide
*/
@@ -444,6 +452,7 @@
*/
public Paint(int flags) {
mNativePaint = nInit();
+ sRegistry.registerNativeAllocation(this, mNativePaint);
setFlags(flags | HIDDEN_DEFAULT_PAINT_FLAGS);
// TODO: Turning off hinting has undesirable side effects, we need to
// revisit hinting once we add support for subpixel positioning
@@ -462,12 +471,12 @@
*/
public Paint(Paint paint) {
mNativePaint = nInitWithPaint(paint.getNativeInstance());
+ sRegistry.registerNativeAllocation(this, mNativePaint);
setClassVariablesFrom(paint);
}
/** Restores the paint to its default settings. */
public void reset() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nReset(mNativePaint);
setFlags(HIDDEN_DEFAULT_PAINT_FLAGS);
@@ -502,8 +511,6 @@
* methods on this.
*/
public void set(Paint src) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
- if (src.mNativePaint == 0) throw new NullPointerException("Source is already finalized!");
if (this != src) {
// copy over the native settings
nSet(mNativePaint, src.mNativePaint);
@@ -554,7 +561,6 @@
* @hide
*/
public long getNativeInstance() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long newNativeShader = mShader == null ? 0 : mShader.getNativeInstance();
if (newNativeShader != mNativeShader) {
mNativeShader = newNativeShader;
@@ -592,7 +598,6 @@
* @return the paint's flags (see enums ending in _Flag for bit masks)
*/
public int getFlags() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetFlags(mNativePaint);
}
@@ -604,7 +609,6 @@
* @param flags The new flag bits for the paint
*/
public void setFlags(int flags) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetFlags(mNativePaint, flags);
}
@@ -615,7 +619,6 @@
* {@link #HINTING_OFF} or {@link #HINTING_ON}.
*/
public int getHinting() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetHinting(mNativePaint);
}
@@ -626,7 +629,6 @@
* {@link #HINTING_OFF} or {@link #HINTING_ON}.
*/
public void setHinting(int mode) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetHinting(mNativePaint, mode);
}
@@ -653,7 +655,6 @@
* @param aa true to set the antialias bit in the flags, false to clear it
*/
public void setAntiAlias(boolean aa) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetAntiAlias(mNativePaint, aa);
}
@@ -684,7 +685,6 @@
* @param dither true to set the dithering bit in flags, false to clear it
*/
public void setDither(boolean dither) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetDither(mNativePaint, dither);
}
@@ -706,7 +706,6 @@
* false to clear it.
*/
public void setLinearText(boolean linearText) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetLinearText(mNativePaint, linearText);
}
@@ -728,7 +727,6 @@
* flags, false to clear it.
*/
public void setSubpixelText(boolean subpixelText) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetSubpixelText(mNativePaint, subpixelText);
}
@@ -750,7 +748,6 @@
* flags, false to clear it.
*/
public void setUnderlineText(boolean underlineText) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetUnderlineText(mNativePaint, underlineText);
}
@@ -772,7 +769,6 @@
* flags, false to clear it.
*/
public void setStrikeThruText(boolean strikeThruText) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetStrikeThruText(mNativePaint, strikeThruText);
}
@@ -794,7 +790,6 @@
* flags, false to clear it.
*/
public void setFakeBoldText(boolean fakeBoldText) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetFakeBoldText(mNativePaint, fakeBoldText);
}
@@ -822,7 +817,6 @@
* flags, false to clear it.
*/
public void setFilterBitmap(boolean filter) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetFilterBitmap(mNativePaint, filter);
}
@@ -836,7 +830,6 @@
* @return the paint's style setting (Fill, Stroke, StrokeAndFill)
*/
public Style getStyle() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return sStyleArray[nGetStyle(mNativePaint)];
}
@@ -848,7 +841,6 @@
* @param style The new style to set in the paint
*/
public void setStyle(Style style) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetStyle(mNativePaint, style.nativeInt);
}
@@ -862,7 +854,6 @@
*/
@ColorInt
public int getColor() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetColor(mNativePaint);
}
@@ -877,7 +868,6 @@
* @param color The new color (including alpha) to set in the paint.
*/
public void setColor(@ColorInt int color) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetColor(mNativePaint, color);
}
@@ -891,7 +881,6 @@
* @return the alpha component of the paint's color.
*/
public int getAlpha() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetAlpha(mNativePaint);
}
@@ -905,7 +894,6 @@
* @param a set the alpha component [0..255] of the paint's color.
*/
public void setAlpha(int a) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetAlpha(mNativePaint, a);
}
@@ -933,7 +921,6 @@
* Stroke or StrokeAndFill.
*/
public float getStrokeWidth() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetStrokeWidth(mNativePaint);
}
@@ -948,7 +935,6 @@
* style is Stroke or StrokeAndFill.
*/
public void setStrokeWidth(float width) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetStrokeWidth(mNativePaint, width);
}
@@ -962,7 +948,6 @@
* Stroke or StrokeAndFill.
*/
public float getStrokeMiter() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetStrokeMiter(mNativePaint);
}
@@ -976,7 +961,6 @@
* style is Stroke or StrokeAndFill.
*/
public void setStrokeMiter(float miter) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetStrokeMiter(mNativePaint, miter);
}
@@ -990,7 +974,6 @@
* style is Stroke or StrokeAndFill.
*/
public Cap getStrokeCap() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return sCapArray[nGetStrokeCap(mNativePaint)];
}
@@ -1001,7 +984,6 @@
* style is Stroke or StrokeAndFill.
*/
public void setStrokeCap(Cap cap) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetStrokeCap(mNativePaint, cap.nativeInt);
}
@@ -1011,7 +993,6 @@
* @return the paint's Join.
*/
public Join getStrokeJoin() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return sJoinArray[nGetStrokeJoin(mNativePaint)];
}
@@ -1022,7 +1003,6 @@
* Stroke or StrokeAndFill.
*/
public void setStrokeJoin(Join join) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetStrokeJoin(mNativePaint, join.nativeInt);
}
@@ -1038,7 +1018,6 @@
* drawn with a hairline (width == 0)
*/
public boolean getFillPath(Path src, Path dst) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetFillPath(mNativePaint, src.ni(), dst.ni());
}
@@ -1087,7 +1066,6 @@
* @return filter
*/
public ColorFilter setColorFilter(ColorFilter filter) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long filterNative = 0;
if (filter != null)
filterNative = filter.native_instance;
@@ -1115,7 +1093,6 @@
* @return xfermode
*/
public Xfermode setXfermode(Xfermode xfermode) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long xfermodeNative = 0;
if (xfermode != null)
xfermodeNative = xfermode.native_instance;
@@ -1143,7 +1120,6 @@
* @return effect
*/
public PathEffect setPathEffect(PathEffect effect) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long effectNative = 0;
if (effect != null) {
effectNative = effect.native_instance;
@@ -1173,7 +1149,6 @@
* @return maskfilter
*/
public MaskFilter setMaskFilter(MaskFilter maskfilter) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long maskfilterNative = 0;
if (maskfilter != null) {
maskfilterNative = maskfilter.native_instance;
@@ -1205,7 +1180,6 @@
* @return typeface
*/
public Typeface setTypeface(Typeface typeface) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long typefaceNative = 0;
if (typeface != null) {
typefaceNative = typeface.native_instance;
@@ -1244,7 +1218,6 @@
*/
@Deprecated
public Rasterizer setRasterizer(Rasterizer rasterizer) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
long rasterizerNative = 0;
if (rasterizer != null) {
rasterizerNative = rasterizer.native_instance;
@@ -1267,7 +1240,6 @@
* opaque, or the alpha from the shadow color if not.
*/
public void setShadowLayer(float radius, float dx, float dy, int shadowColor) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetShadowLayer(mNativePaint, radius, dx, dy, shadowColor);
}
@@ -1285,7 +1257,6 @@
* @hide
*/
public boolean hasShadowLayer() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nHasShadowLayer(mNativePaint);
}
@@ -1298,7 +1269,6 @@
* @return the paint's Align value for drawing text.
*/
public Align getTextAlign() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return sAlignArray[nGetTextAlign(mNativePaint)];
}
@@ -1311,7 +1281,6 @@
* @param align set the paint's Align value for drawing text.
*/
public void setTextAlign(Align align) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetTextAlign(mNativePaint, align.nativeInt);
}
@@ -1345,7 +1314,6 @@
* @param locale the paint's locale value for drawing text, must not be null.
*/
public void setTextLocale(@NonNull Locale locale) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (locale == null) {
throw new IllegalArgumentException("locale cannot be null");
}
@@ -1384,7 +1352,6 @@
* @param locales the paint's locale list for drawing text, must not be null or empty.
*/
public void setTextLocales(@NonNull @Size(min=1) LocaleList locales) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (locales == null || locales.isEmpty()) {
throw new IllegalArgumentException("locales cannot be null or empty");
}
@@ -1413,7 +1380,6 @@
* @return true if elegant metrics are enabled for text drawing.
*/
public boolean isElegantTextHeight() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nIsElegantTextHeight(mNativePaint);
}
@@ -1427,7 +1393,6 @@
* @param elegant set the paint's elegant metrics flag for drawing text.
*/
public void setElegantTextHeight(boolean elegant) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetElegantTextHeight(mNativePaint, elegant);
}
@@ -1439,7 +1404,6 @@
* @return the paint's text size.
*/
public float getTextSize() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetTextSize(mNativePaint);
}
@@ -1451,7 +1415,6 @@
* @param textSize set the paint's text size.
*/
public void setTextSize(float textSize) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetTextSize(mNativePaint, textSize);
}
@@ -1464,7 +1427,6 @@
* @return the paint's scale factor in X for drawing/measuring text
*/
public float getTextScaleX() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetTextScaleX(mNativePaint);
}
@@ -1478,7 +1440,6 @@
* @param scaleX set the paint's scale in X for drawing/measuring text.
*/
public void setTextScaleX(float scaleX) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetTextScaleX(mNativePaint, scaleX);
}
@@ -1491,7 +1452,6 @@
* @return the paint's skew factor in X for drawing text.
*/
public float getTextSkewX() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetTextSkewX(mNativePaint);
}
@@ -1504,7 +1464,6 @@
* @param skewX set the paint's skew factor in X for drawing text.
*/
public void setTextSkewX(float skewX) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetTextSkewX(mNativePaint, skewX);
}
@@ -1517,7 +1476,6 @@
* @return the paint's letter-spacing for drawing text.
*/
public float getLetterSpacing() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetLetterSpacing(mNativePaint);
}
@@ -1529,7 +1487,6 @@
* @param letterSpacing set the paint's letter-spacing for drawing text.
*/
public void setLetterSpacing(float letterSpacing) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetLetterSpacing(mNativePaint, letterSpacing);
}
@@ -1551,7 +1508,6 @@
* @param settings the font feature settings string to use, may be null.
*/
public void setFontFeatureSettings(String settings) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (settings != null && settings.equals("")) {
settings = null;
}
@@ -1571,7 +1527,6 @@
* @hide
*/
public int getHyphenEdit() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetHyphenEdit(mNativePaint);
}
@@ -1584,7 +1539,6 @@
* @hide
*/
public void setHyphenEdit(int hyphen) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
nSetHyphenEdit(mNativePaint, hyphen);
}
@@ -1596,7 +1550,6 @@
* current typeface and text size.
*/
public float ascent() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nAscent(mNativePaint, mNativeTypeface);
}
@@ -1610,7 +1563,6 @@
* the current typeface and text size.
*/
public float descent() {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nDescent(mNativePaint, mNativeTypeface);
}
@@ -1657,7 +1609,6 @@
* @return the font's recommended interline spacing.
*/
public float getFontMetrics(FontMetrics metrics) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetFontMetrics(mNativePaint, mNativeTypeface, metrics);
}
@@ -1703,7 +1654,6 @@
* @return the font's interline spacing.
*/
public int getFontMetricsInt(FontMetricsInt fmi) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nGetFontMetricsInt(mNativePaint, mNativeTypeface, fmi);
}
@@ -1736,7 +1686,6 @@
* @return The width of the text
*/
public float measureText(char[] text, int index, int count) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1769,7 +1718,6 @@
* @return The width of the text
*/
public float measureText(String text, int start, int end) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1799,7 +1747,6 @@
* @return The width of the text
*/
public float measureText(String text) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1860,7 +1807,6 @@
*/
public int breakText(char[] text, int index, int count,
float maxWidth, float[] measuredWidth) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1908,7 +1854,6 @@
public int breakText(CharSequence text, int start, int end,
boolean measureForwards,
float maxWidth, float[] measuredWidth) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1957,7 +1902,6 @@
*/
public int breakText(String text, boolean measureForwards,
float maxWidth, float[] measuredWidth) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -1995,7 +1939,6 @@
*/
public int getTextWidths(char[] text, int index, int count,
float[] widths) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2079,7 +2022,6 @@
* @return the number of code units in the specified text.
*/
public int getTextWidths(String text, int start, int end, float[] widths) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2133,7 +2075,6 @@
int contextIndex, int contextCount, boolean isRtl, float[] advances,
int advancesIndex) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (chars == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2180,7 +2121,6 @@
public float getTextRunAdvances(CharSequence text, int start, int end,
int contextStart, int contextEnd, boolean isRtl, float[] advances,
int advancesIndex) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2262,7 +2202,6 @@
*/
public float getTextRunAdvances(String text, int start, int end, int contextStart,
int contextEnd, boolean isRtl, float[] advances, int advancesIndex) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2327,7 +2266,6 @@
*/
public int getTextRunCursor(char[] text, int contextStart, int contextLength,
int dir, int offset, int cursorOpt) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
int contextEnd = contextStart + contextLength;
if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
| (offset - contextStart) | (contextEnd - offset)
@@ -2415,7 +2353,6 @@
*/
public int getTextRunCursor(String text, int contextStart, int contextEnd,
int dir, int offset, int cursorOpt) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (((contextStart | contextEnd | offset | (contextEnd - contextStart)
| (offset - contextStart) | (contextEnd - offset)
| (text.length() - contextEnd) | cursorOpt) < 0)
@@ -2442,7 +2379,6 @@
*/
public void getTextPath(char[] text, int index, int count,
float x, float y, Path path) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if ((index | count) < 0 || index + count > text.length) {
throw new ArrayIndexOutOfBoundsException();
}
@@ -2465,7 +2401,6 @@
*/
public void getTextPath(String text, int start, int end,
float x, float y, Path path) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if ((start | end | (end - start) | (text.length() - end)) < 0) {
throw new IndexOutOfBoundsException();
}
@@ -2484,7 +2419,6 @@
* allocated by the caller.
*/
public void getTextBounds(String text, int start, int end, Rect bounds) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if ((start | end | (end - start) | (text.length() - end)) < 0) {
throw new IndexOutOfBoundsException();
}
@@ -2505,7 +2439,6 @@
* allocated by the caller.
*/
public void getTextBounds(char[] text, int index, int count, Rect bounds) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if ((index | count) < 0 || index + count > text.length) {
throw new ArrayIndexOutOfBoundsException();
}
@@ -2533,7 +2466,6 @@
* @return true if the typeface has a glyph for the string
*/
public boolean hasGlyph(String string) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
return nHasGlyph(mNativePaint, mNativeTypeface, mBidiFlags, string);
}
@@ -2575,7 +2507,6 @@
*/
public float getRunAdvance(char[] text, int start, int end, int contextStart, int contextEnd,
boolean isRtl, int offset) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2606,7 +2537,6 @@
*/
public float getRunAdvance(CharSequence text, int start, int end, int contextStart,
int contextEnd, boolean isRtl, int offset) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2657,7 +2587,6 @@
*/
public int getOffsetForAdvance(char[] text, int start, int end, int contextStart,
int contextEnd, boolean isRtl, float advance) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2685,7 +2614,6 @@
*/
public int getOffsetForAdvance(CharSequence text, int start, int end, int contextStart,
int contextEnd, boolean isRtl, float advance) {
- if (mNativePaint == 0) throw new NullPointerException("Already finalized!");
if (text == null) {
throw new IllegalArgumentException("text cannot be null");
}
@@ -2703,18 +2631,6 @@
return result;
}
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mNativePaint != 0) {
- nFinalizer(mNativePaint);
- mNativePaint = 0;
- }
- } finally {
- super.finalize();
- }
- }
-
private static native long nInit();
private static native long nInitWithPaint(long paint);
private static native void nReset(long paintPtr);
@@ -2770,7 +2686,7 @@
String text, int start, int end, int bidiFlags, Rect bounds);
private static native void nGetCharArrayBounds(long nativePaint, long typefacePtr,
char[] text, int index, int count, int bidiFlags, Rect bounds);
- private static native void nFinalizer(long nativePaint);
+ private static native long nGetNativeFinalizer();
private static native void nSetShadowLayer(long paintPtr,
float radius, float dx, float dy, int color);
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index e2350b6..fa7c8aa 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -233,6 +233,7 @@
tests/unit/GpuMemoryTrackerTests.cpp \
tests/unit/LayerUpdateQueueTests.cpp \
tests/unit/LinearAllocatorTests.cpp \
+ tests/unit/LeakCheckTests.cpp \
tests/unit/VectorDrawableTests.cpp \
tests/unit/OffscreenBufferPoolTests.cpp \
tests/unit/StringUtilsTests.cpp
@@ -300,9 +301,9 @@
tests/microbench/PathParserBench.cpp \
tests/microbench/ShadowBench.cpp
-# ifeq (true, $(HWUI_NEW_OPS))
-# LOCAL_SRC_FILES += \
-# tests/microbench/FrameBuilderBench.cpp
-# endif
+ifeq (true, $(HWUI_NEW_OPS))
+ LOCAL_SRC_FILES += \
+ tests/microbench/FrameBuilderBench.cpp
+endif
include $(BUILD_EXECUTABLE)
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 6b0c149..7ecc743 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -784,6 +784,7 @@
.build();
renderer.renderGlop(state, glop);
}
+ renderer.renderState().layerPool().putOrDelete(*op.layerHandle);
}
} // namespace uirenderer
diff --git a/libs/hwui/BakedOpRenderer.h b/libs/hwui/BakedOpRenderer.h
index e857f6b..10c4698 100644
--- a/libs/hwui/BakedOpRenderer.h
+++ b/libs/hwui/BakedOpRenderer.h
@@ -45,9 +45,15 @@
* Position agnostic shadow lighting info. Used with all shadow ops in scene.
*/
struct LightInfo {
- float lightRadius = 0;
- uint8_t ambientShadowAlpha = 0;
- uint8_t spotShadowAlpha = 0;
+ LightInfo() : LightInfo(0, 0, 0) {}
+ LightInfo(float lightRadius, uint8_t ambientShadowAlpha,
+ uint8_t spotShadowAlpha)
+ : lightRadius(lightRadius)
+ , ambientShadowAlpha(ambientShadowAlpha)
+ , spotShadowAlpha(spotShadowAlpha) {}
+ float lightRadius;
+ uint8_t ambientShadowAlpha;
+ uint8_t spotShadowAlpha;
};
BakedOpRenderer(Caches& caches, RenderState& renderState, bool opaque, const LightInfo& lightInfo)
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index c506bb3..ae08142 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -171,6 +171,13 @@
syncHierarchyPropertiesAndDisplayListImpl(node.get());
}
+ static std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) {
+ TestUtils::syncHierarchyPropertiesAndDisplayList(node);
+ std::vector<sp<RenderNode>> vec;
+ vec.emplace_back(node);
+ return vec;
+ }
+
typedef std::function<void(renderthread::RenderThread& thread)> RtCallback;
static void setRenderThreadCrashHandler(std::function<void()> crashHandler);
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index bded50a..b51bd2f 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -32,13 +32,6 @@
const LayerUpdateQueue sEmptyLayerUpdateQueue;
const Vector3 sLightCenter = {100, 100, 100};
-static std::vector<sp<RenderNode>> createSyncedNodeList(sp<RenderNode>& node) {
- TestUtils::syncHierarchyPropertiesAndDisplayList(node);
- std::vector<sp<RenderNode>> vec;
- vec.emplace_back(node);
- return vec;
-}
-
/**
* Virtual class implemented by each test to redirect static operation / state transitions to
* virtual methods.
@@ -139,7 +132,7 @@
canvas.drawBitmap(bitmap, 10, 10, nullptr);
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
SimpleTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex()); // 2 ops + start + end
@@ -165,7 +158,7 @@
canvas.drawPoint(50, 50, strokedPaint);
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 200), 100, 200,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
SimpleStrokeTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(1, renderer.getIndex());
@@ -180,7 +173,7 @@
canvas.restore();
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
FailRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
@@ -215,7 +208,7 @@
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
SimpleBatchingTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2 * LOOPS, renderer.getIndex())
@@ -256,7 +249,7 @@
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
ClippedMergingTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
@@ -284,7 +277,7 @@
TestUtils::drawTextToCanvas(&canvas, "Test string1", paint, 100, 100); // not clipped
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
TextMergingTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2, renderer.getIndex()) << "Expect 2 ops";
@@ -315,7 +308,7 @@
}
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 2000), 200, 2000,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
TextStrikethroughTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2 * LOOPS, renderer.getIndex())
@@ -349,7 +342,7 @@
canvas.restore();
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
TextureLayerTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(1, renderer.getIndex());
@@ -394,7 +387,7 @@
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
- createSyncedNodeList(parent), sLightCenter);
+ TestUtils::createSyncedNodeList(parent), sLightCenter);
RenderNodeTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
}
@@ -418,7 +411,7 @@
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue,
SkRect::MakeLTRB(10, 20, 30, 40), // clip to small area, should see in receiver
- 200, 200, createSyncedNodeList(node), sLightCenter);
+ 200, 200, TestUtils::createSyncedNodeList(node), sLightCenter);
ClippedTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
}
@@ -460,7 +453,7 @@
canvas.restore();
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
SaveLayerSimpleTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
@@ -532,7 +525,7 @@
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(800, 800), 800, 800,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
SaveLayerNestedTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(10, renderer.getIndex());
@@ -552,7 +545,7 @@
canvas.restore();
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
FailRenderer renderer;
// should see no ops, even within the layer, since the layer should be rejected
@@ -590,12 +583,12 @@
auto node = TestUtils::createNode(0, 0, 200, 200,
[](RenderProperties& props, RecordingCanvas& canvas) {
- canvas.saveLayerAlpha(10, 10, 190, 190, 128, SkCanvas::kMatrixClip_SaveFlag);
+ canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SkCanvas::SaveFlags)(0));
canvas.drawRect(0, 0, 200, 200, SkPaint());
canvas.restore();
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
SaveLayerUnclippedSimpleTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
@@ -649,7 +642,7 @@
canvas.restoreToCount(restoreTo);
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
SaveLayerUnclippedMergedClearsTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(10, renderer.getIndex())
@@ -711,7 +704,7 @@
canvas.restore();
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(600, 600), 600, 600,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
SaveLayerUnclippedComplexTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(12, renderer.getIndex());
@@ -762,7 +755,7 @@
OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 100, 100);
*layerHandle = &layer;
- auto syncedNodeList = createSyncedNodeList(node);
+ auto syncedNodeList = TestUtils::createSyncedNodeList(node);
// only enqueue partial damage
LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
@@ -863,7 +856,7 @@
OffscreenBuffer parentLayer(renderThread.renderState(), Caches::getInstance(), 200, 200);
*(parent->getLayerHandle()) = &parentLayer;
- auto syncedList = createSyncedNodeList(parent);
+ auto syncedList = TestUtils::createSyncedNodeList(parent);
LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(100, 100));
@@ -919,7 +912,7 @@
drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
- createSyncedNodeList(parent), sLightCenter);
+ TestUtils::createSyncedNodeList(parent), sLightCenter);
ZReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(10, renderer.getIndex());
@@ -1002,7 +995,7 @@
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 100, 100,
- createSyncedNodeList(parent), sLightCenter);
+ TestUtils::createSyncedNodeList(parent), sLightCenter);
ProjectionReorderTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(3, renderer.getIndex());
@@ -1045,7 +1038,7 @@
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
- createSyncedNodeList(parent), sLightCenter);
+ TestUtils::createSyncedNodeList(parent), sLightCenter);
ShadowTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(2, renderer.getIndex());
@@ -1086,7 +1079,7 @@
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
- createSyncedNodeList(parent), (Vector3) { 100, 100, 100 });
+ TestUtils::createSyncedNodeList(parent), (Vector3) { 100, 100, 100 });
ShadowSaveLayerTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(5, renderer.getIndex());
@@ -1132,7 +1125,7 @@
layer.setWindowTransform(windowTransform);
*layerHandle = &layer;
- auto syncedList = createSyncedNodeList(parent);
+ auto syncedList = TestUtils::createSyncedNodeList(parent);
LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
layerUpdateQueue.enqueueLayerWithDamage(parent.get(), Rect(100, 100));
FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
@@ -1165,7 +1158,7 @@
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
- createSyncedNodeList(parent), sLightCenter);
+ TestUtils::createSyncedNodeList(parent), sLightCenter);
ShadowLayeringTestRenderer renderer;
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(4, renderer.getIndex());
@@ -1193,7 +1186,7 @@
});
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(100, 100), 200, 200,
- createSyncedNodeList(node), sLightCenter);
+ TestUtils::createSyncedNodeList(node), sLightCenter);
PropertyTestRenderer renderer(opValidateCallback);
frameBuilder.replayBakedOps<TestDispatcher>(renderer);
EXPECT_EQ(1, renderer.getIndex()) << "Should have seen one op";
@@ -1332,7 +1325,7 @@
paint.setColor(SK_ColorWHITE);
canvas.drawRect(0, 0, 10000, 10000, paint);
});
- auto nodes = createSyncedNodeList(node); // sync before querying height
+ auto nodes = TestUtils::createSyncedNodeList(node); // sync before querying height
FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200, nodes, sLightCenter);
SaveLayerAlphaClipTestRenderer renderer(outObservedData);
diff --git a/libs/hwui/tests/unit/LeakCheckTests.cpp b/libs/hwui/tests/unit/LeakCheckTests.cpp
new file mode 100644
index 0000000..41e44fc
--- /dev/null
+++ b/libs/hwui/tests/unit/LeakCheckTests.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BakedOpRenderer.h"
+#include "BakedOpDispatcher.h"
+#include "FrameBuilder.h"
+#include "LayerUpdateQueue.h"
+#include "RecordingCanvas.h"
+#include "tests/common/TestUtils.h"
+
+#include <gtest/gtest.h>
+
+using namespace android;
+using namespace android::uirenderer;
+
+const LayerUpdateQueue sEmptyLayerUpdateQueue;
+const Vector3 sLightCenter = {100, 100, 100};
+
+RENDERTHREAD_TEST(LeakCheck, saveLayerUnclipped_simple) {
+ auto node = TestUtils::createNode(0, 0, 200, 200,
+ [](RenderProperties& props, RecordingCanvas& canvas) {
+ canvas.saveLayerAlpha(10, 10, 190, 190, 128, (SkCanvas::SaveFlags)(0));
+ canvas.drawRect(0, 0, 200, 200, SkPaint());
+ canvas.restore();
+ });
+ BakedOpRenderer::LightInfo lightInfo = {50.0f, 128, 128};
+ RenderState& renderState = renderThread.renderState();
+ Caches& caches = Caches::getInstance();
+
+ FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(200, 200), 200, 200,
+ TestUtils::createSyncedNodeList(node), sLightCenter);
+ BakedOpRenderer renderer(caches, renderState, true, lightInfo);
+ frameBuilder.replayBakedOps<BakedOpDispatcher>(renderer);
+}
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 974b62e..7c21893 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -51,7 +51,7 @@
* been read yet. Data should be read from the audio hardware in chunks of sizes inferior to
* the total recording buffer size.
*/
-public class AudioRecord
+public class AudioRecord implements AudioRouting
{
//---------------------------------------------------------
// Constants
@@ -392,6 +392,20 @@
}
/**
+ * A constructor which explicitly connects a Native (C++) AudioRecord. For use by
+ * the AudioRecordRoutingProxy subclass.
+ * @param nativeRecordInJavaObj A C/C++ pointer to a native AudioRecord
+ * (associated with an OpenSL ES recorder).
+ */
+ /*package*/ AudioRecord(long nativeRecordInJavaObj) {
+ mNativeRecorderInJavaObj = nativeRecordInJavaObj;
+
+ // other initialization here...
+
+ mState = STATE_INITIALIZED;
+ }
+
+ /**
* Builder class for {@link AudioRecord} objects.
* Use this class to configure and create an <code>AudioRecord</code> instance. By setting the
* recording source and audio format parameters, you indicate which of
@@ -1221,23 +1235,6 @@
return native_set_marker_pos(markerInFrames);
}
-
- //--------------------------------------------------------------------------
- // (Re)Routing Info
- //--------------------
- /**
- * Defines the interface by which applications can receive notifications of routing
- * changes for the associated {@link AudioRecord}.
- */
- public interface OnRoutingChangedListener {
- /**
- * Called when the routing of an AudioRecord changes from either and explicit or
- * policy rerouting. Use {@link #getRoutedDevice()} to retrieve the newly routed-from
- * device.
- */
- public void onRoutingChanged(AudioRecord audioRecord);
- }
-
/**
* Returns an {@link AudioDeviceInfo} identifying the current routing of this AudioRecord.
* Note: The query is only valid if the AudioRecord is currently recording. If it is not,
@@ -1258,6 +1255,89 @@
return null;
}
+ /*
+ * Call BEFORE adding a routing callback handler.
+ */
+ private void testEnableNativeRoutingCallbacks() {
+ if (mRoutingChangeListeners.size() == 0 && mNewRoutingChangeListeners.size() == 0) {
+ native_enableDeviceCallback();
+ }
+ }
+
+ /*
+ * Call AFTER removing a routing callback handler.
+ */
+ private void testDisableNativeRoutingCallbacks() {
+ if (mRoutingChangeListeners.size() == 0 && mNewRoutingChangeListeners.size() == 0) {
+ native_disableDeviceCallback();
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // >= "N" (Re)Routing Info
+ //--------------------
+ /**
+ * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
+ * {@link AudioRecord#addOnRoutingListener(AudioRouting.OnRoutingChangedListener,
+ * android.os.Handler)}
+ * by an app to receive (re)routing notifications.
+ */
+ private ArrayMap<AudioRouting.OnRoutingChangedListener, NativeNewRoutingEventHandlerDelegate>
+ mNewRoutingChangeListeners =
+ new ArrayMap<AudioRouting.OnRoutingChangedListener, NativeNewRoutingEventHandlerDelegate>();
+
+ /**
+ * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of
+ * routing changes on this AudioRecord.
+ * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+ * notifications of rerouting events.
+ * @param handler Specifies the {@link Handler} object for the thread on which to execute
+ * the callback. If <code>null</code>, the {@link Handler} associated with the main
+ * {@link Looper} will be used.
+ */
+ public void addOnRoutingListener(AudioRouting.OnRoutingChangedListener listener,
+ android.os.Handler handler) {
+ if (listener != null && !mNewRoutingChangeListeners.containsKey(listener)) {
+ synchronized (mNewRoutingChangeListeners) {
+ testEnableNativeRoutingCallbacks();
+ mNewRoutingChangeListeners.put(
+ listener, new NativeNewRoutingEventHandlerDelegate(this, listener,
+ handler != null ? handler : new Handler(mInitializationLooper)));
+ }
+ }
+ }
+
+ /**
+ * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+ * to receive rerouting notifications.
+ * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+ * to remove.
+ */
+ public void removeOnRoutingListener(AudioRouting.OnRoutingChangedListener listener) {
+ synchronized (mNewRoutingChangeListeners) {
+ if (mNewRoutingChangeListeners.containsKey(listener)) {
+ mNewRoutingChangeListeners.remove(listener);
+ testDisableNativeRoutingCallbacks();
+ }
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // Marshmallow (Re)Routing Info
+ //--------------------
+ /**
+ * Defines the interface by which applications can receive notifications of routing
+ * changes for the associated {@link AudioRecord}.
+ */
+ public interface OnRoutingChangedListener {
+ /**
+ * Called when the routing of an AudioRecord changes from either and explicit or
+ * policy rerouting. Use {@link #getRoutedDevice()} to retrieve the newly routed-from
+ * device.
+ */
+ public void onRoutingChanged(AudioRecord audioRecord);
+ }
+
/**
* The list of AudioRecord.OnRoutingChangedListener interface added (with
* {@link AudioRecord#addOnRoutingChangedListener(OnRoutingChangedListener,android.os.Handler)}
@@ -1276,13 +1356,12 @@
* the callback. If <code>null</code>, the {@link Handler} associated with the main
* {@link Looper} will be used.
*/
+ @Deprecated
public void addOnRoutingChangedListener(OnRoutingChangedListener listener,
android.os.Handler handler) {
if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
synchronized (mRoutingChangeListeners) {
- if (mRoutingChangeListeners.size() == 0) {
- native_enableDeviceCallback();
- }
+ testEnableNativeRoutingCallbacks();
mRoutingChangeListeners.put(
listener, new NativeRoutingEventHandlerDelegate(this, listener,
handler != null ? handler : new Handler(mInitializationLooper)));
@@ -1291,22 +1370,73 @@
}
/**
- * Removes an {@link OnRoutingChangedListener} which has been previously added
+ * Removes an {@link OnRoutingChangedListener} which has been previously added
* to receive rerouting notifications.
* @param listener The previously added {@link OnRoutingChangedListener} interface to remove.
*/
+ @Deprecated
public void removeOnRoutingChangedListener(OnRoutingChangedListener listener) {
synchronized (mRoutingChangeListeners) {
if (mRoutingChangeListeners.containsKey(listener)) {
mRoutingChangeListeners.remove(listener);
- if (mRoutingChangeListeners.size() == 0) {
- native_disableDeviceCallback();
- }
+ testDisableNativeRoutingCallbacks();
}
}
}
/**
+ * >= "N" Routing
+ * Helper class to handle the forwarding of native events to the appropriate listener
+ * (potentially) handled in a different thread
+ */
+ private class NativeNewRoutingEventHandlerDelegate {
+ private final Handler mHandler;
+
+ NativeNewRoutingEventHandlerDelegate(final AudioRecord record,
+ final AudioRouting.OnRoutingChangedListener listener,
+ Handler handler) {
+ // find the looper for our new event handler
+ Looper looper;
+ if (handler != null) {
+ looper = handler.getLooper();
+ } else {
+ // no given handler, use the looper the AudioRecord was created in
+ looper = mInitializationLooper;
+ }
+
+ // construct the event handler with this looper
+ if (looper != null) {
+ // implement the event handler delegate
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (record == null) {
+ return;
+ }
+ switch(msg.what) {
+ case AudioSystem.NATIVE_EVENT_ROUTING_CHANGE:
+ if (listener != null) {
+ listener.onRoutingChanged(record);
+ }
+ break;
+ default:
+ loge("Unknown native event type: " + msg.what);
+ break;
+ }
+ }
+ };
+ } else {
+ mHandler = null;
+ }
+ }
+
+ Handler getHandler() {
+ return mHandler;
+ }
+ }
+
+ /**
+ * Marshmallow Routing
* Helper class to handle the forwarding of native events to the appropriate listener
* (potentially) handled in a different thread
*/
@@ -1355,21 +1485,34 @@
return mHandler;
}
}
+
/**
* Sends device list change notification to all listeners.
*/
private void broadcastRoutingChange() {
+ AudioManager.resetAudioPortGeneration();
+ // Marshmallow Routing
Collection<NativeRoutingEventHandlerDelegate> values;
synchronized (mRoutingChangeListeners) {
values = mRoutingChangeListeners.values();
}
- AudioManager.resetAudioPortGeneration();
for(NativeRoutingEventHandlerDelegate delegate : values) {
Handler handler = delegate.getHandler();
if (handler != null) {
handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
}
}
+ // >= "N" Routing
+ Collection<NativeNewRoutingEventHandlerDelegate> newValues;
+ synchronized (mNewRoutingChangeListeners) {
+ newValues = mNewRoutingChangeListeners.values();
+ }
+ for(NativeNewRoutingEventHandlerDelegate delegate : newValues) {
+ Handler handler = delegate.getHandler();
+ if (handler != null) {
+ handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
+ }
+ }
}
/**
diff --git a/media/java/android/media/AudioRecordRoutingProxy.java b/media/java/android/media/AudioRecordRoutingProxy.java
new file mode 100644
index 0000000..b0c19e4
--- /dev/null
+++ b/media/java/android/media/AudioRecordRoutingProxy.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * An AudioRecord connected to a native (C/C++) which allows access only to routing methods.
+ */
+class AudioRecordRoutingProxy extends AudioRecord {
+ /**
+ * A constructor which explicitly connects a Native (C++) AudioRecord. For use by
+ * the AudioRecordRoutingProxy subclass.
+ * @param nativeRecordInJavaObj A C/C++ pointer to a native AudioRecord
+ * (associated with an OpenSL ES recorder).
+ */
+ public AudioRecordRoutingProxy(long nativeRecordInJavaObj) {
+ super(nativeRecordInJavaObj);
+ }
+}
diff --git a/media/java/android/media/AudioRouting.java b/media/java/android/media/AudioRouting.java
new file mode 100644
index 0000000..2161cf3
--- /dev/null
+++ b/media/java/android/media/AudioRouting.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.os.Handler;
+import android.os.Looper;
+
+/**
+ * AudioRouting defines an interface for controlling routing and routing notifications in
+ * AudioTrack and AudioRecord objects.
+ */
+public interface AudioRouting {
+ /**
+ * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
+ * the output/input to/from.
+ * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source.
+ * If deviceInfo is null, default routing is restored.
+ * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
+ * does not correspond to a valid audio device.
+ */
+ public boolean setPreferredDevice(AudioDeviceInfo deviceInfo);
+
+ /**
+ * Returns the selected output/input specified by {@link #setPreferredDevice}. Note that this
+ * is not guaranteed to correspond to the actual device being used for playback/recording.
+ */
+ public AudioDeviceInfo getPreferredDevice();
+
+ /**
+ * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+ * changes on this AudioTrack/AudioRecord.
+ * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+ * notifications of rerouting events.
+ * @param handler Specifies the {@link Handler} object for the thread on which to execute
+ * the callback. If <code>null</code>, the {@link Handler} associated with the main
+ * {@link Looper} will be used.
+ */
+ public void addOnRoutingListener(OnRoutingChangedListener listener,
+ Handler handler);
+
+ /**
+ * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+ * to receive rerouting notifications.
+ * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+ * to remove.
+ */
+ public void removeOnRoutingListener(OnRoutingChangedListener listener);
+
+ /**
+ * Defines the interface by which applications can receive notifications of routing
+ * changes for the associated {@link AudioRouting}.
+ */
+ public interface OnRoutingChangedListener {
+ public void onRoutingChanged(AudioRouting router);
+ }
+}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index a810ff1..c110ce8 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -78,7 +78,7 @@
*
* AudioTrack is not final and thus permits subclasses, but such use is not recommended.
*/
-public class AudioTrack
+public class AudioTrack implements AudioRouting
{
//---------------------------------------------------------
// Constants
@@ -318,10 +318,11 @@
// Used exclusively by native code
//--------------------
/**
+ * @hide
* Accessed by native methods: provides access to C++ AudioTrack object.
*/
@SuppressWarnings("unused")
- private long mNativeTrackInJavaObj;
+ protected long mNativeTrackInJavaObj;
/**
* Accessed by native methods: provides access to the JNI data (i.e. resources used by
* the native AudioTrack object, but not stored in it).
@@ -524,6 +525,31 @@
}
/**
+ * A constructor which explicitly connects a Native (C++) AudioTrack. For use by
+ * the AudioTrackRoutingProxy subclass.
+ * @param nativeTrackInJavaObj a C/C++ pointer to a native AudioTrack
+ * (associated with an OpenSL ES player).
+ */
+ /*package*/ AudioTrack(long nativeTrackInJavaObj) {
+ mNativeTrackInJavaObj = nativeTrackInJavaObj;
+
+ // "final"s
+ mAttributes = null;
+ mAppOps = null;
+
+ // remember which looper is associated with the AudioTrack instantiation
+ Looper looper;
+ if ((looper = Looper.myLooper()) == null) {
+ looper = Looper.getMainLooper();
+ }
+ mInitializationLooper = looper;
+
+ // other initialization...
+
+ mState = STATE_INITIALIZED;
+ }
+
+ /**
* Builder class for {@link AudioTrack} objects.
* Use this class to configure and create an <code>AudioTrack</code> instance. By setting audio
* attributes and audio format parameters, you indicate which of those vary from the default
@@ -2247,22 +2273,6 @@
}
}
- //--------------------------------------------------------------------------
- // (Re)Routing Info
- //--------------------
- /**
- * Defines the interface by which applications can receive notifications of routing
- * changes for the associated {@link AudioTrack}.
- */
- public interface OnRoutingChangedListener {
- /**
- * Called when the routing of an AudioTrack changes from either and explicit or
- * policy rerouting. Use {@link #getRoutedDevice()} to retrieve the newly routed-to
- * device.
- */
- public void onRoutingChanged(AudioTrack audioTrack);
- }
-
/**
* Returns an {@link AudioDeviceInfo} identifying the current routing of this AudioTrack.
* Note: The query is only valid if the AudioTrack is currently playing. If it is not,
@@ -2283,6 +2293,89 @@
return null;
}
+ /*
+ * Call BEFORE adding a routing callback handler.
+ */
+ private void testEnableNativeRoutingCallbacks() {
+ if (mRoutingChangeListeners.size() == 0 && mNewRoutingChangeListeners.size() == 0) {
+ native_enableDeviceCallback();
+ }
+ }
+
+ /*
+ * Call AFTER removing a routing callback handler.
+ */
+ private void testDisableNativeRoutingCallbacks() {
+ if (mRoutingChangeListeners.size() == 0 && mNewRoutingChangeListeners.size() == 0) {
+ native_disableDeviceCallback();
+ }
+ }
+
+ //--------------------------------------------------------------------------
+ // >= "N" (Re)Routing Info
+ //--------------------
+ /**
+ * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
+ * {@link AudioTrack#addOnRoutingListener(AudioRouting.OnRoutingChangedListener,
+ * android.os.Handler)}
+ * by an app to receive (re)routing notifications.
+ */
+ private ArrayMap<AudioRouting.OnRoutingChangedListener, NativeNewRoutingEventHandlerDelegate>
+ mNewRoutingChangeListeners =
+ new ArrayMap<AudioRouting.OnRoutingChangedListener, NativeNewRoutingEventHandlerDelegate>();
+
+ /**
+ * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+ * changes on this AudioTrack.
+ * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+ * notifications of rerouting events.
+ * @param handler Specifies the {@link Handler} object for the thread on which to execute
+ * the callback. If <code>null</code>, the {@link Handler} associated with the main
+ * {@link Looper} will be used.
+ */
+ public void addOnRoutingListener(AudioRouting.OnRoutingChangedListener listener,
+ Handler handler) {
+ if (listener != null && !mNewRoutingChangeListeners.containsKey(listener)) {
+ synchronized (mNewRoutingChangeListeners) {
+ testEnableNativeRoutingCallbacks();
+ mNewRoutingChangeListeners.put(
+ listener, new NativeNewRoutingEventHandlerDelegate(this, listener,
+ handler != null ? handler : new Handler(mInitializationLooper)));
+ }
+ }
+ }
+
+ /**
+ * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+ * to receive rerouting notifications.
+ * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+ * to remove.
+ */
+ public void removeOnRoutingListener(AudioRouting.OnRoutingChangedListener listener) {
+ if (mNewRoutingChangeListeners.containsKey(listener)) {
+ mNewRoutingChangeListeners.remove(listener);
+ }
+ testDisableNativeRoutingCallbacks();
+ }
+
+ //--------------------------------------------------------------------------
+ // Marshmallow (Re)Routing Info
+ //--------------------
+ /**
+ * Defines the interface by which applications can receive notifications of routing
+ * changes for the associated {@link AudioTrack}.
+ */
+ @Deprecated
+ public interface OnRoutingChangedListener {
+ /**
+ * Called when the routing of an AudioTrack changes from either and explicit or
+ * policy rerouting. Use {@link #getRoutedDevice()} to retrieve the newly routed-to
+ * device.
+ */
+ @Deprecated
+ public void onRoutingChanged(AudioTrack audioTrack);
+ }
+
/**
* The list of AudioTrack.OnRoutingChangedListener interfaces added (with
* {@link AudioTrack#addOnRoutingChangedListener(OnRoutingChangedListener, android.os.Handler)}
@@ -2301,13 +2394,12 @@
* the callback. If <code>null</code>, the {@link Handler} associated with the main
* {@link Looper} will be used.
*/
+ @Deprecated
public void addOnRoutingChangedListener(OnRoutingChangedListener listener,
android.os.Handler handler) {
if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
synchronized (mRoutingChangeListeners) {
- if (mRoutingChangeListeners.size() == 0) {
- native_enableDeviceCallback();
- }
+ testEnableNativeRoutingCallbacks();
mRoutingChangeListeners.put(
listener, new NativeRoutingEventHandlerDelegate(this, listener,
handler != null ? handler : new Handler(mInitializationLooper)));
@@ -2320,14 +2412,13 @@
* to receive rerouting notifications.
* @param listener The previously added {@link OnRoutingChangedListener} interface to remove.
*/
+ @Deprecated
public void removeOnRoutingChangedListener(OnRoutingChangedListener listener) {
synchronized (mRoutingChangeListeners) {
if (mRoutingChangeListeners.containsKey(listener)) {
mRoutingChangeListeners.remove(listener);
}
- if (mRoutingChangeListeners.size() == 0) {
- native_disableDeviceCallback();
- }
+ testDisableNativeRoutingCallbacks();
}
}
@@ -2335,17 +2426,30 @@
* Sends device list change notification to all listeners.
*/
private void broadcastRoutingChange() {
+ AudioManager.resetAudioPortGeneration();
+
+ // Marshmallow Routing
Collection<NativeRoutingEventHandlerDelegate> values;
synchronized (mRoutingChangeListeners) {
values = mRoutingChangeListeners.values();
}
- AudioManager.resetAudioPortGeneration();
for(NativeRoutingEventHandlerDelegate delegate : values) {
Handler handler = delegate.getHandler();
if (handler != null) {
handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
}
}
+ // >= "N" Routing
+ Collection<NativeNewRoutingEventHandlerDelegate> newValues;
+ synchronized (mNewRoutingChangeListeners) {
+ newValues = mNewRoutingChangeListeners.values();
+ }
+ for(NativeNewRoutingEventHandlerDelegate delegate : newValues) {
+ Handler handler = delegate.getHandler();
+ if (handler != null) {
+ handler.sendEmptyMessage(AudioSystem.NATIVE_EVENT_ROUTING_CHANGE);
+ }
+ }
}
//---------------------------------------------------------
@@ -2428,6 +2532,7 @@
}
/**
+ * Marshmallow Routing API.
* Helper class to handle the forwarding of native events to the appropriate listener
* (potentially) handled in a different thread
*/
@@ -2477,6 +2582,57 @@
}
}
+ /**
+ * Marshmallow Routing API.
+ * Helper class to handle the forwarding of native events to the appropriate listener
+ * (potentially) handled in a different thread
+ */
+ private class NativeNewRoutingEventHandlerDelegate {
+ private final Handler mHandler;
+
+ NativeNewRoutingEventHandlerDelegate(final AudioTrack track,
+ final AudioRouting.OnRoutingChangedListener listener,
+ Handler handler) {
+ // find the looper for our new event handler
+ Looper looper;
+ if (handler != null) {
+ looper = handler.getLooper();
+ } else {
+ // no given handler, use the looper the AudioTrack was created in
+ looper = mInitializationLooper;
+ }
+
+ // construct the event handler with this looper
+ if (looper != null) {
+ // implement the event handler delegate
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (track == null) {
+ return;
+ }
+ switch(msg.what) {
+ case AudioSystem.NATIVE_EVENT_ROUTING_CHANGE:
+ if (listener != null) {
+ listener.onRoutingChanged(track);
+ }
+ break;
+ default:
+ loge("Unknown native event type: " + msg.what);
+ break;
+ }
+ }
+ };
+ } else {
+ mHandler = null;
+ }
+ }
+
+ Handler getHandler() {
+ return mHandler;
+ }
+ }
+
//---------------------------------------------------------
// Java methods called from the native side
//--------------------
diff --git a/media/java/android/media/AudioTrackRoutingProxy.java b/media/java/android/media/AudioTrackRoutingProxy.java
new file mode 100644
index 0000000..9b97ae9
--- /dev/null
+++ b/media/java/android/media/AudioTrackRoutingProxy.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+/**
+ * An AudioTrack connected to a native (C/C++) which allows access only to routing methods.
+ */
+class AudioTrackRoutingProxy extends AudioTrack {
+ /**
+ * A constructor which explicitly connects a Native (C++) AudioTrack. For use by
+ * the AudioTrackRoutingProxy subclass.
+ * @param nativeTrackInJavaObj a C/C++ pointer to a native AudioTrack
+ * (associated with an OpenSL ES player).
+ */
+ public AudioTrackRoutingProxy(long nativeTrackInJavaObj) {
+ super(nativeTrackInJavaObj);
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
new file mode 100644
index 0000000..baa7a2e
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
+import static com.android.documentsui.StubProvider.ROOT_0_ID;
+import static com.android.documentsui.StubProvider.ROOT_1_ID;
+
+import android.app.Instrumentation;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.documentsui.model.RootInfo;
+
+@LargeTest
+public class SearchViewUiTest extends InstrumentationTestCase {
+
+ private static final int TIMEOUT = 5000;
+ private static final String TAG = "SearchViewUiTest";
+ private static final String TARGET_PKG = "com.android.documentsui";
+ private static final String LAUNCHER_PKG = "com.android.launcher";
+
+ private UiBot mBot;
+ private UiDevice mDevice;
+ private Context mContext;
+ private ContentResolver mResolver;
+ private DocumentsProviderHelper mDocsHelper;
+ private ContentProviderClient mClient;
+ private RootInfo mRoot_0;
+ private RootInfo mRoot_1;
+
+ private UiObject mSearchView;
+ private UiObject mSearchTextField;
+ private UiObject mDocsList;
+ private UiObject mMessageTextView;
+ private UiObject mSearchIcon;
+
+ public void setUp() throws Exception {
+ // Initialize UiDevice instance.
+ Instrumentation instrumentation = getInstrumentation();
+
+ mDevice = UiDevice.getInstance(instrumentation);
+
+ Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
+
+ // Start from the home screen.
+ mDevice.pressHome();
+ mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), TIMEOUT);
+
+ // NOTE: Must be the "target" context, else security checks in content provider will fail.
+ mContext = instrumentation.getTargetContext();
+ mResolver = mContext.getContentResolver();
+
+ mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
+ mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
+
+ // Launch app.
+ Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(intent);
+ // Wait for the app to appear.
+ mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
+ mDevice.waitForIdle();
+
+ mBot = new UiBot(mDevice, TIMEOUT);
+
+ resetStorage(); // Just incase a test failed and tearDown didn't happen.
+
+ initUiObjects();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mDevice.pressBack();
+ resetStorage();
+ mClient.release();
+ }
+
+ private void resetStorage() throws RemoteException {
+ mClient.call("clear", null, null);
+ // TODO: Would be nice to have an event to wait on here.
+ mDevice.waitForIdle();
+ }
+
+ private void initTestFiles() throws RemoteException {
+ mRoot_0 = mDocsHelper.getRoot(ROOT_0_ID);
+ mRoot_1 = mDocsHelper.getRoot(ROOT_1_ID);
+
+ mDocsHelper.createDocument(mRoot_0, "text/plain", "file10.log");
+ mDocsHelper.createDocument(mRoot_0, "image/png", "file1.png");
+ mDocsHelper.createDocument(mRoot_0, "text/csv", "file2.csv");
+
+ mDocsHelper.createDocument(mRoot_1, "text/plain", "anotherFile0.log");
+ mDocsHelper.createDocument(mRoot_1, "text/plain", "poodles.text");
+ }
+
+ private void initUiObjects() {
+ mSearchView = mBot.findSearchView();
+ mSearchTextField = mBot.findSearchViewTextField();
+ mDocsList = mBot.findDocumentsList();
+ mMessageTextView = mBot.findMessageTextView();
+ mSearchIcon = mBot.findSearchViewIcon();
+ }
+
+ public void testSearchViewExpandsOnClick() throws Exception {
+ assertTrue(mSearchIcon.exists());
+ assertFalse(mSearchTextField.exists());
+
+ mSearchView.click();
+
+ assertTrue(mSearchTextField.exists());
+ assertTrue(mSearchTextField.isFocused());
+ assertFalse(mSearchIcon.exists());
+ }
+
+ public void testSearchViewCollapsesOnBack() throws Exception {
+ assertTrue(mSearchIcon.exists());
+ assertFalse(mSearchTextField.exists());
+
+ mSearchView.click();
+
+ mDevice.pressBack();
+
+ assertTrue(mSearchIcon.exists());
+ assertFalse(mSearchTextField.exists());
+ }
+
+ public void testSearchViewClearsTextOnBack() throws Exception {
+ assertTrue(mSearchIcon.exists());
+ assertFalse(mSearchTextField.exists());
+
+ String query = "file2";
+ mSearchView.click();
+ mSearchTextField.setText(query);
+
+ assertSearchTextField(true, query);
+
+ mDevice.pressBack();
+
+ assertTrue(mSearchIcon.exists());
+ assertFalse(mSearchTextField.exists());
+ }
+
+ public void testSearchFound() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+
+ assertDefaultTestDir0();
+
+ String query = "file1";
+ mSearchView.click();
+ mSearchTextField.setText(query);
+
+ assertTrue(mDocsList.exists());
+ assertSearchTextField(true, query);
+
+ mDevice.pressEnter();
+
+ assertTrue(mDocsList.exists());
+ assertEquals(2, mDocsList.getChildCount());
+ mBot.assertHasDocuments("file1.png", "file10.log");
+ assertSearchTextField(false, query);
+ }
+
+ public void testSearchFoundClearsOnBack() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+
+ assertDefaultTestDir0();
+
+ String query = "file1";
+ mSearchView.click();
+ mSearchTextField.setText(query);
+
+ mDevice.pressEnter();
+ mDevice.pressBack();
+
+ assertDefaultTestDir0();
+ }
+
+ public void testSearchNoResults() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+
+ assertDefaultTestDir0();
+
+ String query = "chocolate";
+ mSearchView.click();
+ mSearchTextField.setText(query);
+
+ mDevice.pressEnter();
+
+ assertFalse(mDocsList.exists());
+ assertTrue(mMessageTextView.exists());
+ assertEquals(mContext.getString(R.string.empty), mMessageTextView.getText());
+ assertSearchTextField(false, query);
+ }
+
+ public void testSearchNoResultsClearsOnBack() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+
+ assertDefaultTestDir0();
+
+ String query = "chocolate";
+ mSearchView.click();
+ mSearchTextField.setText(query);
+
+ mDevice.pressEnter();
+ mDevice.pressBack();
+
+ assertDefaultTestDir0();
+ }
+
+ public void testSearchFoundClearsDirectoryChange() throws Exception {
+ initTestFiles();
+
+ mBot.openRoot(ROOT_0_ID);
+
+ assertDefaultTestDir0();
+
+ String query = "file1";
+ mSearchView.click();
+ mSearchTextField.setText(query);
+
+ mDevice.pressEnter();
+
+ mBot.openRoot(ROOT_1_ID);
+
+ // This assert is failing right now - fix will come with SearchManager refactoring
+ // assertDefaultTestDir1();
+ //
+ // mBot.openRoot(ROOT_0_ID);
+ //
+ // assertDefaultTestDir0();
+ }
+
+ private void assertDefaultTestDir0() throws UiObjectNotFoundException {
+ assertTrue(mSearchIcon.exists());
+ assertTrue(mDocsList.exists());
+ assertFalse(mSearchTextField.exists());
+ assertEquals(3, mDocsList.getChildCount());
+ mBot.assertHasDocuments("file2.csv", "file1.png", "file10.log");
+ }
+
+ private void assertDefaultTestDir1() throws UiObjectNotFoundException {
+ assertTrue(mSearchIcon.exists());
+ assertFalse(mSearchTextField.exists());
+ assertTrue(mDocsList.exists());
+ assertEquals(2, mDocsList.getChildCount());
+ mBot.assertHasDocuments("anotherFile0.log", "poodles.txt");
+ }
+
+ private void assertSearchTextField(boolean isFocused, String query)
+ throws UiObjectNotFoundException {
+ assertFalse(mSearchIcon.exists());
+ assertTrue(mSearchTextField.exists());
+ assertEquals(isFocused, mSearchTextField.isFocused());
+ assertEquals(query, mSearchTextField.getText());
+ }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index 50f4628..fb6445b 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -135,7 +135,8 @@
final RootInfo info = entry.getValue();
final RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, id);
- row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD);
+ row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD
+ | Root.FLAG_SUPPORTS_SEARCH);
row.add(Root.COLUMN_TITLE, id);
row.add(Root.COLUMN_DOCUMENT_ID, info.document.documentId);
row.add(Root.COLUMN_AVAILABLE_BYTES, info.getRemainingCapacity());
@@ -270,6 +271,29 @@
}
@Override
+ public Cursor querySearchDocuments(String rootId, String query, String[] projection)
+ throws FileNotFoundException {
+
+ StubDocument parentDocument = mRoots.get(rootId).document;
+ if (parentDocument == null || parentDocument.file.isFile()) {
+ throw new FileNotFoundException();
+ }
+
+ final MatrixCursor result = new MatrixCursor(
+ projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
+
+ for (File file : parentDocument.file.listFiles()) {
+ if (file.getName().toLowerCase().contains(query)) {
+ StubDocument document = mStorage.get(getDocumentIdForFile(file));
+ if (document != null) {
+ includeDocument(result, document);
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
throws FileNotFoundException {
final StubDocument document = mStorage.get(docId);
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index 68cdf12..c4def8f 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -187,4 +187,41 @@
mDevice.wait(Until.findObject(selector), mTimeout);
return mDevice.findObject(selector);
}
+
+ private UiObject findObject(String resourceId) {
+ final UiSelector object = new UiSelector().resourceId(resourceId);
+ return mDevice.findObject(object);
+ }
+
+ private UiObject findObject(String parentResourceId, String childResourceId) {
+ final UiSelector selector = new UiSelector()
+ .resourceId(parentResourceId)
+ .childSelector(new UiSelector().resourceId(childResourceId));
+ return mDevice.findObject(selector);
+ }
+
+ UiObject findDocumentsList() {
+ return findObject(
+ "com.android.documentsui:id/container_directory",
+ "com.android.documentsui:id/list");
+ }
+
+ UiObject findSearchView() {
+ return findObject("com.android.documentsui:id/menu_search");
+ }
+
+ UiObject findSearchViewTextField() {
+ return findObject("com.android.documentsui:id/menu_search", "android:id/search_src_text");
+ }
+
+ UiObject findSearchViewIcon() {
+ return findObject("com.android.documentsui:id/menu_search", "android:id/search_button");
+ }
+
+ UiObject findMessageTextView() {
+ return findObject(
+ "com.android.documentsui:id/container_directory",
+ "com.android.documentsui:id/message");
+ }
+
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 72df96d..fa2226d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -2,7 +2,11 @@
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
+import android.content.pm.Signature;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -138,4 +142,33 @@
return statusString;
}
+
+ /**
+ * Determine whether a package is a "system package", in which case certain things (like
+ * disabling notifications or disabling the package altogether) should be disallowed.
+ */
+ public static boolean isSystemPackage(PackageManager pm, PackageInfo pkg) {
+ if (sSystemSignature == null) {
+ sSystemSignature = new Signature[]{ getSystemSignature(pm) };
+ }
+ return sSystemSignature[0] != null && sSystemSignature[0].equals(getFirstSignature(pkg));
+ }
+
+ private static Signature[] sSystemSignature;
+
+ private static Signature getFirstSignature(PackageInfo pkg) {
+ if (pkg != null && pkg.signatures != null && pkg.signatures.length > 0) {
+ return pkg.signatures[0];
+ }
+ return null;
+ }
+
+ private static Signature getSystemSignature(PackageManager pm) {
+ try {
+ final PackageInfo sys = pm.getPackageInfo("android", PackageManager.GET_SIGNATURES);
+ return getFirstSignature(sys);
+ } catch (NameNotFoundException e) {
+ }
+ return null;
+ }
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6201fd6..3dc339d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -146,6 +146,9 @@
<!-- Access battery information -->
<uses-permission android:name="android.permission.BATTERY_STATS" />
+ <!-- DevicePolicyManager get user restrictions -->
+ <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
+
<application
android:name=".SystemUIApplication"
android:persistent="true"
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index bc18221..2ea8c9c 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -13,6 +13,7 @@
-keep class com.android.systemui.statusbar.car.CarStatusBar
-keep class com.android.systemui.statusbar.phone.PhoneStatusBar
-keep class com.android.systemui.statusbar.tv.TvStatusBar
+-keep class com.android.systemui.SystemUIFactory
-keepclassmembers class ** {
public void onBusEvent(**);
diff --git a/packages/SystemUI/res/drawable/car_ic_arrow.xml b/packages/SystemUI/res/drawable/car_ic_arrow.xml
new file mode 100644
index 0000000..9d292cc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/car_ic_arrow.xml
@@ -0,0 +1,27 @@
+<!--
+ ~ 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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48.0dp"
+ android:height="48.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M14.0,20.0l10.0,10.0 10.0,-10.0z"/>
+ <path
+ android:pathData="M0 0h48v48H0z"
+ android:fillColor="#00000000"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/car_navigation_button.xml b/packages/SystemUI/res/layout/car_navigation_button.xml
new file mode 100644
index 0000000..87c8f04
--- /dev/null
+++ b/packages/SystemUI/res/layout/car_navigation_button.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.systemui.statusbar.car.CarNavigationButton
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center">
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/car_nav_button_icon"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:layout_centerInParent="true"
+ android:animateLayoutChanges="true">
+ </com.android.keyguard.AlphaOptimizedImageButton>
+
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/car_nav_button_more_icon"
+ android:layout_height="match_parent"
+ android:layout_width="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_toRightOf="@+id/car_nav_button_icon"
+ android:animateLayoutChanges="true">
+ </com.android.keyguard.AlphaOptimizedImageButton>
+</com.android.systemui.statusbar.car.CarNavigationButton>
diff --git a/packages/SystemUI/res/values/arrays_car.xml b/packages/SystemUI/res/values/arrays_car.xml
index 230479d..8c760fc 100644
--- a/packages/SystemUI/res/values/arrays_car.xml
+++ b/packages/SystemUI/res/values/arrays_car.xml
@@ -22,7 +22,9 @@
isn't a longpress action associated with a shortcut item, put in an empty item to make
sure everything lines up.
-->
- <array name="car_shortcut_icons" />
- <array name="car_shortcut_intent_uris" />
- <array name="car_shortcut_longpress_intent_uris" />
+ <array name="car_facet_icons" />
+ <array name="car_facet_intent_uris" />
+ <array name="car_facet_longpress_intent_uris" />
+ <array name="car_facet_package_filters"/>
+ <array name="car_facet_category_filters"/>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 916e497..e98ec82 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -282,5 +282,8 @@
<!-- Whether to show the full screen user switcher. -->
<bool name="config_enableFullscreenUserSwitcher">false</bool>
+ <!-- SystemUIFactory component -->
+ <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
+
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 1a9b874..3fb5f18 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -300,7 +300,7 @@
<dimen name="swipe_helper_falsing_threshold">70dp</dimen>
<dimen name="notifications_top_padding">4dp</dimen>
-
+
<!-- Minimum distance the user has to drag down to go to the full shade. -->
<dimen name="keyguard_drag_down_min_distance">100dp</dimen>
@@ -511,7 +511,7 @@
<!-- The maximum width of the navigation bar ripples. -->
<dimen name="key_button_ripple_max_width">95dp</dimen>
-
+
<!-- Inset shadow for FakeShadowDrawable. It is used to avoid gaps between the card
and the shadow. -->
<dimen name="fake_shadow_inset">1dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c600a1f..de49677 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3,16 +3,16 @@
/**
* Copyright (c) 2009, 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
+ * 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
+ * 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
+ * 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.
*/
-->
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 19e299254..2d056bf 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -80,6 +80,8 @@
// the theme set there.
setTheme(R.style.systemui_theme);
+ SystemUIFactory.createFromConfig(this);
+
if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
new file mode 100644
index 0000000..681b39e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.ViewGroup;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarWindowManager;
+
+/**
+ * Class factory to provide customizable SystemUI components.
+ */
+public class SystemUIFactory {
+ private static final String TAG = "SystemUIFactory";
+
+ static SystemUIFactory mFactory;
+
+ public static SystemUIFactory getInstance() {
+ return mFactory;
+ }
+
+ public static void createFromConfig(Context context) {
+ final String clsName = context.getString(R.string.config_systemUIFactoryComponent);
+ if (clsName == null || clsName.length() == 0) {
+ throw new RuntimeException("No SystemUIFactory component configured");
+ }
+
+ try {
+ Class<?> cls = null;
+ cls = context.getClassLoader().loadClass(clsName);
+ mFactory = (SystemUIFactory) cls.newInstance();
+ } catch (Throwable t) {
+ Log.w(TAG, "Error creating SystemUIFactory component: " + clsName, t);
+ throw new RuntimeException(t);
+ }
+ }
+
+ public SystemUIFactory() {}
+
+ public StatusBarKeyguardViewManager createStatusBarKeyguardViewManager(Context context,
+ ViewMediatorCallback viewMediatorCallback, LockPatternUtils lockPatternUtils) {
+ return new StatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils);
+ }
+
+ public KeyguardBouncer createKeyguardBouncer(Context context, ViewMediatorCallback callback,
+ LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager,
+ ViewGroup container) {
+ return new KeyguardBouncer(context, callback, lockPatternUtils, windowManager, container);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index ed29a8f..9a00b4b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -69,6 +69,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.SystemUI;
+import com.android.systemui.SystemUIFactory;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.statusbar.phone.FingerprintUnlockController;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
@@ -591,8 +592,9 @@
updateInputRestrictedLocked();
mTrustManager.reportKeyguardShowingChanged();
- mStatusBarKeyguardViewManager = new StatusBarKeyguardViewManager(mContext,
- mViewMediatorCallback, mLockPatternUtils);
+ mStatusBarKeyguardViewManager =
+ SystemUIFactory.getInstance().createStatusBarKeyguardViewManager(mContext,
+ mViewMediatorCallback, mLockPatternUtils);
final ContentResolver cr = mContext.getContentResolver();
mDeviceInteractive = mPM.isInteractive();
@@ -1736,10 +1738,15 @@
public void onActivityDrawn() {
mHandler.sendEmptyMessage(ON_ACTIVITY_DRAWN);
}
+
public ViewMediatorCallback getViewMediatorCallback() {
return mViewMediatorCallback;
}
+ public LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print(" mSystemReady: "); pw.println(mSystemReady);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 501f052..0d9a1c4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -93,12 +93,6 @@
private RecentsTransitionHelper mTransitionHelper;
private RecentsViewTouchHandler mTouchHandler;
- private TaskStack.DockState[] mVisibleDockStates = {
- TaskStack.DockState.LEFT,
- TaskStack.DockState.TOP,
- TaskStack.DockState.RIGHT,
- TaskStack.DockState.BOTTOM,
- };
private final Interpolator mFastOutSlowInInterpolator;
private final Interpolator mFastOutLinearInInterpolator;
@@ -435,8 +429,9 @@
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
- for (int i = mVisibleDockStates.length - 1; i >= 0; i--) {
- Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay;
+ ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+ for (int i = visDockStates.size() - 1; i >= 0; i--) {
+ Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
if (d.getAlpha() > 0) {
d.draw(canvas);
}
@@ -445,8 +440,9 @@
@Override
protected boolean verifyDrawable(Drawable who) {
- for (int i = mVisibleDockStates.length - 1; i >= 0; i--) {
- Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay;
+ ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+ for (int i = visDockStates.size() - 1; i >= 0; i--) {
+ Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
if (d == who) {
return true;
}
@@ -674,7 +670,9 @@
if (newDockStates != null) {
Collections.addAll(newDockStatesSet, newDockStates);
}
- for (TaskStack.DockState dockState : mVisibleDockStates) {
+ ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
+ for (int i = visDockStates.size() - 1; i >= 0; i--) {
+ TaskStack.DockState dockState = visDockStates.get(i);
TaskStack.DockState.ViewState viewState = dockState.viewState;
if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
// This is no longer visible, so hide it
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index 0ca46a0..d8698ee 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -71,6 +71,7 @@
private DropTarget mLastDropTarget;
private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
+ private ArrayList<TaskStack.DockState> mVisibleDockStates = new ArrayList<>();
public RecentsViewTouchHandler(RecentsView rv) {
mRv = rv;
@@ -97,6 +98,13 @@
return dockStates;
}
+ /**
+ * Returns the set of visible dock states for this current drag.
+ */
+ public ArrayList<TaskStack.DockState> getVisibleDockStates() {
+ return mVisibleDockStates;
+ }
+
/** Touch preprocessing for handling below */
public boolean onInterceptTouchEvent(MotionEvent ev) {
handleTouchEvent(ev);
@@ -130,11 +138,13 @@
mTaskView.setTranslationX(x);
mTaskView.setTranslationY(y);
- if (!ssp.hasDockedTask()) {
+ mVisibleDockStates.clear();
+ if (!ssp.hasDockedTask() && mRv.getTaskStack().getTaskCount() > 1) {
// Add the dock state drop targets (these take priority)
TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
for (TaskStack.DockState dockState : dockStates) {
registerDropTargetForCurrentDrag(dockState);
+ mVisibleDockStates.add(dockState);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
index 161f873..2294d40 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java
@@ -26,6 +26,7 @@
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
/**
@@ -50,6 +51,7 @@
| FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
mLp.setTitle(WINDOW_TITLE);
+ mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index 2791dfc..67bb58a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -85,7 +85,8 @@
@Override
public void run() {
try {
- ActivityManagerNative.getDefault().moveTasksToFullscreenStack(DOCKED_STACK_ID);
+ ActivityManagerNative.getDefault().moveTasksToFullscreenStack(
+ DOCKED_STACK_ID, false /* onTop */);
} catch (RemoteException e) {
Log.w(TAG, "Failed to remove stack: " + e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 20a6e7c..52326e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -16,10 +16,11 @@
package com.android.systemui.statusbar;
-import android.annotation.IdRes;
import android.app.INotificationManager;
import android.app.Notification;
import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
@@ -28,13 +29,12 @@
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RadioButton;
-import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.TextView;
+import com.android.settingslib.Utils;
import com.android.systemui.R;
/**
@@ -123,10 +123,27 @@
final TextView topicSummary = ((TextView) row.findViewById(R.id.summary));
final TextView topicTitle = ((TextView) row.findViewById(R.id.title));
mSeekBar = (SeekBar) row.findViewById(R.id.seekbar);
- mSeekBar.setMax(4);
+ boolean systemApp = false;
+ try {
+ final PackageManager pm = BaseStatusBar.getPackageManagerForUser(
+ getContext(), sbn.getUser().getIdentifier());
+ final PackageInfo info =
+ pm.getPackageInfo(sbn.getPackageName(), PackageManager.GET_SIGNATURES);
+ systemApp = Utils.isSystemPackage(pm, info);
+ } catch (PackageManager.NameNotFoundException e) {
+ // unlikely.
+ }
+ final int minProgress = systemApp ?
+ NotificationListenerService.Ranking.IMPORTANCE_LOW
+ : NotificationListenerService.Ranking.IMPORTANCE_NONE;
+ mSeekBar.setMax(NotificationListenerService.Ranking.IMPORTANCE_MAX);
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (progress < minProgress) {
+ seekBar.setProgress(minProgress);
+ progress = minProgress;
+ }
updateTitleAndSummary(progress);
if (fromUser) {
if (appUsesTopics) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
new file mode 100644
index 0000000..3e2c4c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -0,0 +1,275 @@
+/*
+ * 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.systemui.statusbar.car;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.support.v4.util.SimpleArrayMap;
+import android.view.View;
+import android.widget.LinearLayout;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.ActivityStarter;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A controller to populate data for CarNavigationBarView and handle user interactions.
+ * <p/>
+ * Each button inside the navigation bar is defined by data in arrays_car.xml. OEMs can customize
+ * the navigation buttons by updating arrays_car.xml appropriately in an overlay.
+ */
+class CarNavigationBarController {
+
+ // Each facet of the navigation bar maps to a set of package names or categories defined in
+ // arrays_car.xml. Package names for a given facet are delimited by ";"
+ private static final String FACET_FILTER_DEMILITER = ";";
+
+ private Context mContext;
+ private CarNavigationBarView mNavBar;
+ private ActivityStarter mActivityStarter;
+
+ // Set of categories each facet will filter on.
+ private List<String[]> mFacetCategories = new ArrayList<String[]>();
+ // Set of package names each facet will filter on.
+ private List<String[]> mFacetPackages = new ArrayList<String[]>();
+
+ private SimpleArrayMap<String, Integer> mFacetCategoryMap
+ = new SimpleArrayMap<String, Integer>();
+ private SimpleArrayMap<String, Integer> mFacetPackageMap
+ = new SimpleArrayMap<String, Integer>();
+
+ private List<Intent> mIntents = new ArrayList<Intent>();
+ private List<Intent> mLongPressIntents = new ArrayList<Intent>();
+
+ private List<CarNavigationButton> mNavButtons = new ArrayList<CarNavigationButton>();
+
+ private int mCurrentFacetIndex;
+
+ public CarNavigationBarController(Context context,
+ CarNavigationBarView navBar,
+ ActivityStarter activityStarter) {
+ mContext = context;
+ mNavBar = navBar;
+ mActivityStarter = activityStarter;
+ bind();
+ }
+
+ public void taskChanged(String packageName) {
+ // If the package name belongs to a filter, then highlight appropriate button in
+ // the navigation bar.
+ if (mFacetPackageMap.containsKey(packageName)) {
+ setCurrentFacet(mFacetPackageMap.get(packageName));
+ }
+
+ // Check if the package matches any of the categories for the facets
+ String category = getPackageCategory(packageName);
+ if (category != null) {
+ setCurrentFacet(mFacetCategoryMap.get(category));
+ }
+ }
+
+ private void bind() {
+ // Read up arrays_car.xml and populate the navigation bar here.
+ Resources r = mContext.getResources();
+ TypedArray icons = r.obtainTypedArray(R.array.car_facet_icons);
+ TypedArray intents = r.obtainTypedArray(R.array.car_facet_intent_uris);
+ TypedArray longpressIntents =
+ r.obtainTypedArray(R.array.car_facet_longpress_intent_uris);
+ TypedArray facetPackageNames = r.obtainTypedArray(R.array.car_facet_package_filters);
+
+ TypedArray facetCategories = r.obtainTypedArray(R.array.car_facet_category_filters);
+
+ if (icons.length() != intents.length()
+ || icons.length() != longpressIntents.length()
+ || icons.length() != facetPackageNames.length()
+ || icons.length() != facetCategories.length()) {
+ throw new RuntimeException("car_facet array lengths do not match");
+ }
+
+ for (int i = 0; i < icons.length(); i++) {
+ Drawable icon = icons.getDrawable(i);
+ try {
+ mIntents.add(i,
+ Intent.parseUri(intents.getString(i), Intent.URI_INTENT_SCHEME));
+
+ String longpressUri = longpressIntents.getString(i);
+ boolean hasLongpress = !longpressUri.isEmpty();
+ if (hasLongpress) {
+ mLongPressIntents.add(i,
+ Intent.parseUri(longpressUri, Intent.URI_INTENT_SCHEME));
+ }
+
+ CarNavigationButton button = createNavButton(icon, i, hasLongpress);
+ mNavButtons.add(button);
+ mNavBar.addButton(button, createNavButton(icon, i, hasLongpress));
+
+ initFacetFilterMaps(i,
+ facetPackageNames.getString(i).split(FACET_FILTER_DEMILITER),
+ facetCategories.getString(i).split(FACET_FILTER_DEMILITER));
+ } catch (URISyntaxException e) {
+ throw new RuntimeException("Malformed intent uri", e);
+ }
+ }
+ }
+
+ private void initFacetFilterMaps(int id, String[] packageNames, String[] categories){
+ mFacetCategories.add(categories);
+ for (int i = 0; i < categories.length; i++) {
+ mFacetCategoryMap.put(categories[i], id);
+ }
+
+ mFacetPackages.add(packageNames);
+ for (int i = 0; i < packageNames.length; i++) {
+ mFacetPackageMap.put(packageNames[i], id);
+ }
+ }
+
+ private String getPackageCategory(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ int size = mFacetCategories.size();
+ // For each facet, check if the given package name matches one of its categories
+ for (int i = 0; i < size; i++) {
+ String[] categories = mFacetCategories.get(i);
+ for (int j = 0; j < categories.length; j++) {
+ String category = categories[j];
+ Intent intent = new Intent();
+ intent.setPackage(packageName);
+ intent.setAction(Intent.ACTION_MAIN);
+ intent.addCategory(category);
+ List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+ if (list.size() > 0) {
+ // Cache this package name into facetPackageMap, so we won't have to query
+ // all categories next time this package name shows up.
+ mFacetPackageMap.put(packageName, mFacetCategoryMap.get(category));
+ return category;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Helper method to check if a given facet has multiple packages associated with it.
+ * This can be resource defined package names or package names filtered by facet category.
+ */
+ private boolean facetHasMultiplePackages(int index) {
+ PackageManager pm = mContext.getPackageManager();
+
+ // Check if the packages defined for the filter actually exists on the device
+ String[] packages = mFacetPackages.get(index);
+ if (packages.length > 1) {
+ int count = 0;
+ for (int i = 0; i < packages.length; i++) {
+ count += pm.getLaunchIntentForPackage(packages[i]) != null ? 1 : 0;
+ if (count > 1) {
+ return true;
+ }
+ }
+ }
+
+ // If there weren't multiple packages defined for the facet, check the categories
+ // and see if they resolve to multiple package names
+ String categories[] = mFacetCategories.get(index);
+
+ int count = 0;
+ for (int i = 0; i < categories.length; i++) {
+ String category = categories[i];
+ Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_MAIN);
+ intent.addCategory(category);
+ count += pm.queryIntentActivities(intent, 0).size();
+ if (count > 1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void setCurrentFacet(int index) {
+ if (index == mCurrentFacetIndex) {
+ return;
+ }
+
+ if (mNavButtons.get(mCurrentFacetIndex) != null) {
+ mNavButtons.get(mCurrentFacetIndex)
+ .setSelected(false /* selected */, false /* showMoreIcon */);
+ }
+
+ if (mNavButtons.get(index) != null) {
+ mNavButtons.get(index).setSelected(true /* selected */,
+ facetHasMultiplePackages(index) /* showMoreIcon */);
+ }
+ mCurrentFacetIndex = index;
+ }
+
+ private CarNavigationButton createNavButton(Drawable icon, final int id,
+ boolean longClickEnabled) {
+ CarNavigationButton button = (CarNavigationButton) View.inflate(mContext,
+ R.layout.car_navigation_button, null);
+ button.setResources(icon);
+ LinearLayout.LayoutParams lp =
+ new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
+ button.setLayoutParams(lp);
+
+ button.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setCurrentFacet(id);
+ onFacetClicked(id);
+ }
+ });
+
+ if (longClickEnabled) {
+ button.setLongClickable(true);
+ button.setOnLongClickListener(new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ onFacetLongClicked(id);
+ setCurrentFacet(id);
+ return true;
+ }
+ });
+ } else {
+ button.setLongClickable(false);
+ }
+ return button;
+ }
+
+ private void startActivity(Intent intent) {
+ if (mActivityStarter != null && intent != null) {
+ mActivityStarter.startActivity(intent, true);
+ }
+ }
+
+ private void onFacetClicked(int index) {
+ // TODO: determine what data to pass to the trampoline, so it can start
+ // the default app or the lens picker.
+ startActivity(mIntents.get(index));
+ }
+
+ private void onFacetLongClicked(int index) {
+ // TODO: determine what data to pass to the trampoline, so it can start
+ // the default app or the lens picker.
+ startActivity(mLongPressIntents.get(index));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
index e2d64b04..efc3646 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java
@@ -17,36 +17,29 @@
package com.android.systemui.statusbar.car;
import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.R.color;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ActivityStarter;
import com.android.systemui.statusbar.phone.NavigationBarView;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
-import com.android.systemui.statusbar.policy.KeyButtonView;
-import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.HashMap;
/**
* A custom navigation bar for the automotive use case.
* <p>
- * The navigation bar in the automotive use case is more like a list of shortcuts, which we
- * expect to be customizable by the car OEMs. This implementation populates the nav_buttons layout
- * from resources rather than the layout file so customization would then mean updating
- * arrays_car.xml appropriately in an overlay.
+ * The navigation bar in the automotive use case is more like a list of shortcuts, rendered
+ * in a linear layout.
*/
class CarNavigationBarView extends NavigationBarView {
- private ActivityStarter mActivityStarter;
+ private LinearLayout mNavButtons;
+ private LinearLayout mLightsOutButtons;
public CarNavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -54,83 +47,13 @@
@Override
public void onFinishInflate() {
- // Read up arrays_car.xml and populate the navigation bar here.
- Context context = getContext();
- Resources r = getContext().getResources();
- TypedArray icons = r.obtainTypedArray(R.array.car_shortcut_icons);
- TypedArray intents = r.obtainTypedArray(R.array.car_shortcut_intent_uris);
- TypedArray longpressIntents =
- r.obtainTypedArray(R.array.car_shortcut_longpress_intent_uris);
-
- if (icons.length() != intents.length()) {
- throw new RuntimeException("car_shortcut_icons and car_shortcut_intents do not match");
- }
-
- LinearLayout navButtons = (LinearLayout) findViewById(R.id.nav_buttons);
- LinearLayout lightsOut = (LinearLayout) findViewById(R.id.lights_out);
-
- for (int i = 0; i < icons.length(); i++) {
- Drawable icon = icons.getDrawable(i);
-
- try {
- Intent intent = Intent.parseUri(intents.getString(i), Intent.URI_INTENT_SCHEME);
- Intent longpress = null;
- String longpressUri = longpressIntents.getString(i);
- if (!longpressUri.isEmpty()) {
- longpress = Intent.parseUri(longpressUri, Intent.URI_INTENT_SCHEME);
- }
-
- // nav_buttons and lights_out should match exactly.
- navButtons.addView(makeButton(context, icon, intent, longpress));
- lightsOut.addView(makeButton(context, icon, intent, longpress));
- } catch (URISyntaxException e) {
- throw new RuntimeException("Malformed intent uri", e);
- }
- }
+ mNavButtons = (LinearLayout) findViewById(R.id.nav_buttons);
+ mLightsOutButtons = (LinearLayout) findViewById(R.id.lights_out);
}
- private ImageButton makeButton(Context context, Drawable icon,
- final Intent intent, final Intent longpress) {
- ImageButton button = new ImageButton(context);
-
- button.setImageDrawable(icon);
- button.setScaleType(ScaleType.CENTER);
- button.setBackgroundColor(color.transparent);
- LinearLayout.LayoutParams lp =
- new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
- button.setLayoutParams(lp);
-
- button.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (mActivityStarter != null) {
- mActivityStarter.startActivity(intent, true);
- }
- }
- });
-
- // Long click handlers are optional.
- if (longpress != null) {
- button.setLongClickable(true);
- button.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (mActivityStarter != null) {
- mActivityStarter.startActivity(longpress, true);
- return true;
- }
- return false;
- }
- });
- } else {
- button.setLongClickable(false);
- }
-
- return button;
- }
-
- public void setActivityStarter(ActivityStarter activityStarter) {
- mActivityStarter = activityStarter;
+ public void addButton(CarNavigationButton button, CarNavigationButton lightsOutButton){
+ mNavButtons.addView(button);
+ mLightsOutButtons.addView(lightsOutButton);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
new file mode 100644
index 0000000..36b3a8a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationButton.java
@@ -0,0 +1,73 @@
+/*
+ * 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.systemui.statusbar.car;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import com.android.keyguard.AlphaOptimizedImageButton;
+import com.android.systemui.R;
+
+/**
+ * A wrapper view for a car navigation facet, which includes a button icon and a drop down icon.
+ */
+public class CarNavigationButton extends RelativeLayout {
+ private static final float SELECTED_ALPHA = 1;
+ private static final float UNSELECTED_ALPHA = 0.7f;
+
+ private AlphaOptimizedImageButton mIcon;
+ private AlphaOptimizedImageButton mMoreIcon;
+
+ public CarNavigationButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void onFinishInflate() {
+ super.onFinishInflate();
+ mIcon = (AlphaOptimizedImageButton) findViewById(R.id.car_nav_button_icon);
+ mIcon.setClickable(false);
+ mIcon.setScaleType(ImageView.ScaleType.CENTER);
+ mIcon.setBackgroundColor(android.R.color.transparent);
+ mIcon.setAlpha(UNSELECTED_ALPHA);
+
+ mMoreIcon = (AlphaOptimizedImageButton) findViewById(R.id.car_nav_button_more_icon);
+ mMoreIcon.setClickable(false);
+ mMoreIcon.setScaleType(ImageView.ScaleType.CENTER);
+ mMoreIcon.setBackgroundColor(android.R.color.transparent);
+ mMoreIcon.setVisibility(INVISIBLE);
+ mMoreIcon.setImageDrawable(getContext().getDrawable(R.drawable.car_ic_arrow));
+ mMoreIcon.setAlpha(UNSELECTED_ALPHA);
+ }
+
+ public void setResources(Drawable icon) {
+ mIcon.setImageDrawable(icon);
+ }
+
+ public void setSelected(boolean selected, boolean showMoreIcon) {
+ if (selected) {
+ mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : INVISIBLE);
+ mMoreIcon.setAlpha(SELECTED_ALPHA);
+ mIcon.setAlpha(SELECTED_ALPHA);
+ } else {
+ mMoreIcon.setVisibility(INVISIBLE);
+ mIcon.setAlpha(UNSELECTED_ALPHA);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 31631f8..f6f1f94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -16,30 +16,52 @@
package com.android.systemui.statusbar.car;
+import android.app.ActivityManager;
+import android.app.ITaskStackListener;
import android.content.Context;
import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.Looper;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import com.android.systemui.R;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
/**
* A status bar (and navigation bar) tailored for the automotive use case.
*/
public class CarStatusBar extends PhoneStatusBar {
+ private SystemServicesProxy mSystemServicesProxy;
+ private TaskStackListenerImpl mTaskStackListener;
+ private Handler mHandler;
+
+ private CarNavigationBarView mCarNavigationBar;
+ private CarNavigationBarController mController;
+
+ @Override
+ public void start() {
+ super.start();
+ mHandler = new Handler();
+ mTaskStackListener = new TaskStackListenerImpl(mHandler);
+ mSystemServicesProxy = new SystemServicesProxy(mContext);
+ mSystemServicesProxy.registerTaskStackListener(mTaskStackListener);
+ }
+
@Override
protected void addNavigationBar() {
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
- WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
PixelFormat.TRANSLUCENT);
lp.setTitle("CarNavigationBar");
lp.windowAnimations = 0;
@@ -51,11 +73,11 @@
if (mNavigationBarView != null) {
return;
}
-
- CarNavigationBarView carNavBar =
+ mCarNavigationBar =
(CarNavigationBarView) View.inflate(context, R.layout.car_navigation_bar, null);
- carNavBar.setActivityStarter(this);
- mNavigationBarView = carNavBar;
+ mController = new CarNavigationBarController(context, mCarNavigationBar,
+ this /* ActivityStarter*/);
+ mNavigationBarView = mCarNavigationBar;
}
@Override
@@ -63,4 +85,40 @@
// The navigation bar for a vehicle will not need to be repositioned, as it is always
// set at the bottom.
}
+
+ /**
+ * An implementation of ITaskStackListener, that listens for changes in the system task
+ * stack and notifies the navigation bar.
+ */
+ private class TaskStackListenerImpl extends ITaskStackListener.Stub implements Runnable {
+ private Handler mHandler;
+
+ public TaskStackListenerImpl(Handler handler) {
+ this.mHandler = handler;
+ }
+
+ @Override
+ public void onActivityPinned() {
+ }
+
+ @Override
+ public void onTaskStackChanged() {
+ mHandler.removeCallbacks(this);
+ mHandler.post(this);
+ }
+
+ @Override
+ public void run() {
+ ensureMainThread();
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getTopMostTask();
+ mController.taskChanged(runningTaskInfo.baseActivity.getPackageName());
+ }
+
+ private void ensureMainThread() {
+ if (!Looper.getMainLooper().isCurrentThread()) {
+ throw new RuntimeException("Must be called on the UI thread");
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 99436a1..347ba3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -41,13 +41,13 @@
*/
public class KeyguardBouncer {
- private Context mContext;
- private ViewMediatorCallback mCallback;
- private LockPatternUtils mLockPatternUtils;
- private ViewGroup mContainer;
+ protected Context mContext;
+ protected ViewMediatorCallback mCallback;
+ protected LockPatternUtils mLockPatternUtils;
+ protected ViewGroup mContainer;
private StatusBarWindowManager mWindowManager;
- private KeyguardHostView mKeyguardView;
- private ViewGroup mRoot;
+ protected KeyguardHostView mKeyguardView;
+ protected ViewGroup mRoot;
private boolean mShowingSoon;
private int mBouncerPromptReason;
private FalsingManager mFalsingManager;
@@ -134,7 +134,7 @@
public void hide(boolean destroyView) {
mFalsingManager.onBouncerHidden();
cancelShowRunnable();
- if (mKeyguardView != null) {
+ if (mKeyguardView != null) {
mKeyguardView.cancelDismissAction();
mKeyguardView.cleanUp();
}
@@ -184,13 +184,13 @@
mBouncerPromptReason = mCallback.getBouncerPromptReason();
}
- private void ensureView() {
+ protected void ensureView() {
if (mRoot == null) {
inflateView();
}
}
- private void inflateView() {
+ protected void inflateView() {
removeView();
mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
mKeyguardView = (KeyguardHostView) mRoot.findViewById(R.id.keyguard_host_view);
@@ -201,7 +201,7 @@
mRoot.setSystemUiVisibility(View.STATUS_BAR_DISABLE_HOME);
}
- private void removeView() {
+ protected void removeView() {
if (mRoot != null && mRoot.getParent() == mContainer) {
mContainer.removeView(mRoot);
mRoot = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index e5b4f4d..2db0804 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -23,6 +23,7 @@
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.VelocityTracker;
+import android.view.View;
import android.view.ViewConfiguration;
import com.android.internal.logging.MetricsLogger;
@@ -55,6 +56,7 @@
private RecentsComponent mRecentsComponent;
private Divider mDivider;
private Context mContext;
+ private NavigationBarView mNavigationBarView;
private boolean mIsVertical;
private boolean mIsRTL;
@@ -63,6 +65,7 @@
private final int mMinFlingVelocity;
private int mTouchDownX;
private int mTouchDownY;
+ private boolean mDownOnRecents;
private VelocityTracker mVelocityTracker;
private boolean mDockWindowEnabled;
@@ -79,9 +82,11 @@
TunerService.get(context).addTunable(this, KEY_DOCK_WINDOW_GESTURE);
}
- public void setComponents(RecentsComponent recentsComponent, Divider divider) {
+ public void setComponents(RecentsComponent recentsComponent, Divider divider,
+ NavigationBarView navigationBarView) {
mRecentsComponent = recentsComponent;
mDivider = divider;
+ mNavigationBarView = navigationBarView;
}
public void setBarState(boolean isVertical, boolean isRTL) {
@@ -157,6 +162,11 @@
mDockWindowTouchSlopExceeded = false;
mTouchDownX = (int) event.getX();
mTouchDownY = (int) event.getY();
+ View recentsButton = mNavigationBarView.getRecentsButton();
+ mDownOnRecents = mTouchDownX >= recentsButton.getLeft()
+ && mTouchDownX <= recentsButton.getRight()
+ && mTouchDownY >= recentsButton.getTop()
+ && mTouchDownY <= recentsButton.getBottom();
}
private boolean handleDragActionMoveEvent(MotionEvent event) {
@@ -172,8 +182,8 @@
boolean touchSlopExceeded = !mIsVertical
? yDiff > mScrollTouchSlop && yDiff > xDiff
: xDiff > mScrollTouchSlop && xDiff > yDiff;
- if (touchSlopExceeded && mDivider.getView().getWindowManagerProxy().getDockSide()
- == DOCKED_INVALID) {
+ if (mDownOnRecents && touchSlopExceeded
+ && mDivider.getView().getWindowManagerProxy().getDockSide() == DOCKED_INVALID) {
Rect initialBounds = null;
int dragMode = calculateDragMode();
int createMode = ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 55c7cb7..6c0c0ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -197,7 +197,7 @@
}
public void setComponents(RecentsComponent recentsComponent, Divider divider) {
- mGestureHelper.setComponents(recentsComponent, divider);
+ mGestureHelper.setComponents(recentsComponent, divider, this);
}
public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 5be52ca..78497ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -82,6 +82,7 @@
import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewStub;
import android.view.WindowManager;
@@ -279,9 +280,9 @@
VolumeComponent mVolumeComponent;
KeyguardUserSwitcher mKeyguardUserSwitcher;
FlashlightController mFlashlightController;
- UserSwitcherController mUserSwitcherController;
+ protected UserSwitcherController mUserSwitcherController;
NextAlarmController mNextAlarmController;
- KeyguardMonitor mKeyguardMonitor;
+ protected KeyguardMonitor mKeyguardMonitor;
BrightnessMirrorController mBrightnessMirrorController;
AccessibilityController mAccessibilityController;
FullscreenUserSwitcher mFullscreenUserSwitcher;
@@ -295,7 +296,7 @@
StatusBarWindowView mStatusBarWindow;
PhoneStatusBarView mStatusBarView;
private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
- private StatusBarWindowManager mStatusBarWindowManager;
+ protected StatusBarWindowManager mStatusBarWindowManager;
private UnlockMethodCache mUnlockMethodCache;
private DozeServiceHost mDozeServiceHost;
private boolean mWakeUpComingFromTouch;
@@ -422,8 +423,8 @@
private int mMaxKeyguardNotifications;
private ViewMediatorCallback mKeyguardViewMediatorCallback;
- private ScrimController mScrimController;
- private DozeScrimController mDozeScrimController;
+ protected ScrimController mScrimController;
+ protected DozeScrimController mDozeScrimController;
private final Runnable mAutohide = new Runnable() {
@Override
@@ -437,7 +438,7 @@
private boolean mWaitingForKeyguardExit;
private boolean mDozing;
private boolean mDozingRequested;
- private boolean mScrimSrcModeEnabled;
+ protected boolean mScrimSrcModeEnabled;
private Interpolator mLinearInterpolator = new LinearInterpolator();
private Interpolator mBackdropInterpolator = new AccelerateDecelerateInterpolator();
@@ -1072,13 +1073,13 @@
}
}
- private void startKeyguard() {
+ protected void startKeyguard() {
KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
mFingerprintUnlockController = new FingerprintUnlockController(mContext,
mStatusBarWindowManager, mDozeScrimController, keyguardViewMediator,
mScrimController, this);
mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
- mStatusBarWindow, mStatusBarWindowManager, mScrimController,
+ getBouncerContainer(), mStatusBarWindowManager, mScrimController,
mFingerprintUnlockController);
mKeyguardIndicationController.setStatusBarKeyguardViewManager(
mStatusBarKeyguardViewManager);
@@ -1096,6 +1097,10 @@
return mStatusBarWindow;
}
+ protected ViewGroup getBouncerContainer() {
+ return mStatusBarWindow;
+ }
+
public int getStatusBarHeight() {
if (mNaturalBarHeight < 0) {
final Resources res = mContext.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f14f0d4..7a05b8f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -30,6 +30,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.SystemUIFactory;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.RemoteInputController;
@@ -54,11 +55,11 @@
private static String TAG = "StatusBarKeyguardViewManager";
- private final Context mContext;
+ protected final Context mContext;
- private LockPatternUtils mLockPatternUtils;
- private ViewMediatorCallback mViewMediatorCallback;
- private PhoneStatusBar mPhoneStatusBar;
+ protected LockPatternUtils mLockPatternUtils;
+ protected ViewMediatorCallback mViewMediatorCallback;
+ protected PhoneStatusBar mPhoneStatusBar;
private ScrimController mScrimController;
private FingerprintUnlockController mFingerprintUnlockController;
@@ -67,17 +68,17 @@
private boolean mDeviceInteractive = false;
private boolean mScreenTurnedOn;
- private KeyguardBouncer mBouncer;
- private boolean mShowing;
- private boolean mOccluded;
- private boolean mRemoteInputActive;
+ protected KeyguardBouncer mBouncer;
+ protected boolean mShowing;
+ protected boolean mOccluded;
+ protected boolean mRemoteInputActive;
- private boolean mFirstUpdate = true;
- private boolean mLastShowing;
- private boolean mLastOccluded;
+ protected boolean mFirstUpdate = true;
+ protected boolean mLastShowing;
+ protected boolean mLastOccluded;
private boolean mLastBouncerShowing;
private boolean mLastBouncerDismissible;
- private boolean mLastRemoteInputActive;
+ protected boolean mLastRemoteInputActive;
private OnDismissAction mAfterKeyguardGoneAction;
private boolean mDeviceWillWakeUp;
@@ -99,8 +100,8 @@
mStatusBarWindowManager = statusBarWindowManager;
mScrimController = scrimController;
mFingerprintUnlockController = fingerprintUnlockController;
- mBouncer = new KeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils,
- mStatusBarWindowManager, container);
+ mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
+ mViewMediatorCallback, mLockPatternUtils, mStatusBarWindowManager, container);
}
/**
@@ -118,7 +119,7 @@
* Shows the notification keyguard or the bouncer depending on
* {@link KeyguardBouncer#needsFullscreenBouncer()}.
*/
- private void showBouncerOrKeyguard() {
+ protected void showBouncerOrKeyguard() {
if (mBouncer.needsFullscreenBouncer()) {
// The keyguard might be showing (already). So we need to hide it.
@@ -433,7 +434,7 @@
}
};
- private void updateStates() {
+ protected void updateStates() {
int vis = mContainer.getSystemUiVisibility();
boolean showing = mShowing;
boolean occluded = mOccluded;
@@ -451,9 +452,8 @@
}
}
- boolean navBarVisible = (!(showing && !occluded) || bouncerShowing || remoteInputActive);
- boolean lastNavBarVisible = (!(mLastShowing && !mLastOccluded) || mLastBouncerShowing
- || mLastRemoteInputActive);
+ boolean navBarVisible = isNavBarVisible();
+ boolean lastNavBarVisible = getLastNavBarVisible();
if (navBarVisible != lastNavBarVisible || mFirstUpdate) {
if (mPhoneStatusBar.getNavigationBarView() != null) {
if (navBarVisible) {
@@ -495,6 +495,20 @@
mPhoneStatusBar.onKeyguardViewManagerStatesUpdated();
}
+ /**
+ * @return Whether the navigation bar should be made visible based on the current state.
+ */
+ protected boolean isNavBarVisible() {
+ return !(mShowing && !mOccluded) || mBouncer.isShowing() || mRemoteInputActive;
+ }
+
+ /**
+ * @return Whether the navigation bar was made visible based on the last known state.
+ */
+ protected boolean getLastNavBarVisible() {
+ return !(mLastShowing && !mLastOccluded) || mLastBouncerShowing || mLastRemoteInputActive;
+ }
+
public boolean onMenuPressed() {
return mBouncer.onMenuPressed();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index ffec615..05d9626 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -298,6 +298,14 @@
return mContext.getResources().getBoolean(R.bool.config_enableFullscreenUserSwitcher);
}
+ public void logoutCurrentUser() {
+ int currentUser = ActivityManager.getCurrentUser();
+ if (currentUser != UserHandle.USER_SYSTEM) {
+ switchToUserId(UserHandle.USER_SYSTEM);
+ stopUserId(currentUser);
+ }
+ }
+
public void removeUserId(int userId) {
if (userId == UserHandle.USER_SYSTEM) {
Log.w(TAG, "User " + userId + " could not removed.");
@@ -340,6 +348,19 @@
switchToUserId(id);
}
+ public void switchTo(int userId) {
+ final int count = mUsers.size();
+ for (int i = 0; i < count; ++i) {
+ UserRecord record = mUsers.get(i);
+ if (record.info != null && record.info.id == userId) {
+ switchTo(record);
+ return;
+ }
+ }
+
+ Log.e(TAG, "Couldn't switch to user, id=" + userId);
+ }
+
private void switchToUserId(int id) {
try {
pauseRefreshUsers();
@@ -404,11 +425,7 @@
}
return;
} else if (ACTION_LOGOUT_USER.equals(intent.getAction())) {
- int currentUser = ActivityManager.getCurrentUser();
- if (currentUser != UserHandle.USER_SYSTEM) {
- switchToUserId(UserHandle.USER_SYSTEM);
- stopUserId(currentUser);
- }
+ logoutCurrentUser();
} else if (Intent.ACTION_USER_SWITCHED.equals(intent.getAction())) {
if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
mExitGuestDialog.cancel();
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index bd95892..58a0356 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -50,11 +50,13 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.IDeviceIdleController;
+import android.os.IMaintenanceActivityListener;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.Process;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -206,9 +208,13 @@
private boolean mSyncActive;
private boolean mJobsActive;
private boolean mAlarmsActive;
+ private boolean mReportedMaintenanceActivity;
public final AtomicFile mConfigFile;
+ private final RemoteCallbackList<IMaintenanceActivityListener> mMaintenanceActivityListeners =
+ new RemoteCallbackList<IMaintenanceActivityListener>();
+
/**
* Package names the system has white-listed to opt out of power save restrictions,
* except for device idle mode.
@@ -813,6 +819,7 @@
static final int MSG_REPORT_IDLE_OFF = 4;
static final int MSG_REPORT_ACTIVE = 5;
static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6;
+ static final int MSG_REPORT_MAINTENANCE_ACTIVITY = 7;
final class MyHandler extends Handler {
MyHandler(Looper looper) {
@@ -902,6 +909,21 @@
int uid = msg.arg1;
checkTempAppWhitelistTimeout(uid);
} break;
+ case MSG_REPORT_MAINTENANCE_ACTIVITY: {
+ boolean active = (msg.arg1 == 1);
+ final int size = mMaintenanceActivityListeners.beginBroadcast();
+ try {
+ for (int i = 0; i < size; i++) {
+ try {
+ mMaintenanceActivityListeners.getBroadcastItem(i)
+ .onMaintenanceActivityChanged(active);
+ } catch (RemoteException ignored) {
+ }
+ }
+ } finally {
+ mMaintenanceActivityListeners.finishBroadcast();
+ }
+ } break;
}
}
}
@@ -996,6 +1018,16 @@
DeviceIdleController.this.downloadServiceInactive();
}
+ @Override public boolean registerMaintenanceActivityListener(
+ IMaintenanceActivityListener listener) {
+ return DeviceIdleController.this.registerMaintenanceActivityListener(listener);
+ }
+
+ @Override public void unregisterMaintenanceActivityListener(
+ IMaintenanceActivityListener listener) {
+ DeviceIdleController.this.unregisterMaintenanceActivityListener(listener);
+ }
+
@Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
DeviceIdleController.this.dump(fd, pw, args);
}
@@ -1704,6 +1736,7 @@
void downloadServiceActive(IBinder token) {
synchronized (this) {
mDownloadServiceActive = token;
+ reportMaintenanceActivityIfNeededLocked();
try {
token.linkToDeath(new IBinder.DeathRecipient() {
@Override public void binderDied() {
@@ -1719,6 +1752,7 @@
void downloadServiceInactive() {
synchronized (this) {
mDownloadServiceActive = null;
+ reportMaintenanceActivityIfNeededLocked();
exitMaintenanceEarlyIfNeededLocked();
}
}
@@ -1726,6 +1760,7 @@
void setSyncActive(boolean active) {
synchronized (this) {
mSyncActive = active;
+ reportMaintenanceActivityIfNeededLocked();
if (!active) {
exitMaintenanceEarlyIfNeededLocked();
}
@@ -1735,6 +1770,7 @@
void setJobsActive(boolean active) {
synchronized (this) {
mJobsActive = active;
+ reportMaintenanceActivityIfNeededLocked();
if (!active) {
exitMaintenanceEarlyIfNeededLocked();
}
@@ -1750,6 +1786,30 @@
}
}
+ boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener) {
+ synchronized (this) {
+ mMaintenanceActivityListeners.register(listener);
+ return mReportedMaintenanceActivity;
+ }
+ }
+
+ void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener) {
+ synchronized (this) {
+ mMaintenanceActivityListeners.unregister(listener);
+ }
+ }
+
+ void reportMaintenanceActivityIfNeededLocked() {
+ boolean active = mJobsActive | mSyncActive | (mDownloadServiceActive != null);
+ if (active == mReportedMaintenanceActivity) {
+ return;
+ }
+ mReportedMaintenanceActivity = active;
+ Message msg = mHandler.obtainMessage(MSG_REPORT_MAINTENANCE_ACTIVITY,
+ mReportedMaintenanceActivity ? 1 : 0, 0);
+ mHandler.sendMessage(msg);
+ }
+
void exitMaintenanceEarlyIfNeededLocked() {
if (mState == STATE_IDLE_MAINTENANCE || mLightState == LIGHT_STATE_IDLE_MAINTENANCE) {
if (mActiveIdleOpCount <= 0 && mDownloadServiceActive == null
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ca38b71..0f4b68e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18166,7 +18166,7 @@
}
@Override
- public void moveTasksToFullscreenStack(int fromStackId) {
+ public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTasksToFullscreenStack()");
if (fromStackId == HOME_STACK_ID) {
throw new IllegalArgumentException("You can't move tasks from the home stack.");
@@ -18176,9 +18176,18 @@
final ActivityStack stack = mStackSupervisor.getStack(fromStackId);
if (stack != null) {
final ArrayList<TaskRecord> tasks = stack.getAllTasks();
- for (int i = tasks.size() - 1; i >= 0; i--) {
- mStackSupervisor.positionTaskInStackLocked(tasks.get(i).taskId,
- FULLSCREEN_WORKSPACE_STACK_ID, 0);
+ final int size = tasks.size();
+ if (onTop) {
+ for (int i = 0; i < size; i++) {
+ mStackSupervisor.moveTaskToStackLocked(tasks.get(i).taskId,
+ FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, !FORCE_FOCUS,
+ "moveTasksToFullscreenStack", ANIMATE);
+ }
+ } else {
+ for (int i = size - 1; i >= 0; i--) {
+ mStackSupervisor.positionTaskInStackLocked(tasks.get(i).taskId,
+ FULLSCREEN_WORKSPACE_STACK_ID, 0);
+ }
}
}
Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index 92df851..fe5e8c9 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -34,14 +34,13 @@
public class IdleController extends StateController {
private static final String TAG = "IdleController";
- // Policy: we decide that we're "idle" if the device has been unused /
- // screen off or dreaming for at least this long
- private static final long INACTIVITY_IDLE_THRESHOLD = 71 * 60 * 1000; // millis; 71 min
- private static final long IDLE_WINDOW_SLOP = 5 * 60 * 1000; // 5 minute window, to be nice
-
private static final String ACTION_TRIGGER_IDLE =
"com.android.server.task.controllers.IdleController.ACTION_TRIGGER_IDLE";
+ // Policy: we decide that we're "idle" if the device has been unused /
+ // screen off or dreaming for at least this long
+ private long mInactivityIdleThreshold;
+ private long mIdleWindowSlop;
final ArrayList<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
IdlenessTracker mIdleTracker;
@@ -100,6 +99,10 @@
* significant state changes occur
*/
private void initIdleStateTracking() {
+ mInactivityIdleThreshold = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold);
+ mIdleWindowSlop = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop);
mIdleTracker = new IdlenessTracker();
mIdleTracker.startTracking();
}
@@ -168,14 +171,14 @@
// alarm that will tell us when we have decided the device is
// truly idle.
final long nowElapsed = SystemClock.elapsedRealtime();
- final long when = nowElapsed + INACTIVITY_IDLE_THRESHOLD;
+ final long when = nowElapsed + mInactivityIdleThreshold;
if (DEBUG) {
Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when="
+ when);
}
mScreenOn = false;
mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- when, IDLE_WINDOW_SLOP, mIdleTriggerIntent);
+ when, mIdleWindowSlop, mIdleTriggerIntent);
} else if (action.equals(ACTION_TRIGGER_IDLE)) {
// idle time starts now. Do not set mIdle if screen is on.
if (!mIdle && !mScreenOn) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 018bf2d..b1fe68c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1727,6 +1727,14 @@
}
@Override
+ public int getRuleInstanceCount(ComponentName owner) throws RemoteException {
+ Preconditions.checkNotNull(owner, "Owner is null");
+ enforceSystemOrSystemUI("getRuleInstanceCount");
+
+ return mZenModeHelper.getCurrentInstanceCount(owner);
+ }
+
+ @Override
public void setInterruptionFilter(String pkg, int filter) throws RemoteException {
enforcePolicyAccess(pkg, "setInterruptionFilter");
final int zen = NotificationManager.zenModeFromInterruptionFilter(filter, -1);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f7043a6..1d91fb7 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -27,7 +27,10 @@
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
@@ -45,7 +48,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings.Global;
-import android.service.notification.IConditionListener;
+import android.service.notification.ConditionProviderService;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.EventInfo;
import android.service.notification.ZenModeConfig.ScheduleInfo;
@@ -91,6 +94,7 @@
private final ZenModeConditions mConditions;
private final SparseArray<ZenModeConfig> mConfigs = new SparseArray<>();
private final Metrics mMetrics = new Metrics();
+ private final ConditionProviders.Config mServiceConfig;
private int mZenMode;
private int mUser = UserHandle.USER_SYSTEM;
@@ -113,6 +117,7 @@
mSettingsObserver.observe();
mFiltering = new ZenModeFiltering(mContext);
mConditions = new ZenModeConditions(this, conditionProviders);
+ mServiceConfig = conditionProviders.getConfig();
}
public Looper getLooper() {
@@ -197,7 +202,7 @@
config.user = user;
}
synchronized (mConfig) {
- setConfig(config, "onUserSwitched");
+ setConfigLocked(config, "onUserSwitched");
}
cleanUpZenRules();
}
@@ -257,22 +262,34 @@
}
public AutomaticZenRule addAutomaticZenRule(AutomaticZenRule automaticZenRule, String reason) {
+ if (!TextUtils.isEmpty(automaticZenRule.getId())) {
+ throw new IllegalArgumentException("Rule already exists");
+ }
+ if (!isSystemRule(automaticZenRule)) {
+ ServiceInfo owner = getServiceInfo(automaticZenRule.getOwner());
+ if (owner == null) {
+ throw new IllegalArgumentException("Owner is not a condition provider service");
+ }
+
+ final int ruleInstanceLimit = owner.metaData.getInt(
+ ConditionProviderService.META_DATA_RULE_INSTANCE_LIMIT, -1);
+ if (ruleInstanceLimit > 0 && ruleInstanceLimit
+ < (getCurrentInstanceCount(automaticZenRule.getOwner()) + 1)) {
+ throw new IllegalArgumentException("Rule instance limit exceeded");
+ }
+ }
+
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");
+ Log.d(TAG, "addAutomaticZenRule rule= " + automaticZenRule + " reason=" + reason);
}
newConfig = mConfig.copy();
-
ZenRule rule = new ZenRule();
populateZenRule(automaticZenRule, rule, true);
newConfig.automaticRules.put(rule.id, rule);
- if (setConfig(newConfig, reason, true)) {
+ if (setConfigLocked(newConfig, reason, true)) {
return createAutomaticZenRule(rule);
} else {
return null;
@@ -302,7 +319,7 @@
}
populateZenRule(automaticZenRule, rule, false);
newConfig.automaticRules.put(ruleId, rule);
- return setConfig(newConfig, reason, true);
+ return setConfigLocked(newConfig, reason, true);
}
}
@@ -320,7 +337,7 @@
throw new SecurityException(
"Cannot delete rules not owned by your condition provider");
}
- return setConfig(newConfig, reason, true);
+ return setConfigLocked(newConfig, reason, true);
}
}
@@ -336,10 +353,22 @@
newConfig.automaticRules.removeAt(i);
}
}
- return setConfig(newConfig, reason, true);
+ return setConfigLocked(newConfig, reason, true);
}
}
+ public int getCurrentInstanceCount(ComponentName owner) {
+ int count = 0;
+ synchronized (mConfig) {
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (rule.component != null && rule.component.equals(owner)) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
public boolean canManageAutomaticZenRule(ZenRule rule) {
final int callingUid = Binder.getCallingUid();
if (callingUid == 0 || callingUid == Process.SYSTEM_UID) {
@@ -361,6 +390,29 @@
}
}
+ private boolean isSystemRule(AutomaticZenRule rule) {
+ return ZenModeConfig.SYSTEM_AUTHORITY.equals(rule.getOwner().getPackageName());
+ }
+
+ private ServiceInfo getServiceInfo(ComponentName owner) {
+ Intent queryIntent = new Intent();
+ queryIntent.setComponent(owner);
+ List<ResolveInfo> installedServices = mPm.queryIntentServicesAsUser(
+ queryIntent,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+ UserHandle.getCallingUserId());
+ if (installedServices != null) {
+ for (int i = 0, count = installedServices.size(); i < count; i++) {
+ ResolveInfo resolveInfo = installedServices.get(i);
+ ServiceInfo info = resolveInfo.serviceInfo;
+ if (mServiceConfig.bindPermission.equals(info.permission)) {
+ return info;
+ }
+ }
+ }
+ return null;
+ }
+
private void populateZenRule(AutomaticZenRule automaticZenRule, ZenRule rule, boolean isNew) {
if (isNew) {
rule.id = ZenModeConfig.newRuleId();
@@ -413,7 +465,7 @@
newRule.conditionId = conditionId;
newConfig.manualRule = newRule;
}
- setConfig(newConfig, reason, setRingerMode);
+ setConfigLocked(newConfig, reason, setRingerMode);
}
}
@@ -478,7 +530,7 @@
}
if (DEBUG) Log.d(TAG, "readXml");
synchronized (mConfig) {
- setConfig(config, "readXml");
+ setConfigLocked(config, "readXml");
}
}
}
@@ -507,7 +559,7 @@
synchronized (mConfig) {
final ZenModeConfig newConfig = mConfig.copy();
newConfig.applyNotificationPolicy(policy);
- setConfig(newConfig, "setNotificationPolicy");
+ setConfigLocked(newConfig, "setNotificationPolicy");
}
}
@@ -530,7 +582,7 @@
}
}
}
- setConfig(newConfig, "cleanUpZenRules");
+ setConfigLocked(newConfig, "cleanUpZenRules");
}
}
@@ -543,30 +595,30 @@
}
}
- public boolean setConfig(ZenModeConfig config, String reason) {
- return setConfig(config, reason, true /*setRingerMode*/);
+ public boolean setConfigLocked(ZenModeConfig config, String reason) {
+ return setConfigLocked(config, reason, true /*setRingerMode*/);
}
public void setConfigAsync(ZenModeConfig config, String reason) {
mHandler.postSetConfig(config, reason);
}
- private boolean setConfig(ZenModeConfig config, String reason, boolean setRingerMode) {
+ private boolean setConfigLocked(ZenModeConfig config, String reason, boolean setRingerMode) {
final long identity = Binder.clearCallingIdentity();
try {
if (config == null || !config.isValid()) {
- Log.w(TAG, "Invalid config in setConfig; " + config);
+ Log.w(TAG, "Invalid config in setConfigLocked; " + config);
return false;
}
if (config.user != mUser) {
// simply store away for background users
mConfigs.put(config.user, config);
- if (DEBUG) Log.d(TAG, "setConfig: store config for user " + config.user);
+ if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user);
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());
+ if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
ZenLog.traceConfig(reason, mConfig, config);
final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
getNotificationPolicy(config));
@@ -1041,7 +1093,7 @@
case MSG_SET_CONFIG:
ConfigMessageData configData = (ConfigMessageData)msg.obj;
synchronized (mConfig) {
- setConfig(configData.config, configData.reason);
+ setConfigLocked(configData.config, configData.reason);
}
break;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c1cba97..9f571e3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -76,7 +76,6 @@
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
-
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
@@ -227,6 +226,7 @@
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
import com.android.server.EventLogTags;
import com.android.server.FgThread;
import com.android.server.IntentResolver;
@@ -978,6 +978,30 @@
private static final String TAG_DEFAULT_APPS = "da";
private static final String TAG_INTENT_FILTER_VERIFICATION = "iv";
+ private static final String TAG_PERMISSION_BACKUP = "perm-grant-backup";
+ private static final String TAG_ALL_GRANTS = "rt-grants";
+ private static final String TAG_GRANT = "grant";
+ private static final String ATTR_PACKAGE_NAME = "pkg";
+
+ private static final String TAG_PERMISSION = "perm";
+ private static final String ATTR_PERMISSION_NAME = "name";
+ private static final String ATTR_IS_GRANTED = "g";
+ private static final String ATTR_USER_SET = "set";
+ private static final String ATTR_USER_FIXED = "fixed";
+ private static final String ATTR_REVOKE_ON_UPGRADE = "rou";
+
+ // System/policy permission grants are not backed up
+ private static final int SYSTEM_RUNTIME_GRANT_MASK =
+ FLAG_PERMISSION_POLICY_FIXED
+ | FLAG_PERMISSION_SYSTEM_FIXED
+ | FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+
+ // And we back up these user-adjusted states
+ private static final int USER_RUNTIME_GRANT_MASK =
+ FLAG_PERMISSION_USER_SET
+ | FLAG_PERMISSION_USER_FIXED
+ | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+
final @Nullable String mRequiredVerifierPackage;
final @Nullable String mRequiredInstallerPackage;
@@ -1490,16 +1514,25 @@
deleteOld = true;
}
- // If this app is a browser and it's newly-installed for some
- // users, clear any default-browser state in those users
+
+ // Work that needs to happen on first install within each user
if (firstUsers.length > 0) {
- // the app's nature doesn't depend on the user, so we can just
- // check its browser nature in any user and generalize.
- if (packageIsBrowser(packageName, firstUsers[0])) {
+ for (int userId : firstUsers) {
synchronized (mPackages) {
- for (int userId : firstUsers) {
- mSettings.setDefaultBrowserPackageNameLPw(null, userId);
+ // If this app is a browser and it's newly-installed for
+ // some users, clear any default-browser state in those
+ // users. The app's nature doesn't depend on the user,
+ // so we can just check its browser nature in any user
+ // and generalize.
+ if (packageIsBrowser(packageName, firstUsers[0])) {
+ mSettings.setDefaultBrowserPackageNameLPw(
+ null, userId);
}
+
+ // We may also need to apply pending (restored) runtime
+ // permission grants within these users.
+ mSettings.applyPendingPermissionGrantsLPw(
+ packageName, userId);
}
}
}
@@ -14823,7 +14856,7 @@
}
return;
}
-
+Slog.v(TAG, ":: restoreFromXml() : got to tag " + parser.getName());
// this is supposed to be TAG_PREFERRED_BACKUP
if (!expectedStartTag.equals(parser.getName())) {
if (DEBUG_BACKUP) {
@@ -14834,6 +14867,7 @@
// skip interfering stuff, then we're aligned with the backing implementation
while ((type = parser.next()) == XmlPullParser.TEXT) { }
+Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
functor.apply(parser, userId);
}
@@ -15021,6 +15055,192 @@
}
@Override
+ public byte[] getPermissionGrantBackup(int userId) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system may call getPermissionGrantBackup()");
+ }
+
+ ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+ try {
+ final XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(dataStream, StandardCharsets.UTF_8.name());
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_PERMISSION_BACKUP);
+
+ synchronized (mPackages) {
+ serializeRuntimePermissionGrantsLPr(serializer, userId);
+ }
+
+ serializer.endTag(null, TAG_PERMISSION_BACKUP);
+ serializer.endDocument();
+ serializer.flush();
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Unable to write default apps for backup", e);
+ }
+ return null;
+ }
+
+ return dataStream.toByteArray();
+ }
+
+ @Override
+ public void restorePermissionGrants(byte[] backup, int userId) {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Only the system may call restorePermissionGrants()");
+ }
+
+ try {
+ final XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new ByteArrayInputStream(backup), StandardCharsets.UTF_8.name());
+ restoreFromXml(parser, userId, TAG_PERMISSION_BACKUP,
+ new BlobXmlRestorer() {
+ @Override
+ public void apply(XmlPullParser parser, int userId)
+ throws XmlPullParserException, IOException {
+ synchronized (mPackages) {
+ processRestoredPermissionGrantsLPr(parser, userId);
+ }
+ }
+ } );
+ } catch (Exception e) {
+ if (DEBUG_BACKUP) {
+ Slog.e(TAG, "Exception restoring preferred activities: " + e.getMessage());
+ }
+ }
+ }
+
+ private void serializeRuntimePermissionGrantsLPr(XmlSerializer serializer, final int userId)
+ throws IOException {
+ serializer.startTag(null, TAG_ALL_GRANTS);
+
+ final int N = mSettings.mPackages.size();
+ for (int i = 0; i < N; i++) {
+ final PackageSetting ps = mSettings.mPackages.valueAt(i);
+ boolean pkgGrantsKnown = false;
+
+ PermissionsState packagePerms = ps.getPermissionsState();
+
+ for (PermissionState state : packagePerms.getRuntimePermissionStates(userId)) {
+ final int grantFlags = state.getFlags();
+ // only look at grants that are not system/policy fixed
+ if ((grantFlags & SYSTEM_RUNTIME_GRANT_MASK) == 0) {
+ final boolean isGranted = state.isGranted();
+ // And only back up the user-twiddled state bits
+ if (isGranted || (grantFlags & USER_RUNTIME_GRANT_MASK) != 0) {
+ final String packageName = mSettings.mPackages.keyAt(i);
+ if (!pkgGrantsKnown) {
+ serializer.startTag(null, TAG_GRANT);
+ serializer.attribute(null, ATTR_PACKAGE_NAME, packageName);
+ pkgGrantsKnown = true;
+ }
+
+ final boolean userSet =
+ (grantFlags & FLAG_PERMISSION_USER_SET) != 0;
+ final boolean userFixed =
+ (grantFlags & FLAG_PERMISSION_USER_FIXED) != 0;
+ final boolean revoke =
+ (grantFlags & FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0;
+
+ serializer.startTag(null, TAG_PERMISSION);
+ serializer.attribute(null, ATTR_PERMISSION_NAME, state.getName());
+ if (isGranted) {
+ serializer.attribute(null, ATTR_IS_GRANTED, "true");
+ }
+ if (userSet) {
+ serializer.attribute(null, ATTR_USER_SET, "true");
+ }
+ if (userFixed) {
+ serializer.attribute(null, ATTR_USER_FIXED, "true");
+ }
+ if (revoke) {
+ serializer.attribute(null, ATTR_REVOKE_ON_UPGRADE, "true");
+ }
+ serializer.endTag(null, TAG_PERMISSION);
+ }
+ }
+ }
+
+ if (pkgGrantsKnown) {
+ serializer.endTag(null, TAG_GRANT);
+ }
+ }
+
+ serializer.endTag(null, TAG_ALL_GRANTS);
+ }
+
+ private void processRestoredPermissionGrantsLPr(XmlPullParser parser, int userId)
+ throws XmlPullParserException, IOException {
+ String pkgName = null;
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ final String tagName = parser.getName();
+ if (tagName.equals(TAG_GRANT)) {
+ pkgName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, "+++ Restoring grants for package " + pkgName);
+ }
+ } else if (tagName.equals(TAG_PERMISSION)) {
+
+ final boolean isGranted = "true".equals(parser.getAttributeValue(null, ATTR_IS_GRANTED));
+ final String permName = parser.getAttributeValue(null, ATTR_PERMISSION_NAME);
+
+ int newFlagSet = 0;
+ if ("true".equals(parser.getAttributeValue(null, ATTR_USER_SET))) {
+ newFlagSet |= FLAG_PERMISSION_USER_SET;
+ }
+ if ("true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED))) {
+ newFlagSet |= FLAG_PERMISSION_USER_FIXED;
+ }
+ if ("true".equals(parser.getAttributeValue(null, ATTR_REVOKE_ON_UPGRADE))) {
+ newFlagSet |= FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+ }
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, " + Restoring grant: pkg=" + pkgName + " perm=" + permName
+ + " granted=" + isGranted + " bits=0x" + Integer.toHexString(newFlagSet));
+ }
+ final PackageSetting ps = mSettings.mPackages.get(pkgName);
+ if (ps != null) {
+ // Already installed so we apply the grant immediately
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, " + already installed; applying");
+ }
+ PermissionsState perms = ps.getPermissionsState();
+ BasePermission bp = mSettings.mPermissions.get(permName);
+ if (bp != null) {
+ if (isGranted) {
+ perms.grantRuntimePermission(bp, userId);
+ }
+ if (newFlagSet != 0) {
+ perms.updatePermissionFlags(bp, userId, USER_RUNTIME_GRANT_MASK, newFlagSet);
+ }
+ }
+ } else {
+ // Need to wait for post-restore install to apply the grant
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG, " - not yet installed; saving for later");
+ }
+ mSettings.processRestoredPermissionGrantLPr(pkgName, permName,
+ isGranted, newFlagSet, userId);
+ }
+ } else {
+ PackageManagerService.reportSettingsProblem(Log.WARN,
+ "Unknown element under <" + TAG_PERMISSION_BACKUP + ">: " + tagName);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ scheduleWriteSettingsLocked();
+ mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+ }
+
+ @Override
public void addCrossProfileIntentFilter(IntentFilter intentFilter, String ownerPackage,
int sourceUserId, int targetUserId, int flags) {
mContext.enforceCallingOrSelfPermission(
@@ -16032,6 +16252,10 @@
mSettings.dumpSharedUsersLPr(pw, packageName, permissionNames, dumpState, checkin);
}
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS) && packageName == null) {
+ mSettings.dumpRestoredPermissionGrantsLPr(pw, dumpState);
+ }
+
if (!checkin && dumpState.isDumping(DumpState.DUMP_INSTALLS) && packageName == null) {
// XXX should handle packageName != null by dumping only install data that
// the given package is involved with.
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9fef515..ad28685 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -22,6 +22,9 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
@@ -32,7 +35,6 @@
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.os.Process.PACKAGE_INFO_GID;
import static android.os.Process.SYSTEM_UID;
-
import static com.android.server.pm.PackageManagerService.DEBUG_DOMAIN_VERIFICATION;
import android.annotation.NonNull;
@@ -217,6 +219,22 @@
private static final String ATTR_SDK_VERSION = "sdkVersion";
private static final String ATTR_DATABASE_VERSION = "databaseVersion";
+ // Bookkeeping for restored permission grants
+ private static final String TAG_RESTORED_RUNTIME_PERMISSIONS = "restored-perms";
+ // package name: ATTR_PACKAGE_NAME
+ private static final String TAG_PERMISSION_ENTRY = "perm";
+ // permission name: ATTR_NAME
+ // permission granted (boolean): ATTR_GRANTED
+ private static final String ATTR_USER_SET = "set";
+ private static final String ATTR_USER_FIXED = "fixed";
+ private static final String ATTR_REVOKE_ON_UPGRADE = "rou";
+
+ // Flag mask of restored permission grants that are applied at install time
+ private static final int USER_RUNTIME_GRANT_MASK =
+ FLAG_PERMISSION_USER_SET
+ | FLAG_PERMISSION_USER_FIXED
+ | FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+
private final Object mLock;
private final RuntimePermissionPersistence mRuntimePermissionsPersistence;
@@ -238,6 +256,26 @@
private final ArrayMap<String, IntentFilterVerificationInfo> mRestoredIntentFilterVerifications =
new ArrayMap<String, IntentFilterVerificationInfo>();
+ // Bookkeeping for restored user permission grants
+ final class RestoredPermissionGrant {
+ String permissionName;
+ boolean granted;
+ int grantBits;
+
+ RestoredPermissionGrant(String name, boolean isGranted, int theGrantBits) {
+ permissionName = name;
+ granted = isGranted;
+ grantBits = theGrantBits;
+ }
+ }
+
+ // This would be more compact as a flat array of restored grants or something, but we
+ // may have quite a few, especially during early device lifetime, and avoiding all those
+ // linear lookups will be important.
+ private final SparseArray<ArrayMap<String, ArraySet<RestoredPermissionGrant>>>
+ mRestoredUserGrants =
+ new SparseArray<ArrayMap<String, ArraySet<RestoredPermissionGrant>>>();
+
private static int mFirstAvailableUid = 0;
/** Map from volume UUID to {@link VersionInfo} */
@@ -388,7 +426,7 @@
return mPackages.get(name);
}
- void setInstallStatus(String pkgName, int status) {
+ void setInstallStatus(String pkgName, final int status) {
PackageSetting p = mPackages.get(pkgName);
if(p != null) {
if(p.getInstallStatus() != status) {
@@ -397,6 +435,43 @@
}
}
+ void applyPendingPermissionGrantsLPw(String packageName, int userId) {
+ ArrayMap<String, ArraySet<RestoredPermissionGrant>> grantsByPackage =
+ mRestoredUserGrants.get(userId);
+ if (grantsByPackage == null || grantsByPackage.size() == 0) {
+ return;
+ }
+
+ ArraySet<RestoredPermissionGrant> grants = grantsByPackage.get(packageName);
+ if (grants == null || grants.size() == 0) {
+ return;
+ }
+
+ final PackageSetting ps = mPackages.get(packageName);
+ if (ps == null) {
+ Slog.e(TAG, "Can't find supposedly installed package " + packageName);
+ return;
+ }
+ final PermissionsState perms = ps.getPermissionsState();
+
+ for (RestoredPermissionGrant grant : grants) {
+ BasePermission bp = mPermissions.get(grant.permissionName);
+ if (bp != null) {
+ if (grant.granted) {
+ perms.grantRuntimePermission(bp, userId);
+ }
+ perms.updatePermissionFlags(bp, userId, USER_RUNTIME_GRANT_MASK, grant.grantBits);
+ }
+ }
+
+ // And remove it from the pending-grant bookkeeping
+ grantsByPackage.remove(packageName);
+ if (grantsByPackage.size() < 1) {
+ mRestoredUserGrants.remove(userId);
+ }
+ writeRuntimePermissionsForUserLPr(userId, false);
+ }
+
void setInstallerPackageName(String pkgName, String installerPkgName) {
PackageSetting p = mPackages.get(pkgName);
if (p != null) {
@@ -1725,6 +1800,14 @@
}
}
+ // Specifically for backup/restore
+ public void processRestoredPermissionGrantLPr(String pkgName, String permission,
+ boolean isGranted, int restoredFlagSet, int userId)
+ throws IOException, XmlPullParserException {
+ mRuntimePermissionsPersistence.rememberRestoredUserGrantLPr(
+ pkgName, permission, isGranted, restoredFlagSet, userId);
+ }
+
void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
throws IllegalArgumentException, IllegalStateException, IOException {
serializer.startTag(null, TAG_DEFAULT_APPS);
@@ -4410,6 +4493,48 @@
pw.print(mReadMessages.toString());
}
+ void dumpRestoredPermissionGrantsLPr(PrintWriter pw, DumpState dumpState) {
+ if (mRestoredUserGrants.size() > 0) {
+ pw.println();
+ pw.println("Restored (pending) permission grants:");
+ for (int userIndex = 0; userIndex < mRestoredUserGrants.size(); userIndex++) {
+ ArrayMap<String, ArraySet<RestoredPermissionGrant>> grantsByPackage =
+ mRestoredUserGrants.valueAt(userIndex);
+ if (grantsByPackage != null && grantsByPackage.size() > 0) {
+ final int userId = mRestoredUserGrants.keyAt(userIndex);
+ pw.print(" User "); pw.println(userId);
+
+ for (int pkgIndex = 0; pkgIndex < grantsByPackage.size(); pkgIndex++) {
+ ArraySet<RestoredPermissionGrant> grants = grantsByPackage.valueAt(pkgIndex);
+ if (grants != null && grants.size() > 0) {
+ final String pkgName = grantsByPackage.keyAt(pkgIndex);
+ pw.print(" "); pw.print(pkgName); pw.println(" :");
+
+ for (RestoredPermissionGrant g : grants) {
+ pw.print(" ");
+ pw.print(g.permissionName);
+ if (g.granted) {
+ pw.print(" GRANTED");
+ }
+ if ((g.grantBits&FLAG_PERMISSION_USER_SET) != 0) {
+ pw.print(" user_set");
+ }
+ if ((g.grantBits&FLAG_PERMISSION_USER_FIXED) != 0) {
+ pw.print(" user_fixed");
+ }
+ if ((g.grantBits&FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0) {
+ pw.print(" revoke_on_upgrade");
+ }
+ pw.println();
+ }
+ }
+ }
+ }
+ }
+ pw.println();
+ }
+ }
+
private static void dumpSplitNames(PrintWriter pw, PackageParser.Package pkg) {
if (pkg == null) {
pw.print("unknown");
@@ -4507,7 +4632,6 @@
private final class RuntimePermissionPersistence {
private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 200;
-
private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000;
private final Handler mHandler = new MyHandler();
@@ -4624,6 +4748,7 @@
serializer.setFeature(
"http://xmlpull.org/v1/doc/features.html#indent-output", true);
serializer.startDocument(null, true);
+
serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);
String fingerprint = mFingerprints.get(userId);
@@ -4652,6 +4777,51 @@
}
serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
+
+ // Now any restored permission grants that are waiting for the apps
+ // in question to be installed. These are stored as per-package
+ // TAG_RESTORED_RUNTIME_PERMISSIONS blocks, each containing some
+ // number of individual permission grant entities.
+ if (mRestoredUserGrants.get(userId) != null) {
+ ArrayMap<String, ArraySet<RestoredPermissionGrant>> restoredGrants =
+ mRestoredUserGrants.get(userId);
+ if (restoredGrants != null) {
+ final int pkgCount = restoredGrants.size();
+ for (int i = 0; i < pkgCount; i++) {
+ final ArraySet<RestoredPermissionGrant> pkgGrants =
+ restoredGrants.valueAt(i);
+ if (pkgGrants != null && pkgGrants.size() > 0) {
+ final String pkgName = restoredGrants.keyAt(i);
+ serializer.startTag(null, TAG_RESTORED_RUNTIME_PERMISSIONS);
+ serializer.attribute(null, ATTR_PACKAGE_NAME, pkgName);
+
+ final int N = pkgGrants.size();
+ for (int z = 0; z < N; z++) {
+ RestoredPermissionGrant g = pkgGrants.valueAt(z);
+ serializer.startTag(null, TAG_PERMISSION_ENTRY);
+ serializer.attribute(null, ATTR_NAME, g.permissionName);
+
+ if (g.granted) {
+ serializer.attribute(null, ATTR_GRANTED, "true");
+ }
+
+ if ((g.grantBits&FLAG_PERMISSION_USER_SET) != 0) {
+ serializer.attribute(null, ATTR_USER_SET, "true");
+ }
+ if ((g.grantBits&FLAG_PERMISSION_USER_FIXED) != 0) {
+ serializer.attribute(null, ATTR_USER_FIXED, "true");
+ }
+ if ((g.grantBits&FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0) {
+ serializer.attribute(null, ATTR_REVOKE_ON_UPGRADE, "true");
+ }
+ serializer.endTag(null, TAG_PERMISSION_ENTRY);
+ }
+ serializer.endTag(null, TAG_RESTORED_RUNTIME_PERMISSIONS);
+ }
+ }
+ }
+ }
+
serializer.endDocument();
destination.finishWrite(out);
@@ -4725,6 +4895,31 @@
}
}
+ // Backup/restore support
+
+ public void rememberRestoredUserGrantLPr(String pkgName, String permission,
+ boolean isGranted, int restoredFlagSet, int userId) {
+ // This change will be remembered at write-settings time
+ ArrayMap<String, ArraySet<RestoredPermissionGrant>> grantsByPackage =
+ mRestoredUserGrants.get(userId);
+ if (grantsByPackage == null) {
+ grantsByPackage = new ArrayMap<String, ArraySet<RestoredPermissionGrant>>();
+ mRestoredUserGrants.put(userId, grantsByPackage);
+ }
+
+ ArraySet<RestoredPermissionGrant> grants = grantsByPackage.get(pkgName);
+ if (grants == null) {
+ grants = new ArraySet<RestoredPermissionGrant>();
+ grantsByPackage.put(pkgName, grants);
+ }
+
+ RestoredPermissionGrant grant = new RestoredPermissionGrant(permission,
+ isGranted, restoredFlagSet);
+ grants.add(grant);
+ }
+
+ // Private internals
+
private void parseRuntimePermissionsLPr(XmlPullParser parser, int userId)
throws IOException, XmlPullParserException {
final int outerDepth = parser.getDepth();
@@ -4764,6 +4959,46 @@
}
parsePermissionsLPr(parser, sus.getPermissionsState(), userId);
} break;
+
+ case TAG_RESTORED_RUNTIME_PERMISSIONS: {
+ final String pkgName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
+ parseRestoredRuntimePermissionsLPr(parser, pkgName, userId);
+ } break;
+ }
+ }
+ }
+
+ private void parseRestoredRuntimePermissionsLPr(XmlPullParser parser,
+ final String pkgName, final int userId) throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ switch (parser.getName()) {
+ case TAG_PERMISSION_ENTRY: {
+ final String permName = parser.getAttributeValue(null, ATTR_NAME);
+ final boolean isGranted = "true".equals(
+ parser.getAttributeValue(null, ATTR_GRANTED));
+
+ int permBits = 0;
+ if ("true".equals(parser.getAttributeValue(null, ATTR_USER_SET))) {
+ permBits |= FLAG_PERMISSION_USER_SET;
+ }
+ if ("true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED))) {
+ permBits |= FLAG_PERMISSION_USER_FIXED;
+ }
+ if ("true".equals(parser.getAttributeValue(null, ATTR_REVOKE_ON_UPGRADE))) {
+ permBits |= FLAG_PERMISSION_REVOKE_ON_UPGRADE;
+ }
+
+ if (isGranted || permBits != 0) {
+ rememberRestoredUserGrantLPr(pkgName, permName, isGranted, permBits, userId);
+ }
+ } break;
}
}
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9c629bd..806c4ca 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -183,6 +183,8 @@
static final int LONG_PRESS_HOME_NOTHING = 0;
static final int LONG_PRESS_HOME_RECENT_SYSTEM_UI = 1;
static final int LONG_PRESS_HOME_ASSIST = 2;
+ static final int LONG_PRESS_HOME_PICTURE_IN_PICTURE = 3;
+ static final int LAST_LONG_PRESS_HOME_BEHAVIOR = LONG_PRESS_HOME_PICTURE_IN_PICTURE;
static final int DOUBLE_TAP_HOME_NOTHING = 0;
static final int DOUBLE_TAP_HOME_RECENT_SYSTEM_UI = 1;
@@ -1313,16 +1315,26 @@
}
}
- private void handleLongPressOnHome(int deviceId) {
- if (mLongPressOnHomeBehavior != LONG_PRESS_HOME_NOTHING) {
- mHomeConsumed = true;
- performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+ private void handleLongPressOnHome(int deviceId, KeyEvent event) {
+ if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_NOTHING) {
+ return;
+ }
+ mHomeConsumed = true;
+ performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
- if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI) {
+ switch (mLongPressOnHomeBehavior) {
+ case LONG_PRESS_HOME_RECENT_SYSTEM_UI:
toggleRecentApps();
- } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_ASSIST) {
+ break;
+ case LONG_PRESS_HOME_ASSIST:
launchAssistAction(null, deviceId);
- }
+ break;
+ case LONG_PRESS_HOME_PICTURE_IN_PICTURE:
+ handlePipKey(event);
+ break;
+ default:
+ Log.w(TAG, "Not defined home long press behavior: " + mLongPressOnHomeBehavior);
+ break;
}
}
@@ -1333,6 +1345,13 @@
}
}
+ private void handlePipKey(KeyEvent event) {
+ if (DEBUG_INPUT) Log.d(TAG, "handlePipKey event=" + event);
+ Intent intent = new Intent(Intent.ACTION_PICTURE_IN_PICTURE_BUTTON);
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
private final Runnable mHomeDoubleTapTimeoutRunnable = new Runnable() {
@Override
public void run() {
@@ -1625,7 +1644,7 @@
mLongPressOnHomeBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnHomeBehavior);
if (mLongPressOnHomeBehavior < LONG_PRESS_HOME_NOTHING ||
- mLongPressOnHomeBehavior > LONG_PRESS_HOME_ASSIST) {
+ mLongPressOnHomeBehavior > LAST_LONG_PRESS_HOME_BEHAVIOR) {
mLongPressOnHomeBehavior = LONG_PRESS_HOME_NOTHING;
}
@@ -2851,7 +2870,7 @@
}
} else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
if (!keyguardOn) {
- handleLongPressOnHome(event.getDeviceId());
+ handleLongPressOnHome(event.getDeviceId(), event);
}
}
return -1;
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 8cdff11..1d498e1 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -664,11 +664,16 @@
public boolean isDeviceLocked(int userId) throws RemoteException {
userId = ActivityManager.handleIncomingUser(getCallingPid(), getCallingUid(), userId,
false /* allowAll */, true /* requireFull */, "isDeviceLocked", null);
- if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
- userId = resolveProfileParent(userId);
- }
- return isDeviceLockedInner(userId);
+ long token = Binder.clearCallingIdentity();
+ try {
+ if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
+ userId = resolveProfileParent(userId);
+ }
+ return isDeviceLockedInner(userId);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 751f871..46240783 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -67,6 +67,7 @@
// Whether we're performing an entering animation with a saved surface.
boolean mAnimatingWithSavedSurface;
+
Task mTask;
boolean appFullscreen;
int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -477,6 +478,15 @@
*/
void unfreezeBounds() {
mFrozenBounds.remove();
+ for (int i = windows.size() - 1; i >= 0; i--) {
+ final WindowState win = windows.get(i);
+ win.mLayoutNeeded = true;
+ win.setDisplayLayoutNeeded();
+ if (!service.mResizingWindows.contains(win)) {
+ service.mResizingWindows.add(win);
+ }
+ }
+ service.mWindowPlacerLocked.performSurfacePlacement();
}
@Override
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1b6957d..ac38424 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -203,15 +203,14 @@
public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags,
int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
- Rect outVisibleInsets, Rect outStableInsets, Rect outsets, Configuration
- outConfig,
- Surface outSurface) {
+ Rect outVisibleInsets, Rect outStableInsets, Rect outsets, Rect outBackdropFrame,
+ Configuration outConfig, Surface outSurface) {
if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from "
+ Binder.getCallingPid());
int res = mService.relayoutWindow(this, window, seq, attrs,
requestedWidth, requestedHeight, viewFlags, flags,
outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
- outStableInsets, outsets, outConfig, outSurface);
+ outStableInsets, outsets, outBackdropFrame, outConfig, outSurface);
if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to "
+ Binder.getCallingPid());
return res;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 632c033..7748c6a 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -105,12 +105,6 @@
return mTasks;
}
- void resizeWindows() {
- for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
- mTasks.get(taskNdx).resizeWindows();
- }
- }
-
/**
* Set the bounds of the stack and its containing tasks.
* @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen.
@@ -137,11 +131,11 @@
// it might no longer fully cover the stack area.
// Save the old bounds and re-apply the scroll. This adjusts the bounds to
// fit the new stack bounds.
- task.setBounds(bounds, config);
+ task.resizeLocked(bounds, config, false /* forced */);
task.getBounds(mTmpRect);
task.scrollLocked(mTmpRect);
} else {
- task.setBounds(bounds, config);
+ task.resizeLocked(bounds, config, false /* forced */);
task.setTempInsetBounds(
taskTempInsetBounds != null ? taskTempInsetBounds.get(task.mTaskId)
: null);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7d142ec..4fa3856 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2561,8 +2561,8 @@
WindowManager.LayoutParams attrs, int requestedWidth,
int requestedHeight, int viewVisibility, int flags,
Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
- Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Configuration outConfig,
- Surface outSurface) {
+ Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
+ Configuration outConfig, Surface outSurface) {
int result = 0;
boolean configChanged;
boolean hasStatusBarPermission =
@@ -2749,6 +2749,7 @@
outVisibleInsets.set(win.mVisibleInsets);
outStableInsets.set(win.mStableInsets);
outOutsets.set(win.mOutsets);
+ outBackdropFrame.set(win.getBackdropFrame(win.mFrame));
if (localLOGV) Slog.v(
TAG_WM, "Relayout given client " + client.asBinder()
+ ", requestedWidth=" + requestedWidth
@@ -2869,7 +2870,12 @@
result |= WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
}
}
- result |= win.isDragResizing() ? WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING : 0;
+ final boolean freeformResizing = win.isDragResizing()
+ && win.getResizeMode() == WindowState.DRAG_RESIZE_MODE_FREEFORM;
+ final boolean dockedResizing = win.isDragResizing()
+ && win.getResizeMode() == WindowState.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
+ result |= freeformResizing ? WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM : 0;
+ result |= dockedResizing ? WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED : 0;
if (win.isAnimatingWithSavedSurface()) {
// If we're animating with a saved surface now, request client to report draw.
// We still need to know when the real thing is drawn.
@@ -4858,7 +4864,6 @@
}
if (stack.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
&& stack.isVisibleLocked()) {
- stack.resizeWindows();
stack.getDisplayContent().layoutNeeded = true;
mWindowPlacerLocked.performSurfacePlacement();
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index afbaf00..ff86aae 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -729,6 +729,9 @@
// will return the correct value to the renderer.
mDisplayContent.getDockedDividerController().positionDockedStackedDivider(mFrame);
mContentFrame.set(mFrame);
+ if (!mFrame.equals(mLastFrame)) {
+ mMovedByResize = true;
+ }
}
} else {
mContentFrame.set(Math.max(mContentFrame.left, frame.left),
@@ -752,15 +755,7 @@
Math.max(frame.right - mOverscanFrame.right, 0),
Math.max(frame.bottom - mOverscanFrame.bottom, 0));
- mContentInsets.set(mContentFrame.left - frame.left,
- mContentFrame.top - frame.top,
- frame.right - mContentFrame.right,
- frame.bottom - mContentFrame.bottom);
- mVisibleInsets.set(mVisibleFrame.left - frame.left,
- mVisibleFrame.top - frame.top,
- frame.right - mVisibleFrame.right,
- frame.bottom - mVisibleFrame.bottom);
if (mAttrs.type == TYPE_DOCK_DIVIDER) {
@@ -770,7 +765,22 @@
Math.max(mStableFrame.top - mDisplayFrame.top, 0),
Math.max(mDisplayFrame.right - mStableFrame.right, 0),
Math.max(mDisplayFrame.bottom - mStableFrame.bottom, 0));
+
+ // The divider doesn't care about insets in any case, so set it to empty so we don't
+ // trigger a relayout when moving it.
+ mContentInsets.setEmpty();
+ mVisibleInsets.setEmpty();
} else {
+ mContentInsets.set(mContentFrame.left - frame.left,
+ mContentFrame.top - frame.top,
+ frame.right - mContentFrame.right,
+ frame.bottom - mContentFrame.bottom);
+
+ mVisibleInsets.set(mVisibleFrame.left - frame.left,
+ mVisibleFrame.top - frame.top,
+ frame.right - mVisibleFrame.right,
+ frame.bottom - mVisibleFrame.bottom);
+
mStableInsets.set(Math.max(mStableFrame.left - frame.left, 0),
Math.max(mStableFrame.top - frame.top, 0),
Math.max(frame.right - mStableFrame.right, 0),
@@ -1993,21 +2003,24 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
- private void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
- Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
- Configuration newConfig) throws RemoteException {
- DisplayInfo displayInfo = getDisplayInfo();
- mTmpRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
+ Rect getBackdropFrame(Rect frame) {
// When the task is docked, we send fullscreen sized backDropFrame as soon as resizing
// start even if we haven't received the relayout window, so that the client requests
// the relayout sooner. When dragging stops, backDropFrame needs to stay fullscreen
// until the window to small size, otherwise the multithread renderer will shift last
// one or more frame to wrong offset. So here we send fullscreen backdrop if either
// isDragResizing() or isDragResizeChanged() is true.
+ DisplayInfo displayInfo = getDisplayInfo();
+ mTmpRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
boolean resizing = isDragResizing() || isDragResizeChanged();
- final Rect backDropFrame = (inFreeformWorkspace() || !resizing) ? frame : mTmpRect;
+ return (inFreeformWorkspace() || !resizing) ? frame : mTmpRect;
+ }
+
+ private void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets,
+ Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw,
+ Configuration newConfig) throws RemoteException {
mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets,
- reportDraw, newConfig, backDropFrame);
+ reportDraw, newConfig, getBackdropFrame(frame));
}
public void registerFocusObserver(IWindowFocusObserver observer) {
diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java
index 0af1525..5ee4066 100644
--- a/services/print/java/com/android/server/print/RemotePrintService.java
+++ b/services/print/java/com/android/server/print/RemotePrintService.java
@@ -863,8 +863,7 @@
}
private void throwIfPrinterIdTampered(ComponentName serviceName, PrinterId printerId) {
- if (printerId == null || printerId.getServiceName() == null
- || !printerId.getServiceName().equals(serviceName)) {
+ if (printerId == null || !printerId.getServiceName().equals(serviceName)) {
throw new IllegalArgumentException("Invalid printer id: " + printerId);
}
}
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl b/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
new file mode 100644
index 0000000..b7e78d1
--- /dev/null
+++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable ParcelableCallAnalytics;
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
new file mode 100644
index 0000000..e7c9672
--- /dev/null
+++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.telecom;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+@SystemApi
+public class ParcelableCallAnalytics implements Parcelable {
+ public static final int CALLTYPE_UNKNOWN = 0;
+ public static final int CALLTYPE_INCOMING = 1;
+ public static final int CALLTYPE_OUTGOING = 2;
+
+ // Constants for call technology
+ public static final int CDMA_PHONE = 0x1;
+ public static final int GSM_PHONE = 0x2;
+ public static final int IMS_PHONE = 0x4;
+ public static final int SIP_PHONE = 0x8;
+ public static final int THIRD_PARTY_PHONE = 0x10;
+
+ public static final long MILLIS_IN_5_MINUTES = 1000 * 60 * 5;
+ public static final long MILLIS_IN_1_SECOND = 1000;
+
+ public static final int STILL_CONNECTED = -1;
+
+ public static final Parcelable.Creator<ParcelableCallAnalytics> CREATOR =
+ new Parcelable.Creator<ParcelableCallAnalytics> () {
+
+ @Override
+ public ParcelableCallAnalytics createFromParcel(Parcel in) {
+ return new ParcelableCallAnalytics(in);
+ }
+
+ @Override
+ public ParcelableCallAnalytics[] newArray(int size) {
+ return new ParcelableCallAnalytics[size];
+ }
+ };
+
+ // The start time of the call in milliseconds since Jan. 1, 1970, rounded to the nearest
+ // 5 minute increment.
+ private final long startTimeMillis;
+
+ // The duration of the call, in milliseconds.
+ private final long callDurationMillis;
+
+ // ONE OF calltype_unknown, calltype_incoming, or calltype_outgoing
+ private final int callType;
+
+ // true if the call came in while another call was in progress or if the user dialed this call
+ // while in the middle of another call.
+ private final boolean isAdditionalCall;
+
+ // true if the call was interrupted by an incoming or outgoing call.
+ private final boolean isInterrupted;
+
+ // bitmask denoting which technologies a call used.
+ private final int callTechnologies;
+
+ // Any of the DisconnectCause codes, or STILL_CONNECTED.
+ private final int callTerminationCode;
+
+ // Whether the call is an emergency call
+ private final boolean isEmergencyCall;
+
+ // The package name of the connection service that this call used.
+ private final String connectionService;
+
+ // Whether the call object was created from an existing connection.
+ private final boolean isCreatedFromExistingConnection;
+
+ public ParcelableCallAnalytics(long startTimeMillis, long callDurationMillis, int callType,
+ boolean isAdditionalCall, boolean isInterrupted, int callTechnologies,
+ int callTerminationCode, boolean isEmergencyCall, String connectionService,
+ boolean isCreatedFromExistingConnection) {
+ this.startTimeMillis = startTimeMillis;
+ this.callDurationMillis = callDurationMillis;
+ this.callType = callType;
+ this.isAdditionalCall = isAdditionalCall;
+ this.isInterrupted = isInterrupted;
+ this.callTechnologies = callTechnologies;
+ this.callTerminationCode = callTerminationCode;
+ this.isEmergencyCall = isEmergencyCall;
+ this.connectionService = connectionService;
+ this.isCreatedFromExistingConnection = isCreatedFromExistingConnection;
+ }
+
+ public ParcelableCallAnalytics(Parcel in) {
+ startTimeMillis = in.readLong();
+ callDurationMillis = in.readLong();
+ callType = in.readInt();
+ isAdditionalCall = readByteAsBoolean(in);
+ isInterrupted = readByteAsBoolean(in);
+ callTechnologies = in.readInt();
+ callTerminationCode = in.readInt();
+ isEmergencyCall = readByteAsBoolean(in);
+ connectionService = in.readString();
+ isCreatedFromExistingConnection = readByteAsBoolean(in);
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(startTimeMillis);
+ out.writeLong(callDurationMillis);
+ out.writeInt(callType);
+ writeBooleanAsByte(out, isAdditionalCall);
+ writeBooleanAsByte(out, isInterrupted);
+ out.writeInt(callTechnologies);
+ out.writeInt(callTerminationCode);
+ writeBooleanAsByte(out, isEmergencyCall);
+ out.writeString(connectionService);
+ writeBooleanAsByte(out, isCreatedFromExistingConnection);
+ }
+
+ public long getStartTimeMillis() {
+ return startTimeMillis;
+ }
+
+ public long getCallDurationMillis() {
+ return callDurationMillis;
+ }
+
+ public int getCallType() {
+ return callType;
+ }
+
+ public boolean isAdditionalCall() {
+ return isAdditionalCall;
+ }
+
+ public boolean isInterrupted() {
+ return isInterrupted;
+ }
+
+ public int getCallTechnologies() {
+ return callTechnologies;
+ }
+
+ public int getCallTerminationCode() {
+ return callTerminationCode;
+ }
+
+ public boolean isEmergencyCall() {
+ return isEmergencyCall;
+ }
+
+ public String getConnectionService() {
+ return connectionService;
+ }
+
+ public boolean isCreatedFromExistingConnection() {
+ return isCreatedFromExistingConnection;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private static void writeBooleanAsByte(Parcel out, boolean b) {
+ out.writeByte((byte) (b ? 1 : 0));
+ }
+
+ private static boolean readByteAsBoolean(Parcel in) {
+ return (in.readByte() == 1);
+ }
+}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 9eb1665..d45b26f 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -14,6 +14,7 @@
package android.telecom;
+import android.Manifest;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.ComponentName;
@@ -1377,6 +1378,27 @@
}
}
+ /**
+ * Dumps telecom analytics for uploading.
+ *
+ * @return
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.DUMP)
+ public List<ParcelableCallAnalytics> dumpAnalytics() {
+ ITelecomService service = getTelecomService();
+ List<ParcelableCallAnalytics> result = null;
+ if (service != null) {
+ try {
+ result = service.dumpCallAnalytics();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error dumping call analytics", e);
+ }
+ }
+ return result;
+ }
+
private ITelecomService getTelecomService() {
if (mTelecomServiceOverride != null) {
return mTelecomServiceOverride;
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 856e210..af36b3e 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -17,6 +17,7 @@
package com.android.internal.telecom;
import android.content.ComponentName;
+import android.telecom.ParcelableCallAnalytics;
import android.telecom.PhoneAccountHandle;
import android.net.Uri;
import android.os.Bundle;
@@ -143,6 +144,11 @@
*/
String getSystemDialerPackage();
+ /**
+ * @see TelecomServiceImpl#dumpCallAnalytics
+ */
+ List<ParcelableCallAnalytics> dumpCallAnalytics();
+
//
// Internal system apis relating to call management.
//
diff --git a/tests/FeatureSplit/base/AndroidManifest.xml b/tests/FeatureSplit/base/AndroidManifest.xml
index 989e802..e82b3b9 100644
--- a/tests/FeatureSplit/base/AndroidManifest.xml
+++ b/tests/FeatureSplit/base/AndroidManifest.xml
@@ -16,7 +16,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test.split.feature">
- <application android:label="@string/app_title"
- android:hasCode="false">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+ <application android:label="@string/app_title">
+ <activity android:name=".ActivityMain" android:label="Feature Base">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/FeatureSplit/base/res/layout/main.xml b/tests/FeatureSplit/base/res/layout/main.xml
new file mode 100644
index 0000000..f01b920
--- /dev/null
+++ b/tests/FeatureSplit/base/res/layout/main.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="16dp">
+ <TextView android:id="@+id/text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerInParent="true"
+ android:textAppearance="?android:textAppearanceLarge" />
+</RelativeLayout>
diff --git a/tests/FeatureSplit/base/res/values/values.xml b/tests/FeatureSplit/base/res/values/values.xml
index 564d301..854a8bb 100644
--- a/tests/FeatureSplit/base/res/values/values.xml
+++ b/tests/FeatureSplit/base/res/values/values.xml
@@ -16,6 +16,7 @@
<resources>
<string name="app_title">FeatureSplit APK</string>
+ <string name="base">Base</string>
<item type="id" name="test_id"/>
<integer name="test_integer">100</integer>
diff --git a/tests/FeatureSplit/base/src/com/android/test/split/feature/ActivityMain.java b/tests/FeatureSplit/base/src/com/android/test/split/feature/ActivityMain.java
new file mode 100644
index 0000000..6cca7c3
--- /dev/null
+++ b/tests/FeatureSplit/base/src/com/android/test/split/feature/ActivityMain.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.split.feature;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class ActivityMain extends Activity {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ ((TextView) findViewById(R.id.text)).setText(R.string.base);
+ }
+}
+
diff --git a/tests/FeatureSplit/feature1/Android.mk b/tests/FeatureSplit/feature1/Android.mk
index adfb575..aa222dd 100644
--- a/tests/FeatureSplit/feature1/Android.mk
+++ b/tests/FeatureSplit/feature1/Android.mk
@@ -22,10 +22,12 @@
LOCAL_MODULE_TAGS := tests
featureOf := FeatureSplitBase
+
+LOCAL_APK_LIBRARIES := $(featureOf)
featureOfApk := $(call intermediates-dir-for,APPS,$(featureOf))/package.apk
localRStamp := $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),,COMMON)/src/R.stamp
$(localRStamp): $(featureOfApk)
-LOCAL_AAPT_FLAGS := --feature-of $(featureOfApk)
+LOCAL_AAPT_FLAGS := --feature-of $(featureOfApk) --custom-package com.android.test.split.feature.one
include $(BUILD_PACKAGE)
diff --git a/tests/FeatureSplit/feature1/AndroidManifest.xml b/tests/FeatureSplit/feature1/AndroidManifest.xml
index 2aadc6d..42619b6 100644
--- a/tests/FeatureSplit/feature1/AndroidManifest.xml
+++ b/tests/FeatureSplit/feature1/AndroidManifest.xml
@@ -17,5 +17,15 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test.split.feature"
featureName="feature1">
- <application android:hasCode="false" />
+
+ <uses-sdk android:minSdkVersion="21" />
+
+ <application>
+ <activity android:name=".one.One" android:label="Feature One">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
</manifest>
diff --git a/tests/FeatureSplit/feature1/res/values/values.xml b/tests/FeatureSplit/feature1/res/values/values.xml
index 70eb56a..10dbd97 100644
--- a/tests/FeatureSplit/feature1/res/values/values.xml
+++ b/tests/FeatureSplit/feature1/res/values/values.xml
@@ -15,6 +15,7 @@
-->
<resources>
+ <string name="feature_string">Feature1</string>
<item type="id" name="test_id2"/>
<integer name="test_integer2">200</integer>
<color name="test_color2">#00ff00</color>
diff --git a/tests/FeatureSplit/feature1/src/com/android/test/split/feature/one/One.java b/tests/FeatureSplit/feature1/src/com/android/test/split/feature/one/One.java
new file mode 100644
index 0000000..def1339
--- /dev/null
+++ b/tests/FeatureSplit/feature1/src/com/android/test/split/feature/one/One.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.split.feature.one;
+
+import com.android.test.split.feature.ActivityMain;
+
+import android.widget.TextView;
+import android.os.Bundle;
+
+public class One extends ActivityMain {
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ((TextView) findViewById(com.android.test.split.feature.R.id.text))
+ .setText(R.string.feature_string);
+ }
+}
diff --git a/tests/FeatureSplit/feature2/Android.mk b/tests/FeatureSplit/feature2/Android.mk
index e69cbe8..1a0322b 100644
--- a/tests/FeatureSplit/feature2/Android.mk
+++ b/tests/FeatureSplit/feature2/Android.mk
@@ -24,6 +24,8 @@
featureOf := FeatureSplitBase
featureAfter := FeatureSplit1
+LOCAL_APK_LIBRARIES := $(featureOf)
+
featureOfApk := $(call intermediates-dir-for,APPS,$(featureOf))/package.apk
featureAfterApk := $(call intermediates-dir-for,APPS,$(featureAfter))/package.apk
localRStamp := $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),,COMMON)/src/R.stamp
@@ -31,5 +33,6 @@
LOCAL_AAPT_FLAGS := --feature-of $(featureOfApk)
LOCAL_AAPT_FLAGS += --feature-after $(featureAfterApk)
+LOCAL_AAPT_FLAGS += --custom-package com.android.test.split.feature.two
include $(BUILD_PACKAGE)
diff --git a/tests/FeatureSplit/feature2/AndroidManifest.xml b/tests/FeatureSplit/feature2/AndroidManifest.xml
index d139900..b50044a 100644
--- a/tests/FeatureSplit/feature2/AndroidManifest.xml
+++ b/tests/FeatureSplit/feature2/AndroidManifest.xml
@@ -17,5 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test.split.feature"
featureName="feature2">
+
+ <uses-sdk android:minSdkVersion="21" />
+
<application android:hasCode="false"/>
</manifest>