Merge "Fix build"
diff --git a/api/current.txt b/api/current.txt
index e75ab1c..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 {
@@ -38132,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 49ff948..28dd71f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8219,6 +8219,7 @@
field public static final int BIND_ALLOW_OOM_MANAGEMENT = 16; // 0x10
field public static final int BIND_AUTO_CREATE = 1; // 0x1
field public static final int BIND_DEBUG_UNBIND = 2; // 0x2
+ field public static final int BIND_EXTERNAL_SERVICE = -2147483648; // 0x80000000
field public static final int BIND_IMPORTANT = 64; // 0x40
field public static final int BIND_NOT_FOREGROUND = 4; // 0x4
field public static final int BIND_WAIVE_PRIORITY = 32; // 0x20
@@ -20706,10 +20707,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 +20735,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 +20776,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 +20828,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 +20882,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 {
@@ -37565,6 +37581,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();
@@ -37782,6 +37827,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);
@@ -40491,9 +40537,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 68f8a41..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 {
@@ -38148,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/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/JobSchedulerImpl.java b/core/java/android/app/JobSchedulerImpl.java
index 09038d5..dacf4ea 100644
--- a/core/java/android/app/JobSchedulerImpl.java
+++ b/core/java/android/app/JobSchedulerImpl.java
@@ -46,6 +46,15 @@
}
@Override
+ public int scheduleAsPackage(JobInfo job, String packageName, int userId) {
+ try {
+ return mBinder.scheduleAsPackage(job, packageName, userId);
+ } catch (RemoteException e) {
+ return JobScheduler.RESULT_FAILURE;
+ }
+ }
+
+ @Override
public void cancel(int jobId) {
try {
mBinder.cancel(jobId);
diff --git a/core/java/android/app/job/IJobScheduler.aidl b/core/java/android/app/job/IJobScheduler.aidl
index f1258ae..f0c3302 100644
--- a/core/java/android/app/job/IJobScheduler.aidl
+++ b/core/java/android/app/job/IJobScheduler.aidl
@@ -24,6 +24,7 @@
*/
interface IJobScheduler {
int schedule(in JobInfo job);
+ int scheduleAsPackage(in JobInfo job, String packageName, int userId);
void cancel(int jobId);
void cancelAll();
List<JobInfo> getAllPendingJobs();
diff --git a/core/java/android/app/job/JobScheduler.java b/core/java/android/app/job/JobScheduler.java
index 6e96da5..5e1a4256 100644
--- a/core/java/android/app/job/JobScheduler.java
+++ b/core/java/android/app/job/JobScheduler.java
@@ -63,6 +63,17 @@
public abstract int schedule(JobInfo job);
/**
+ *
+ * @param job The job to be scheduled.
+ * @param packageName The package on behalf of which the job is to be scheduled. This will be
+ * used to track battery usage and appIdleState.
+ * @param userId User on behalf of whom this job is to be scheduled.
+ * @return {@link #RESULT_SUCCESS} or {@link #RESULT_FAILURE}
+ * @hide
+ */
+ public abstract int scheduleAsPackage(JobInfo job, String packageName, int userId);
+
+ /**
* Cancel a job that is pending in the JobScheduler.
* @param jobId unique identifier for this job. Obtain this value from the jobs returned by
* {@link #getAllPendingJobs()}.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fb27910..48d0196 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -335,6 +335,7 @@
* calling application's package, rather than the package in which the service is declared.
* @hide
*/
+ @SystemApi
public static final int BIND_EXTERNAL_SERVICE = 0x80000000;
/**
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/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 3435474..0f98cfb 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -23,4 +23,12 @@
<!-- Flags enabling default window features. See Window.java -->
<bool name="config_defaultWindowFeatureOptionsPanel">false</bool>
+
+ <!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. -->
+ <string translatable="false" name="config_defaultPictureInPictureBounds">"1420 100 1820 325"</string>
+
+ <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, when the PIP
+ is located in center. -->
+ <string translatable="false" name="config_centeredPictureInPictureBounds">"600 331 1320 749"</string>
+
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3f6a0c1..b6150e0 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2419,6 +2419,10 @@
<!-- Default bounds [left top right bottom] on screen for picture-in-picture windows. -->
<string translatable="false" name="config_defaultPictureInPictureBounds">"0 0 100 100"</string>
+ <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, when the PIP
+ is located in center. -->
+ <string translatable="false" name="config_centeredPictureInPictureBounds">"0 0 300 300"</string>
+
<!-- Controls the snap mode for the docked stack divider
0 - 3 snap targets: left/top has 16:9 ratio, 1:1, and right/bottom has 16:9 ratio
1 - 3 snap targets: fixed ratio, 1:1, (1 - fixed ratio)
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e65ce5e..bfcd3fd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -305,6 +305,7 @@
<java-symbol type="bool" name="config_supportSpeakerNearUltrasound" />
<java-symbol type="bool" name="config_freeformWindowManagement" />
<java-symbol type="string" name="config_defaultPictureInPictureBounds" />
+ <java-symbol type="string" name="config_centeredPictureInPictureBounds" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_threshold" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_boost_factor" />
<java-symbol type="integer" name="config_wifi_framework_5GHz_preference_penalty_threshold" />
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/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 3dc339d..8d57b88 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -309,6 +309,27 @@
android:launchMode="singleTop"
android:excludeFromRecents="true" />
+ <!-- started from PipUI -->
+ <activity
+ android:name="com.android.systemui.tv.pip.PipMenuActivity"
+ android:exported="true"
+ android:theme="@style/PipTheme"
+ android:launchMode="singleTop"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|layoutDirection"
+ android:resizeable="true"
+ android:supportsPictureInPicture="true"
+ androidprv:alwaysFocusable="true"
+ android:excludeFromRecents="true" />
+ <activity
+ android:name="com.android.systemui.tv.pip.PipOverlayActivity"
+ android:exported="true"
+ android:theme="@style/PipTheme"
+ android:launchMode="singleTop"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|layoutDirection"
+ android:resizeable="true"
+ android:supportsPictureInPicture="true"
+ android:excludeFromRecents="true" />
+
<!-- platform logo easter egg activity -->
<activity
android:name=".DessertCase"
diff --git a/packages/SystemUI/res/layout/tv_pip_menu.xml b/packages/SystemUI/res/layout/tv_pip_menu.xml
new file mode 100644
index 0000000..a638d17
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_pip_menu.xml
@@ -0,0 +1,61 @@
+<?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.
+*/
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <FrameLayout
+ android:layout_alignParentEnd="true"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="10dp"
+ android:paddingEnd="10dp"
+ android:background="#88FFFFFF">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_gravity="center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" >
+
+ <Button android:id="@+id/full"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/pip_fullscreen"
+ android:textSize="10sp"
+ android:focusable="true" />
+
+ <Button android:id="@+id/exit"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/pip_exit"
+ android:textSize="10sp"
+ android:focusable="true" />
+
+ <Button android:id="@+id/cancel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:text="@string/pip_cancel"
+ android:textSize="10sp"
+ android:focusable="true" />
+ </LinearLayout>
+ </FrameLayout>
+</RelativeLayout>
diff --git a/packages/SystemUI/res/layout/tv_pip_overlay.xml b/packages/SystemUI/res/layout/tv_pip_overlay.xml
new file mode 100644
index 0000000..e8691b5
--- /dev/null
+++ b/packages/SystemUI/res/layout/tv_pip_overlay.xml
@@ -0,0 +1,33 @@
+<?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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:id="@+id/guide_overlay"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:padding="3dp"
+ android:textSize="13sp"
+ android:textColor="#111111"
+ android:background="#99EEEEEE"
+ android:text="@string/pip_hold_home" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml
new file mode 100644
index 0000000..d432a62
--- /dev/null
+++ b/packages/SystemUI/res/values/strings_tv.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Button to close PIP on PIP UI -->
+ <string name="pip_exit">Close PIP</string>
+ <!-- Button to move PIP screen to the fullscreen on PIP UI -->
+ <string name="pip_fullscreen">Full screen</string>
+ <!-- Button to play the current media on PIP UI -->
+ <string name="pip_play">Play</string>
+ <!-- Button to pause the current media on PIP UI -->
+ <string name="pip_pause">Pause</string>
+ <!-- Button to close PIP overlay menu on PIP UI -->
+ <string name="pip_cancel">Cancel</string>
+ <!-- Overlay text on PIP -->
+ <string name="pip_hold_home">Hold HOME to control PIP</string>
+</resources>
diff --git a/packages/SystemUI/res/values/styles_tv.xml b/packages/SystemUI/res/values/styles_tv.xml
new file mode 100644
index 0000000..3f0caab
--- /dev/null
+++ b/packages/SystemUI/res/values/styles_tv.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <style name="PipTheme" parent="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ </style>
+</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 2d056bf..472648a 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -54,6 +54,7 @@
com.android.systemui.power.PowerUI.class,
com.android.systemui.media.RingtonePlayer.class,
com.android.systemui.keyboard.KeyboardUI.class,
+ com.android.systemui.tv.pip.PipUI.class
};
/**
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/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
new file mode 100644
index 0000000..2aac69a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -0,0 +1,295 @@
+/*
+ * 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.tv.pip;
+
+import android.app.ActivityManager.StackInfo;
+import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
+import android.app.IActivityManager;
+import android.app.ITaskStackListener;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states.
+ */
+public class PipManager {
+ private static final String TAG = "PipManager";
+ private static final boolean DEBUG = false;
+
+ private static PipManager sPipManager;
+
+ private static final int STATE_NO_PIP = 0;
+ private static final int STATE_PIP_OVERLAY = 1;
+ private static final int STATE_PIP_MENU = 2;
+
+ private Context mContext;
+ private IActivityManager mActivityManager;
+ private int mState = STATE_NO_PIP;
+ private final Handler mHandler = new Handler();
+ private List<Listener> mListeners = new ArrayList<>();
+ private Rect mPipBound;
+ private Rect mMenuModePipBound;
+ private boolean mInitialized;
+ private final Runnable mOnActivityPinnedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ StackInfo stackInfo = null;
+ try {
+ stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ if (stackInfo == null) {
+ Log.w(TAG, "There is no pinned stack");
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getStackInfo failed", e);
+ return;
+ }
+ if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
+ mState = STATE_PIP_OVERLAY;
+ launchPipOverlayActivity();
+ }
+ };
+ private final Runnable mOnTaskStackChanged = new Runnable() {
+ @Override
+ public void run() {
+ if (mState != STATE_NO_PIP) {
+ // TODO: check whether PIP task is closed.
+ }
+ }
+ };
+
+ private final BroadcastReceiver mPipButtonReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Log.d(TAG, "PIP button pressed");
+ if (!hasPipTasks()) {
+ startPip();
+ } else if (mState == STATE_PIP_OVERLAY) {
+ showPipMenu();
+ }
+ }
+ };
+
+ private PipManager() { }
+
+ /**
+ * Initializes {@link PipManager}.
+ */
+ public void initialize(Context context) {
+ if (mInitialized) {
+ return;
+ }
+ mInitialized = true;
+ mContext = context;
+ Resources res = context.getResources();
+ mPipBound = Rect.unflattenFromString(res.getString(
+ com.android.internal.R.string.config_defaultPictureInPictureBounds));
+ mMenuModePipBound = Rect.unflattenFromString(res.getString(
+ com.android.internal.R.string.config_centeredPictureInPictureBounds));
+
+ mActivityManager = ActivityManagerNative.getDefault();
+ TaskStackListener taskStackListener = new TaskStackListener();
+ IActivityManager iam = ActivityManagerNative.getDefault();
+ try {
+ iam.registerTaskStackListener(taskStackListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "registerTaskStackListener failed", e);
+ }
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_PICTURE_IN_PICTURE_BUTTON);
+ mContext.registerReceiver(mPipButtonReceiver, intentFilter);
+ }
+
+ private void startPip() {
+ try {
+ mActivityManager.moveTopActivityToPinnedStack(FULLSCREEN_WORKSPACE_STACK_ID, mPipBound);
+ } catch (RemoteException|IllegalArgumentException e) {
+ Log.e(TAG, "moveTopActivityToPinnedStack failed", e);
+ }
+
+ }
+
+ /**
+ * Closes PIP (PIPped activity and PIP system UI).
+ */
+ public void closePip() {
+ mState = STATE_NO_PIP;
+ StackInfo stackInfo = null;
+ try {
+ stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ if (stackInfo == null) {
+ return;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getStackInfo failed", e);
+ return;
+ }
+ for (int taskId : stackInfo.taskIds) {
+ try {
+ mActivityManager.removeTask(taskId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "removeTask failed", e);
+ }
+ }
+ }
+
+ /**
+ * Moves the PIPped activity to the fullscreen and closes PIP system UI.
+ */
+ public void movePipToFullscreen() {
+ mState = STATE_NO_PIP;
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onMoveToFullscreen();
+ }
+ try {
+ mActivityManager.moveTasksToFullscreenStack(PINNED_STACK_ID, true);
+ } catch (RemoteException e) {
+ Log.e(TAG, "moveTasksToFullscreenStack failed", e);
+ }
+ }
+
+ /**
+ * Shows PIP overlay UI by launching {@link PipOverlayActivity}. It also locates the pinned
+ * stack to the default PIP bound {@link com.android.internal.R.string
+ * .config_defaultPictureInPictureBounds}.
+ */
+ public void showPipOverlay() {
+ if (DEBUG) Log.d(TAG, "showPipOverlay()");
+ try {
+ mActivityManager.resizeStack(PINNED_STACK_ID, mPipBound, false);
+ } catch (Exception e) {
+ Log.e(TAG, "resizeStack failed", e);
+ closePip();
+ return;
+ }
+ mState = STATE_PIP_OVERLAY;
+ launchPipOverlayActivity();
+ }
+
+ /**
+ * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
+ * stack to the centered PIP bound {@link com.android.internal.R.string
+ * .config_centeredPictureInPictureBounds}.
+ */
+ public void showPipMenu() {
+ if (DEBUG) Log.d(TAG, "showPipMenu()");
+ try {
+ mActivityManager.resizeStack(PINNED_STACK_ID, mMenuModePipBound, false);
+ } catch (Exception e) {
+ Log.e(TAG, "resizeStack failed", e);
+ closePip();
+ return;
+ }
+ mState = STATE_PIP_MENU;
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onShowPipMenu();
+ }
+ Intent intent = new Intent(mContext, PipMenuActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchStackId(PINNED_STACK_ID);
+ mContext.startActivity(intent, options.toBundle());
+ }
+
+ /**
+ * Adds {@link Listener}.
+ */
+ public void addListener(Listener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes {@link Listener}.
+ */
+ public void removeListener(Listener listener) {
+ mListeners.remove(listener);
+ }
+
+ private void launchPipOverlayActivity() {
+ Intent intent = new Intent(mContext, PipOverlayActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchStackId(PINNED_STACK_ID);
+ mContext.startActivity(intent, options.toBundle());
+ }
+
+ private boolean hasPipTasks() {
+ try {
+ StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+ return stackInfo != null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "getStackInfo failed", e);
+ return false;
+ }
+ }
+
+ private class TaskStackListener extends ITaskStackListener.Stub {
+ @Override
+ public void onTaskStackChanged() throws RemoteException {
+ // Post the message back to the UI thread.
+ mHandler.post(mOnTaskStackChanged);
+ }
+
+ @Override
+ public void onActivityPinned() throws RemoteException {
+ // Post the message back to the UI thread.
+ mHandler.post(mOnActivityPinnedRunnable);
+ }
+ }
+
+ /**
+ * A listener interface to receive notification on changes in PIP.
+ */
+ public interface Listener {
+ /**
+ * Invoked when a PIPped activity is closed.
+ */
+ void onPipActivityClosed();
+ /**
+ * Invoked when the PIP menu gets shown.
+ */
+ void onShowPipMenu();
+ /**
+ * Invoked when the PIPped activity is returned back to the fullscreen.
+ */
+ void onMoveToFullscreen();
+ }
+
+ /**
+ * Gets an instance of {@link PipManager}.
+ */
+ public static PipManager getInstance() {
+ if (sPipManager == null) {
+ sPipManager = new PipManager();
+ }
+ return sPipManager;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
new file mode 100644
index 0000000..1248321
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
@@ -0,0 +1,88 @@
+/*
+ * 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.tv.pip;
+
+import android.app.Activity;
+import android.media.session.MediaController;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * Activity to show the PIP menu to control PIP.
+ */
+public class PipMenuActivity extends Activity implements PipManager.Listener {
+ private static final String TAG = "PipMenuActivity";
+ private static final boolean DEBUG = false;
+
+ private final PipManager mPipManager = PipManager.getInstance();
+ private MediaController mMediaController;
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.tv_pip_menu);
+ mPipManager.addListener(this);
+ findViewById(R.id.full).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mPipManager.movePipToFullscreen();
+ }
+ });
+ findViewById(R.id.exit).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mPipManager.closePip();
+ finish();
+ }
+ });
+ findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mPipManager.showPipOverlay();
+ finish();
+ }
+ });
+ }
+
+ @Override
+ protected void onDestroy() {
+ mPipManager.removeListener(this);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onBackPressed() {
+ mPipManager.showPipOverlay();
+ finish();
+ }
+
+ @Override
+ public void onPipActivityClosed() {
+ finish();
+ }
+
+ @Override
+ public void onShowPipMenu() { }
+
+ @Override
+ public void onMoveToFullscreen() {
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
new file mode 100644
index 0000000..de997a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
@@ -0,0 +1,76 @@
+/*
+ * 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.tv.pip;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.R;
+
+/**
+ * Activity to show an overlay on top of PIP activity to show how to pop up PIP menu.
+ */
+public class PipOverlayActivity extends Activity implements PipManager.Listener {
+ private static final String TAG = "PipOverlayActivity";
+ private static final boolean DEBUG = false;
+
+ private static final long SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS = 2000;
+
+ private final PipManager mPipManager = PipManager.getInstance();
+ private final Handler mHandler = new Handler();
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+ setContentView(R.layout.tv_pip_overlay);
+ mPipManager.addListener(this);
+ final View overlayView = findViewById(R.id.guide_overlay);
+ // TODO: apply animation
+ overlayView.setVisibility(View.VISIBLE);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ overlayView.setVisibility(View.INVISIBLE);
+ }
+ }, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mHandler.removeCallbacksAndMessages(null);
+ mPipManager.removeListener(this);
+ }
+
+ @Override
+ public void onPipActivityClosed() {
+ finish();
+ }
+
+ @Override
+ public void onShowPipMenu() {
+ finish();
+ }
+
+ @Override
+ public void onMoveToFullscreen() {
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java
new file mode 100644
index 0000000..182b9b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipUI.java
@@ -0,0 +1,53 @@
+/*
+ * 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.tv.pip;
+
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+
+import com.android.systemui.SystemUI;
+
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+/**
+ * Controls the picture-in-picture window for TV devices.
+ */
+public class PipUI extends SystemUI {
+ private boolean mSupportPip;
+
+ @Override
+ public void start() {
+ PackageManager pm = mContext.getPackageManager();
+ mSupportPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)
+ && pm.hasSystemFeature(FEATURE_LEANBACK);
+ if (!mSupportPip) {
+ return;
+ }
+ PipManager pipManager = PipManager.getInstance();
+ pipManager.initialize(mContext);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (!mSupportPip) {
+ return;
+ }
+ // TODO: handle configuration change.
+ }
+}
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/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index e837d9a..3459dc8 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1940,7 +1940,8 @@
// static stacks need to be adjusted so they don't overlap with the docked stack.
// We get the bounds to use from window manager which has been adjusted for any
// screen controls and is also the same for all stacks.
- mWindowManager.getStackDockedModeBounds(HOME_STACK_ID, tempRect);
+ mWindowManager.getStackDockedModeBounds(
+ HOME_STACK_ID, tempRect, true /* ignoreVisibilityOnKeyguardShowing */);
for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
if (StackId.isResizeableByDockedStack(i)) {
ActivityStack otherStack = getStack(i);
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index a6db613..24519db 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -49,6 +49,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.Process;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
@@ -220,7 +221,14 @@
* @return Result of this operation. See <code>JobScheduler#RESULT_*</code> return codes.
*/
public int schedule(JobInfo job, int uId) {
+ return scheduleAsPackage(job, uId, null, -1);
+ }
+
+ public int scheduleAsPackage(JobInfo job, int uId, String packageName, int userId) {
JobStatus jobStatus = new JobStatus(job, uId);
+ if (packageName != null) {
+ jobStatus.setSource(packageName, userId);
+ }
cancelJob(uId, job.getId());
try {
if (ActivityManagerNative.getDefault().getAppStartMode(uId,
@@ -1041,6 +1049,25 @@
}
@Override
+ public int scheduleAsPackage(JobInfo job, String packageName, int userId)
+ throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduling job: " + job.toString() + " on behalf of " + packageName);
+ }
+ final int uid = Binder.getCallingUid();
+ if (uid != Process.SYSTEM_UID) {
+ throw new IllegalArgumentException("Only system process is allowed"
+ + "to set packageName");
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return JobSchedulerService.this.scheduleAsPackage(job, uid, packageName, userId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override
public List<JobInfo> getAllPendingJobs() throws RemoteException {
final int uid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 5935319e..c359c4d 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -24,6 +24,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -190,7 +191,7 @@
return false;
}
try {
- mBatteryStats.noteJobStart(job.getName(), job.getUid());
+ mBatteryStats.noteJobStart(job.getName(), job.getSourceUid());
} catch (RemoteException e) {
// Whatever.
}
@@ -566,7 +567,7 @@
}
completedJob = mRunningJob;
try {
- mBatteryStats.noteJobFinish(mRunningJob.getName(), mRunningJob.getUid());
+ mBatteryStats.noteJobFinish(mRunningJob.getName(), mRunningJob.getSourceUid());
} catch (RemoteException e) {
// Whatever.
}
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index a8cb19f..c88f5d7 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -345,6 +345,10 @@
out.attribute(null, "jobid", Integer.toString(jobStatus.getJobId()));
out.attribute(null, "package", jobStatus.getServiceComponent().getPackageName());
out.attribute(null, "class", jobStatus.getServiceComponent().getClassName());
+ if (jobStatus.getSourcePackageName() != null) {
+ out.attribute(null, "sourcePackageName", jobStatus.getSourcePackageName());
+ }
+ out.attribute(null, "sourceUserId", String.valueOf(jobStatus.getSourceUserId()));
out.attribute(null, "uid", Integer.toString(jobStatus.getUid()));
out.attribute(null, "priority", String.valueOf(jobStatus.getPriority()));
}
@@ -542,7 +546,7 @@
private JobStatus restoreJobFromXml(XmlPullParser parser) throws XmlPullParserException,
IOException {
JobInfo.Builder jobBuilder;
- int uid;
+ int uid, userId;
// Read out job identifier attributes and priority.
try {
@@ -550,15 +554,19 @@
jobBuilder.setPersisted(true);
uid = Integer.valueOf(parser.getAttributeValue(null, "uid"));
- String priority = parser.getAttributeValue(null, "priority");
- if (priority != null) {
- jobBuilder.setPriority(Integer.valueOf(priority));
+ String val = parser.getAttributeValue(null, "priority");
+ if (val != null) {
+ jobBuilder.setPriority(Integer.valueOf(val));
}
+ val = parser.getAttributeValue(null, "sourceUserId");
+ userId = val == null ? -1 : Integer.valueOf(val);
} catch (NumberFormatException e) {
Slog.e(TAG, "Error parsing job's required fields, skipping");
return null;
}
+ final String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
+
int eventType;
// Read out constraints tag.
do {
@@ -672,8 +680,12 @@
jobBuilder.setExtras(extras);
parser.nextTag(); // Consume </extras>
- return new JobStatus(
+ JobStatus js = new JobStatus(
jobBuilder.build(), uid, elapsedRuntimes.first, elapsedRuntimes.second);
+ if (userId != -1) {
+ js.setSource(sourcePackageName, userId);
+ }
+ return js;
}
private JobInfo.Builder buildBuilderFromXml(XmlPullParser parser) throws NumberFormatException {
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index 6fc02f6..c09e06c 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -65,9 +65,9 @@
public void maybeStartTrackingJob(JobStatus jobStatus) {
synchronized (mTrackedTasks) {
mTrackedTasks.add(jobStatus);
- String packageName = jobStatus.job.getService().getPackageName();
+ String packageName = jobStatus.getSourcePackageName();
final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
- jobStatus.uId, jobStatus.getUserId());
+ jobStatus.getSourceUid(), jobStatus.getSourceUserId());
if (DEBUG) {
Slog.d(LOG_TAG, "Start tracking, setting idle state of "
+ packageName + " to " + appIdle);
@@ -89,7 +89,7 @@
pw.println("Parole On: " + mAppIdleParoleOn);
synchronized (mTrackedTasks) {
for (JobStatus task : mTrackedTasks) {
- pw.print(task.job.getService().getPackageName());
+ pw.print(task.getSourcePackageName());
pw.print(":idle=" + !task.appNotIdleConstraintSatisfied.get());
pw.print(", ");
}
@@ -106,9 +106,9 @@
}
mAppIdleParoleOn = isAppIdleParoleOn;
for (JobStatus task : mTrackedTasks) {
- String packageName = task.job.getService().getPackageName();
+ String packageName = task.getSourcePackageName();
final boolean appIdle = !mAppIdleParoleOn && mUsageStatsInternal.isAppIdle(packageName,
- task.uId, task.getUserId());
+ task.getSourceUid(), task.getSourceUserId());
if (DEBUG) {
Slog.d(LOG_TAG, "Setting idle state of " + packageName + " to " + appIdle);
}
@@ -133,8 +133,8 @@
return;
}
for (JobStatus task : mTrackedTasks) {
- if (task.job.getService().getPackageName().equals(packageName)
- && task.getUserId() == userId) {
+ if (task.getSourcePackageName().equals(packageName)
+ && task.getSourceUserId() == userId) {
if (task.appNotIdleConstraintSatisfied.get() != !idle) {
if (DEBUG) {
Slog.d(LOG_TAG, "App Idle state changed, setting idle state of "
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 56d92d5..a621e6a 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -16,9 +16,11 @@
package com.android.server.job.controllers;
+import android.app.AppGlobals;
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.os.PersistableBundle;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateUtils;
@@ -47,6 +49,10 @@
final String name;
final String tag;
+ String sourcePackageName;
+ int sourceUserId = -1;
+ int sourceUid = -1;
+
// Constraints.
final AtomicBoolean chargingConstraintSatisfied = new AtomicBoolean();
final AtomicBoolean timeDelayConstraintSatisfied = new AtomicBoolean();
@@ -77,6 +83,7 @@
private JobStatus(JobInfo job, int uId, int numFailures) {
this.job = job;
this.uId = uId;
+ this.sourceUid = uId;
this.name = job.getService().flattenToShortString();
this.tag = "*job*/" + this.name;
this.numFailures = numFailures;
@@ -146,6 +153,21 @@
return job.getService();
}
+ public String getSourcePackageName() {
+ return sourcePackageName != null ? sourcePackageName : job.getService().getPackageName();
+ }
+
+ public int getSourceUid() {
+ return sourceUid;
+ }
+
+ public int getSourceUserId() {
+ if (sourceUserId == -1) {
+ sourceUserId = getUserId();
+ }
+ return sourceUserId;
+ }
+
public int getUserId() {
return UserHandle.getUserId(uId);
}
@@ -165,7 +187,7 @@
public PersistableBundle getExtras() {
return job.getExtras();
}
-
+
public int getPriority() {
return job.getPriority();
}
@@ -263,6 +285,22 @@
}
}
+ public void setSource(String sourcePackageName, int sourceUserId) {
+ this.sourcePackageName = sourcePackageName;
+ this.sourceUserId = sourceUserId;
+ try {
+ sourceUid = AppGlobals.getPackageManager().getPackageUid(sourcePackageName, 0,
+ sourceUserId);
+ } catch (RemoteException ex) {
+ // Can't happen, PackageManager runs in the same process.
+ }
+ if (sourceUid == -1) {
+ sourceUid = uId;
+ this.sourceUserId = getUserId();
+ this.sourcePackageName = null;
+ }
+ }
+
/**
* Convenience function to identify a job uniquely without pulling all the data that
* {@link #toString()} returns.
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 71d616d..d9667a1 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -425,7 +425,7 @@
}
/** Original bounds of the task if applicable, otherwise fullscreen rect. */
- public void getBounds(Rect out) {
+ void getBounds(Rect out) {
if (useCurrentBounds()) {
// No need to adjust the output bounds if fullscreen or the docked stack is visible
// since it is already what we want to represent to the rest of the system.
@@ -433,9 +433,8 @@
return;
}
- // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
- // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
- // system.
+ // The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
+ // not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
mStack.getDisplayContent().getLogicalDisplayRect(out);
}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 7748c6a..a75f2c9 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -485,7 +485,7 @@
}
}
- void getStackDockedModeBoundsLocked(Rect outBounds) {
+ void getStackDockedModeBoundsLocked(Rect outBounds, boolean ignoreVisibilityOnKeyguardShowing) {
if (!StackId.isResizeableByDockedStack(mStackId) || mDisplayContent == null) {
outBounds.set(mBounds);
return;
@@ -497,11 +497,11 @@
throw new IllegalStateException(
"Calling getStackDockedModeBoundsLocked() when there is no docked stack.");
}
- if (!dockedStack.isVisibleLocked()) {
+ if (!dockedStack.isVisibleLocked(ignoreVisibilityOnKeyguardShowing)) {
// The docked stack is being dismissed, but we caught before it finished being
// dismissed. In that case we want to treat it as if it is not occupying any space and
// let others occupy the whole display.
- mDisplayContent.getLogicalDisplayRect(mTmpRect);
+ mDisplayContent.getLogicalDisplayRect(outBounds);
return;
}
@@ -782,10 +782,19 @@
}
boolean isVisibleLocked() {
+ return isVisibleLocked(false);
+ }
+
+ boolean isVisibleLocked(boolean ignoreVisibilityOnKeyguardShowing) {
final boolean keyguardOn = mService.mPolicy.isKeyguardShowingOrOccluded();
if (keyguardOn && !StackId.isAllowedOverLockscreen(mStackId)) {
- return false;
+ // The keyguard is showing and the stack shouldn't show on top of the keyguard.
+ // We return false for visibility except in cases where the caller wants us to return
+ // true for visibility when the keyguard is showing. One example, is if the docked
+ // is being resized due to orientation while the keyguard is on.
+ return ignoreVisibilityOnKeyguardShowing;
}
+
for (int i = mTasks.size() - 1; i >= 0; i--) {
Task task = mTasks.get(i);
for (int j = task.mAppTokens.size() - 1; j >= 0; j--) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4fa3856..adc2da4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -4801,11 +4801,12 @@
}
}
- public void getStackDockedModeBounds(int stackId, Rect bounds) {
+ public void getStackDockedModeBounds(
+ int stackId, Rect bounds, boolean ignoreVisibilityOnKeyguardShowing) {
synchronized (mWindowMap) {
final TaskStack stack = mStackIdToStack.get(stackId);
if (stack != null) {
- stack.getStackDockedModeBoundsLocked(bounds);
+ stack.getStackDockedModeBoundsLocked(bounds, ignoreVisibilityOnKeyguardShowing);
return;
}
bounds.setEmpty();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ff86aae..e8a02b0 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1055,61 +1055,59 @@
}
/**
- * Is this window visible? It is not visible if there is no
- * surface, or we are in the process of running an exit animation
- * that will remove the surface, or its app token has been hidden.
+ * Does the minimal check for visibility. Callers generally want to use one of the public
+ * methods as they perform additional checks on the app token.
+ * TODO: See if there are other places we can use this check below instead of duplicating...
*/
- @Override
- public boolean isVisibleLw() {
- final AppWindowToken atoken = mAppToken;
+ private boolean isVisibleUnchecked() {
return mHasSurface && mPolicyVisibility && !mAttachedHidden
- && (atoken == null || !atoken.hiddenRequested)
- && !mExiting && !mDestroying;
+ && !mExiting && !mDestroying && (!mIsWallpaper || mWallpaperVisible);
}
/**
- * Like {@link #isVisibleLw}, but also counts a window that is currently
- * "hidden" behind the keyguard as visible. This allows us to apply
- * things like window flags that impact the keyguard.
- * XXX I am starting to think we need to have ANOTHER visibility flag
- * for this "hidden behind keyguard" state rather than overloading
- * mPolicyVisibility. Ungh.
+ * Is this window visible? It is not visible if there is no surface, or we are in the process
+ * of running an exit animation that will remove the surface, or its app token has been hidden.
+ */
+ @Override
+ public boolean isVisibleLw() {
+ return (mAppToken == null || !mAppToken.hiddenRequested) && isVisibleUnchecked();
+ }
+
+ /**
+ * Like {@link #isVisibleLw}, but also counts a window that is currently "hidden" behind the
+ * keyguard as visible. This allows us to apply things like window flags that impact the
+ * keyguard. XXX I am starting to think we need to have ANOTHER visibility flag for this
+ * "hidden behind keyguard" state rather than overloading mPolicyVisibility. Ungh.
*/
@Override
public boolean isVisibleOrBehindKeyguardLw() {
- if (mRootToken.waitingToShow &&
- mService.mAppTransition.isTransitionSet()) {
+ if (mRootToken.waitingToShow && mService.mAppTransition.isTransitionSet()) {
return false;
}
final AppWindowToken atoken = mAppToken;
final boolean animating = atoken != null && atoken.mAppAnimator.animation != null;
return mHasSurface && !mDestroying && !mExiting
&& (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested)
- && ((!mAttachedHidden && mViewVisibility == View.VISIBLE
- && !mRootToken.hidden)
+ && ((!mAttachedHidden && mViewVisibility == View.VISIBLE && !mRootToken.hidden)
|| mWinAnimator.mAnimation != null || animating);
}
/**
- * Is this window visible, ignoring its app token? It is not visible
- * if there is no surface, or we are in the process of running an exit animation
- * that will remove the surface.
+ * Is this window visible, ignoring its app token? It is not visible if there is no surface,
+ * or we are in the process of running an exit animation that will remove the surface.
*/
public boolean isWinVisibleLw() {
- final AppWindowToken atoken = mAppToken;
- return mHasSurface && mPolicyVisibility && !mAttachedHidden
- && (atoken == null || !atoken.hiddenRequested || atoken.mAppAnimator.animating)
- && !mExiting && !mDestroying;
+ return (mAppToken == null || !mAppToken.hiddenRequested || mAppToken.mAppAnimator.animating)
+ && isVisibleUnchecked();
}
/**
- * The same as isVisible(), but follows the current hidden state of
- * the associated app token, not the pending requested hidden state.
+ * The same as isVisible(), but follows the current hidden state of the associated app token,
+ * not the pending requested hidden state.
*/
boolean isVisibleNow() {
- return mHasSurface && mPolicyVisibility && !mAttachedHidden
- && (!mRootToken.hidden || mAttrs.type == TYPE_APPLICATION_STARTING)
- && !mExiting && !mDestroying;
+ return (!mRootToken.hidden || mAttrs.type == TYPE_APPLICATION_STARTING)
+ && isVisibleUnchecked();
}
/**
@@ -1168,8 +1166,7 @@
return false;
}
return mHasSurface && mPolicyVisibility && !mDestroying
- && ((!mAttachedHidden && mViewVisibility == View.VISIBLE
- && !mRootToken.hidden)
+ && ((!mAttachedHidden && mViewVisibility == View.VISIBLE && !mRootToken.hidden)
|| mWinAnimator.mAnimation != null
|| ((mAppToken != null) && (mAppToken.mAppAnimator.animation != null)));
}
@@ -1189,8 +1186,7 @@
return false;
}
return mHasSurface && !mDestroying
- && ((!mAttachedHidden && mViewVisibility == View.VISIBLE
- && !mRootToken.hidden)
+ && ((!mAttachedHidden && mViewVisibility == View.VISIBLE && !mRootToken.hidden)
|| mWinAnimator.mAnimation != null
|| ((atoken != null) && (atoken.mAppAnimator.animation != null)
&& !mWinAnimator.isDummyAnimation()));
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.
//