Merge "Code cleaning" into lmp-preview-dev
diff --git a/api/current.txt b/api/current.txt
index 73decfb..2bd4234 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1015,6 +1015,7 @@
field public static final int selectAllOnFocus = 16843102; // 0x101015e
field public static final int selectable = 16843238; // 0x10101e6
field public static final int selectableItemBackground = 16843534; // 0x101030e
+ field public static final int selectableItemBackgroundBorderless = 16843867; // 0x101045b
field public static final int selectedDateVerticalBar = 16843591; // 0x1010347
field public static final int selectedWeekBackgroundColor = 16843586; // 0x1010342
field public static final int sessionService = 16843841; // 0x1010441
@@ -5029,6 +5030,11 @@
method public boolean[] supportsCommands(java.lang.String[]);
}
+ public static class VoiceInteractor.AbortVoiceRequest extends android.app.VoiceInteractor.Request {
+ ctor public VoiceInteractor.AbortVoiceRequest(java.lang.CharSequence, android.os.Bundle);
+ method public void onAbortResult(android.os.Bundle);
+ }
+
public static class VoiceInteractor.CommandRequest extends android.app.VoiceInteractor.Request {
ctor public VoiceInteractor.CommandRequest(java.lang.String, android.os.Bundle);
method public void onCommandResult(android.os.Bundle);
@@ -6902,6 +6908,7 @@
method public abstract java.io.File[] getExternalCacheDirs();
method public abstract java.io.File getExternalFilesDir(java.lang.String);
method public abstract java.io.File[] getExternalFilesDirs(java.lang.String);
+ method public abstract java.io.File[] getExternalMediaDirs();
method public abstract java.io.File getFileStreamPath(java.lang.String);
method public abstract java.io.File getFilesDir();
method public abstract android.os.Looper getMainLooper();
@@ -7070,6 +7077,7 @@
method public java.io.File[] getExternalCacheDirs();
method public java.io.File getExternalFilesDir(java.lang.String);
method public java.io.File[] getExternalFilesDirs(java.lang.String);
+ method public java.io.File[] getExternalMediaDirs();
method public java.io.File getFileStreamPath(java.lang.String);
method public java.io.File getFilesDir();
method public android.os.Looper getMainLooper();
@@ -11463,6 +11471,7 @@
}
public class RippleDrawable extends android.graphics.drawable.LayerDrawable {
+ ctor public RippleDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
}
public class RotateDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
@@ -17145,86 +17154,6 @@
method public void setWorkSource(android.os.WorkSource);
}
- public class WifiScanner {
- method public void configureWifiChange(int, int, int, int, int, android.net.wifi.WifiScanner.HotspotInfo[]);
- method public void resetHotlist(android.net.wifi.WifiScanner.HotlistListener);
- method public void retrieveScanResults(boolean, android.net.wifi.WifiScanner.ScanListener);
- method public void setHotlist(android.net.wifi.WifiScanner.HotspotInfo[], int, android.net.wifi.WifiScanner.HotlistListener);
- method public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener);
- method public void startTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener);
- method public void stopBackgroundScan(android.net.wifi.WifiScanner.ScanListener);
- method public void stopTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener);
- field public static final int MAX_SCAN_PERIOD_MS = 1024000; // 0xfa000
- field public static final int MIN_SCAN_PERIOD_MS = 2000; // 0x7d0
- field public static final int REASON_CONFLICTING_REQUEST = -4; // 0xfffffffc
- field public static final int REASON_INVALID_LISTENER = -2; // 0xfffffffe
- field public static final int REASON_INVALID_REQUEST = -3; // 0xfffffffd
- field public static final int REASON_SUCCEEDED = 0; // 0x0
- field public static final int REASON_UNSPECIFIED = -1; // 0xffffffff
- field public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0; // 0x0
- field public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1; // 0x1
- field public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2; // 0x2
- field public static final int WIFI_BAND_24_GHZ = 1; // 0x1
- field public static final int WIFI_BAND_5_GHZ = 2; // 0x2
- field public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; // 0x4
- field public static final int WIFI_BAND_5_GHZ_WITH_DFS = 6; // 0x6
- field public static final int WIFI_BAND_BOTH = 3; // 0x3
- field public static final int WIFI_BAND_BOTH_WITH_DFS = 7; // 0x7
- field public static final int WIFI_BAND_UNSPECIFIED = 0; // 0x0
- }
-
- public static class WifiScanner.ChannelSpec {
- ctor public WifiScanner.ChannelSpec(int);
- field public int frequency;
- }
-
- public static class WifiScanner.FullScanResult implements android.os.Parcelable {
- ctor public WifiScanner.FullScanResult();
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public android.net.wifi.WifiScanner.InformationElement[] informationElements;
- field public android.net.wifi.ScanResult result;
- }
-
- public static abstract interface WifiScanner.HotlistListener {
- method public abstract void onFound(android.net.wifi.ScanResult[]);
- }
-
- public static class WifiScanner.HotspotInfo {
- ctor public WifiScanner.HotspotInfo();
- field public java.lang.String bssid;
- field public int frequencyHint;
- field public int high;
- field public int low;
- }
-
- public static class WifiScanner.InformationElement {
- ctor public WifiScanner.InformationElement();
- field public byte[] bytes;
- field public int id;
- }
-
- public static abstract interface WifiScanner.ScanListener {
- method public abstract void onFullResult(android.net.wifi.WifiScanner.FullScanResult);
- method public abstract void onPeriodChanged(int);
- method public abstract void onResults(android.net.wifi.ScanResult[]);
- }
-
- public static class WifiScanner.ScanSettings implements android.os.Parcelable {
- ctor public WifiScanner.ScanSettings();
- method public int describeContents();
- method public void writeToParcel(android.os.Parcel, int);
- field public int band;
- field public android.net.wifi.WifiScanner.ChannelSpec[] channels;
- field public int periodInMs;
- field public int reportEvents;
- }
-
- public static abstract interface WifiScanner.WifiChangeListener {
- method public abstract void onChanging(android.net.wifi.ScanResult[]);
- method public abstract void onQuiescence(android.net.wifi.ScanResult[]);
- }
-
public class WpsInfo implements android.os.Parcelable {
ctor public WpsInfo();
ctor public WpsInfo(android.net.wifi.WpsInfo);
@@ -26193,16 +26122,17 @@
method public android.view.LayoutInflater getLayoutInflater();
method public android.app.Dialog getWindow();
method public void hideWindow();
+ method public void onAbortVoice(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle);
method public void onBackPressed();
method public abstract void onCancel(android.service.voice.VoiceInteractionSession.Request);
method public void onCloseSystemDialogs();
method public abstract void onCommand(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle);
method public void onComputeInsets(android.service.voice.VoiceInteractionSession.Insets);
- method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle);
+ method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle);
method public void onCreate(android.os.Bundle);
method public android.view.View onCreateContentView();
method public void onDestroy();
- method public abstract boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]);
+ method public boolean[] onGetSupportedCommands(android.service.voice.VoiceInteractionSession.Caller, java.lang.String[]);
method public boolean onKeyDown(int, android.view.KeyEvent);
method public boolean onKeyLongPress(int, android.view.KeyEvent);
method public boolean onKeyMultiple(int, int, android.view.KeyEvent);
@@ -26229,6 +26159,7 @@
}
public static class VoiceInteractionSession.Request {
+ method public void sendAbortVoiceResult(android.os.Bundle);
method public void sendCancelResult();
method public void sendCommandResult(boolean, android.os.Bundle);
method public void sendConfirmResult(boolean, android.os.Bundle);
@@ -28268,6 +28199,7 @@
method public java.io.File[] getExternalCacheDirs();
method public java.io.File getExternalFilesDir(java.lang.String);
method public java.io.File[] getExternalFilesDirs(java.lang.String);
+ method public java.io.File[] getExternalMediaDirs();
method public java.io.File getFileStreamPath(java.lang.String);
method public java.io.File getFilesDir();
method public android.os.Looper getMainLooper();
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index f05f4c7..d4c4318 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -26,6 +26,7 @@
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.Gravity;
+import android.view.KeyEvent;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
@@ -1013,6 +1014,26 @@
return null;
}
+ /** @hide */
+ public boolean openOptionsMenu() {
+ return false;
+ }
+
+ /** @hide */
+ public boolean invalidateOptionsMenu() {
+ return false;
+ }
+
+ /** @hide */
+ public boolean onMenuKeyEvent(KeyEvent event) {
+ return false;
+ }
+
+ /** @hide */
+ public boolean collapseActionView() {
+ return false;
+ }
+
/**
* Listener interface for ActionBar navigation events.
*
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 5257430..23b5f29 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2075,15 +2075,16 @@
* <p>In order to use a Toolbar within the Activity's window content the application
* must not request the window feature {@link Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p>
*
- * @param actionBar Toolbar to set as the Activity's action bar
+ * @param toolbar Toolbar to set as the Activity's action bar
*/
- public void setActionBar(@Nullable Toolbar actionBar) {
+ public void setActionBar(@Nullable Toolbar toolbar) {
if (getActionBar() instanceof WindowDecorActionBar) {
throw new IllegalStateException("This Activity already has an action bar supplied " +
"by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
"android:windowActionBar to false in your theme to use a Toolbar instead.");
}
- mActionBar = new ToolbarActionBar(actionBar);
+ mActionBar = new ToolbarActionBar(toolbar, getTitle(), this);
+ mActionBar.invalidateOptionsMenu();
}
/**
@@ -2449,6 +2450,10 @@
* but you can override this to do whatever you want.
*/
public void onBackPressed() {
+ if (mActionBar != null && mActionBar.collapseActionView()) {
+ return;
+ }
+
if (!mFragments.popBackStackImmediate()) {
finishAfterTransition();
}
@@ -2660,6 +2665,14 @@
*/
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
+
+ // Let action bars open menus in response to the menu key prioritized over
+ // the window handling it
+ if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&
+ mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
+ return true;
+ }
+
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
@@ -2907,7 +2920,9 @@
* time it needs to be displayed.
*/
public void invalidateOptionsMenu() {
- mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+ if (mActionBar == null || !mActionBar.invalidateOptionsMenu()) {
+ mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+ }
}
/**
@@ -3117,7 +3132,9 @@
* open, this method does nothing.
*/
public void openOptionsMenu() {
- mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+ if (mActionBar == null || !mActionBar.openOptionsMenu()) {
+ mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+ }
}
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ff8688d..e66534b 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -58,7 +58,9 @@
import android.hardware.SerialManager;
import android.hardware.SystemSensorManager;
import android.hardware.hdmi.HdmiCecManager;
+import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.IHdmiCecService;
+import android.hardware.hdmi.IHdmiControlService;
import android.hardware.camera2.CameraManager;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputManager;
@@ -249,6 +251,8 @@
private File[] mExternalFilesDirs;
@GuardedBy("mSync")
private File[] mExternalCacheDirs;
+ @GuardedBy("mSync")
+ private File[] mExternalMediaDirs;
private static final String[] EMPTY_FILE_LIST = {};
@@ -386,6 +390,11 @@
return new HdmiCecManager(IHdmiCecService.Stub.asInterface(b));
}});
+ registerService(HDMI_CONTROL_SERVICE, new StaticServiceFetcher() {
+ public Object createStaticService() {
+ IBinder b = ServiceManager.getService(HDMI_CONTROL_SERVICE);
+ return new HdmiControlManager(IHdmiControlService.Stub.asInterface(b));
+ }});
registerService(CLIPBOARD_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
@@ -1032,6 +1041,18 @@
}
@Override
+ public File[] getExternalMediaDirs() {
+ synchronized (mSync) {
+ if (mExternalMediaDirs == null) {
+ mExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName());
+ }
+
+ // Create dirs if needed
+ return ensureDirsExistOrFilter(mExternalMediaDirs);
+ }
+ }
+
+ @Override
public File getFileStreamPath(String name) {
return makeFilename(getFilesDir(), name);
}
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index fe85ef4..f332c9d 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -33,7 +33,26 @@
import java.util.ArrayList;
/**
- * Interface for an {@link Activity} to interact with the user through voice.
+ * Interface for an {@link Activity} to interact with the user through voice. Use
+ * {@link android.app.Activity#getVoiceInteractor() Activity.getVoiceInteractor}
+ * to retrieve the interface, if the activity is currently involved in a voice interaction.
+ *
+ * <p>The voice interactor revolves around submitting voice interaction requests to the
+ * back-end voice interaction service that is working with the user. These requests are
+ * submitted with {@link #submitRequest}, providing a new instance of a
+ * {@link Request} subclass describing the type of operation to perform -- currently the
+ * possible requests are {@link ConfirmationRequest} and {@link CommandRequest}.
+ *
+ * <p>Once a request is submitted, the voice system will process it and evetually deliver
+ * the result to the request object. The application can cancel a pending request at any
+ * time.
+ *
+ * <p>The VoiceInteractor is integrated with Activity's state saving mechanism, so that
+ * if an activity is being restarted with retained state, it will retain the current
+ * VoiceInteractor and any outstanding requests. Because of this, you should always use
+ * {@link Request#getActivity() Request.getActivity} to get back to the activity of a
+ * request, rather than holding on to the actvitity instance yourself, either explicitly
+ * or implicitly through a non-static inner class.
*/
public class VoiceInteractor {
static final String TAG = "VoiceInteractor";
@@ -62,6 +81,16 @@
request.clear();
}
break;
+ case MSG_ABORT_VOICE_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
+ if (DEBUG) Log.d(TAG, "onAbortVoice: req="
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+ + " result=" + args.arg1);
+ if (request != null) {
+ ((AbortVoiceRequest)request).onAbortResult((Bundle) args.arg2);
+ request.clear();
+ }
+ break;
case MSG_COMMAND_RESULT:
request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0);
if (DEBUG) Log.d(TAG, "onCommandResult: req="
@@ -96,6 +125,12 @@
}
@Override
+ public void deliverAbortVoiceResult(IVoiceInteractorRequest request, Bundle result) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
+ MSG_ABORT_VOICE_RESULT, request, result));
+ }
+
+ @Override
public void deliverCommandResult(IVoiceInteractorRequest request, boolean complete,
Bundle result) {
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(
@@ -112,8 +147,9 @@
final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
static final int MSG_CONFIRMATION_RESULT = 1;
- static final int MSG_COMMAND_RESULT = 2;
- static final int MSG_CANCEL_RESULT = 3;
+ static final int MSG_ABORT_VOICE_RESULT = 2;
+ static final int MSG_COMMAND_RESULT = 3;
+ static final int MSG_CANCEL_RESULT = 4;
public static abstract class Request {
IVoiceInteractorRequest mRequestInterface;
@@ -188,9 +224,42 @@
IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
IVoiceInteractorCallback callback) throws RemoteException {
- return interactor.startConfirmation(packageName, callback, mPrompt.toString(), mExtras);
+ return interactor.startConfirmation(packageName, callback, mPrompt, mExtras);
}
- }
+ }
+
+ public static class AbortVoiceRequest extends Request {
+ final CharSequence mMessage;
+ final Bundle mExtras;
+
+ /**
+ * Reports that the current interaction can not be complete with voice, so the
+ * application will need to switch to a traditional input UI. Applications should
+ * only use this when they need to completely bail out of the voice interaction
+ * and switch to a traditional UI. When the resonsponse comes back, the voice
+ * system has handled the request and is ready to switch; at that point the application
+ * can start a new non-voice activity. Be sure when starting the new activity
+ * to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
+ * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice
+ * interaction task.
+ *
+ * @param message Optional message to tell user about not being able to complete
+ * the interaction with voice.
+ * @param extras Additional optional information.
+ */
+ public AbortVoiceRequest(CharSequence message, Bundle extras) {
+ mMessage = message;
+ mExtras = extras;
+ }
+
+ public void onAbortResult(Bundle result) {
+ }
+
+ IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+ IVoiceInteractorCallback callback) throws RemoteException {
+ return interactor.startAbortVoice(packageName, callback, mMessage, mExtras);
+ }
+ }
public static class CommandRequest extends Request {
final String mCommand;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 2ff85c6..d3a979c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -40,6 +40,7 @@
import android.os.StatFs;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.MediaStore;
import android.util.AttributeSet;
import android.view.DisplayAdjustments;
import android.view.Display;
@@ -929,6 +930,40 @@
public abstract File[] getExternalCacheDirs();
/**
+ * Returns absolute paths to application-specific directories on all
+ * external storage devices where the application can place media files.
+ * These files are scanned and made available to other apps through
+ * {@link MediaStore}.
+ * <p>
+ * This is like {@link #getExternalFilesDirs} in that these files will be
+ * deleted when the application is uninstalled, however there are some
+ * important differences:
+ * <ul>
+ * <li>External files are not always available: they will disappear if the
+ * user mounts the external storage on a computer or removes it.
+ * <li>There is no security enforced with these files.
+ * </ul>
+ * <p>
+ * External storage devices returned here are considered a permanent part of
+ * the device, including both emulated external storage and physical media
+ * slots, such as SD cards in a battery compartment. The returned paths do
+ * not include transient devices, such as USB flash drives.
+ * <p>
+ * An application may store data on any or all of the returned devices. For
+ * example, an app may choose to store large files on the device with the
+ * most available space, as measured by {@link StatFs}.
+ * <p>
+ * No permissions are required to read or write to the returned paths; they
+ * are always accessible to the calling app. Write access outside of these
+ * paths on secondary external storage devices is not available.
+ * <p>
+ * Returned paths may be {@code null} if a storage device is unavailable.
+ *
+ * @see Environment#getExternalStorageState(File)
+ */
+ public abstract File[] getExternalMediaDirs();
+
+ /**
* Returns an array of strings naming the private files associated with
* this Context's application package.
*
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index c66355b..dbf9122 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -237,6 +237,11 @@
}
@Override
+ public File[] getExternalMediaDirs() {
+ return mBase.getExternalMediaDirs();
+ }
+
+ @Override
public File getDir(String name, int mode) {
return mBase.getDir(name, mode);
}
diff --git a/core/java/android/hardware/hdmi/HdmiCecClient.java b/core/java/android/hardware/hdmi/HdmiCecClient.java
index cd86cd8..dcb3624 100644
--- a/core/java/android/hardware/hdmi/HdmiCecClient.java
+++ b/core/java/android/hardware/hdmi/HdmiCecClient.java
@@ -69,44 +69,28 @@
* Send <Active Source> message.
*/
public void sendActiveSource() {
- try {
- mService.sendActiveSource(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "sendActiveSource threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
* Send <Inactive Source> message.
*/
public void sendInactiveSource() {
- try {
- mService.sendInactiveSource(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "sendInactiveSource threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
* Send <Text View On> message.
*/
public void sendTextViewOn() {
- try {
- mService.sendTextViewOn(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "sendTextViewOn threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
* Send <Image View On> message.
*/
public void sendImageViewOn() {
- try {
- mService.sendImageViewOn(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "sendImageViewOn threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
@@ -116,11 +100,7 @@
* {@link HdmiCec#ADDR_TV}.
*/
public void sendGiveDevicePowerStatus(int address) {
- try {
- mService.sendGiveDevicePowerStatus(mBinder, address);
- } catch (RemoteException e) {
- Log.e(TAG, "sendGiveDevicePowerStatus threw exception ", e);
- }
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
}
/**
@@ -133,11 +113,7 @@
* @return true if TV is on; otherwise false.
*/
public boolean isTvOn() {
- try {
- return mService.isTvOn(mBinder);
- } catch (RemoteException e) {
- Log.e(TAG, "isTvOn threw exception ", e);
- }
- return false;
+ Log.w(TAG, "In transition to HdmiControlManager. Will not work.");
+ return true;
}
}
diff --git a/core/java/android/hardware/hdmi/HdmiCecManager.java b/core/java/android/hardware/hdmi/HdmiCecManager.java
index 10b058c..03c46d8 100644
--- a/core/java/android/hardware/hdmi/HdmiCecManager.java
+++ b/core/java/android/hardware/hdmi/HdmiCecManager.java
@@ -45,15 +45,7 @@
* @return {@link HdmiCecClient} instance. {@code null} on failure.
*/
public HdmiCecClient getClient(int type, HdmiCecClient.Listener listener) {
- if (mService == null) {
- return null;
- }
- try {
- IBinder b = mService.allocateLogicalDevice(type, getListenerWrapper(listener));
- return HdmiCecClient.create(mService, b);
- } catch (RemoteException e) {
- return null;
- }
+ return HdmiCecClient.create(mService, null);
}
private IHdmiCecListener getListenerWrapper(final HdmiCecClient.Listener listener) {
diff --git a/core/java/android/hardware/hdmi/HdmiCecMessage.java b/core/java/android/hardware/hdmi/HdmiCecMessage.java
index ddaf870..62fa279 100644
--- a/core/java/android/hardware/hdmi/HdmiCecMessage.java
+++ b/core/java/android/hardware/hdmi/HdmiCecMessage.java
@@ -46,7 +46,7 @@
public HdmiCecMessage(int source, int destination, int opcode, byte[] params) {
mSource = source;
mDestination = destination;
- mOpcode = opcode;
+ mOpcode = opcode & 0xFF;
mParams = Arrays.copyOf(params, params.length);
}
@@ -123,6 +123,7 @@
* @param p HdmiCecMessage object to read the Rating from
* @return a new HdmiCecMessage created from the data in the parcel
*/
+ @Override
public HdmiCecMessage createFromParcel(Parcel p) {
int source = p.readInt();
int destination = p.readInt();
@@ -131,6 +132,7 @@
p.readByteArray(params);
return new HdmiCecMessage(source, destination, opcode, params);
}
+ @Override
public HdmiCecMessage[] newArray(int size) {
return new HdmiCecMessage[size];
}
@@ -139,11 +141,40 @@
@Override
public String toString() {
StringBuffer s = new StringBuffer();
- s.append(String.format("src: %d dst: %d op: %2X params: ", mSource, mDestination, mOpcode));
- for (byte data : mParams) {
- s.append(String.format("%02X ", data));
+ s.append(String.format("<%s> src: %d, dst: %d",
+ opcodeToString(mOpcode), mSource, mDestination));
+ if (mParams.length > 0) {
+ s.append(", params:");
+ for (byte data : mParams) {
+ s.append(String.format(" %02X", data));
+ }
}
return s.toString();
}
+
+ private static String opcodeToString(int opcode) {
+ switch (opcode) {
+ case HdmiCec.MESSAGE_FEATURE_ABORT:
+ return "Feature Abort";
+ case HdmiCec.MESSAGE_CEC_VERSION:
+ return "CEC Version";
+ case HdmiCec.MESSAGE_REQUEST_ARC_INITIATION:
+ return "Request ARC Initiation";
+ case HdmiCec.MESSAGE_REQUEST_ARC_TERMINATION:
+ return "Request ARC Termination";
+ case HdmiCec.MESSAGE_REPORT_ARC_INITIATED:
+ return "Report ARC Initiated";
+ case HdmiCec.MESSAGE_REPORT_ARC_TERMINATED:
+ return "Report ARC Terminated";
+ case HdmiCec.MESSAGE_TEXT_VIEW_ON:
+ return "Text View On";
+ case HdmiCec.MESSAGE_ACTIVE_SOURCE:
+ return "Active Source";
+ case HdmiCec.MESSAGE_GIVE_DEVICE_POWER_STATUS:
+ return "Give Device Power Status";
+ default:
+ return String.format("Opcode: %02X", opcode);
+ }
+ }
}
diff --git a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
index 83da29a..f0bd237 100644
--- a/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
+++ b/core/java/android/hardware/hdmi/HdmiPlaybackClient.java
@@ -90,7 +90,7 @@
public void queryDisplayStatus(DisplayStatusCallback callback) {
// TODO: PendingResult.
try {
- mService.oneTouchPlay(getCallbackWrapper(callback));
+ mService.queryDisplayStatus(getCallbackWrapper(callback));
} catch (RemoteException e) {
Log.e(TAG, "queryDisplayStatus threw exception ", e);
}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index e98a26b..e84b695 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -191,6 +191,10 @@
return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName);
}
+ public File[] buildExternalStorageAppMediaDirsForVold(String packageName) {
+ return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_MEDIA, packageName);
+ }
+
public File[] buildExternalStorageAppObbDirs(String packageName) {
return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName);
}
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 5e005d0..d66fc0f 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -16,7 +16,10 @@
package android.preference;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.Ringtone;
@@ -45,11 +48,14 @@
private final Context mContext;
private final Handler mHandler;
+ private final H mUiHandler = new H();
private final Callback mCallback;
private final Uri mDefaultUri;
private final AudioManager mAudioManager;
private final int mStreamType;
private final int mMaxStreamVolume;
+ private final Receiver mReceiver = new Receiver();
+ private final Observer mVolumeObserver;
private int mOriginalStreamVolume;
private Ringtone mRingtone;
@@ -63,17 +69,6 @@
private static final int MSG_INIT_SAMPLE = 3;
private static final int CHECK_RINGTONE_PLAYBACK_DELAY_MS = 1000;
- private ContentObserver mVolumeObserver = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- if (mSeekBar != null && mAudioManager != null) {
- int volume = mAudioManager.getStreamVolume(mStreamType);
- mSeekBar.setProgress(volume);
- }
- }
- };
-
public SeekBarVolumizer(Context context, int streamType, Uri defaultUri,
Callback callback) {
mContext = context;
@@ -85,10 +80,11 @@
mHandler = new Handler(thread.getLooper(), this);
mCallback = callback;
mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
+ mVolumeObserver = new Observer(mHandler);
mContext.getContentResolver().registerContentObserver(
System.getUriFor(System.VOLUME_SETTINGS[mStreamType]),
false, mVolumeObserver);
-
+ mReceiver.setListening(true);
if (defaultUri == null) {
if (mStreamType == AudioManager.STREAM_RING) {
defaultUri = Settings.System.DEFAULT_RINGTONE_URI;
@@ -103,6 +99,9 @@
}
public void setSeekBar(SeekBar seekBar) {
+ if (mSeekBar != null) {
+ mSeekBar.setOnSeekBarChangeListener(null);
+ }
mSeekBar = seekBar;
mSeekBar.setOnSeekBarChangeListener(null);
mSeekBar.setMax(mMaxStreamVolume);
@@ -150,7 +149,11 @@
mCallback.onSampleStarting(this);
}
if (mRingtone != null) {
- mRingtone.play();
+ try {
+ mRingtone.play();
+ } catch (Throwable e) {
+ Log.w(TAG, "Error playing ringtone, stream " + mStreamType, e);
+ }
}
}
}
@@ -172,6 +175,8 @@
postStopSample();
mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
mSeekBar.setOnSeekBarChangeListener(null);
+ mReceiver.setListening(false);
+ mHandler.getLooper().quitSafely();
}
public void revertVolume() {
@@ -252,4 +257,62 @@
postSetVolume(mLastProgress);
}
}
-}
\ No newline at end of file
+
+ private final class H extends Handler {
+ private static final int UPDATE_SLIDER = 1;
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == UPDATE_SLIDER) {
+ if (mSeekBar != null) {
+ mSeekBar.setProgress(msg.arg1);
+ mLastProgress = mSeekBar.getProgress();
+ }
+ }
+ }
+
+ public void postUpdateSlider(int volume) {
+ obtainMessage(UPDATE_SLIDER, volume, 0).sendToTarget();
+ }
+ }
+
+ private final class Observer extends ContentObserver {
+ public Observer(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ if (mSeekBar != null && mAudioManager != null) {
+ final int volume = mAudioManager.getStreamVolume(mStreamType);
+ mUiHandler.postUpdateSlider(volume);
+ }
+ }
+ }
+
+ private final class Receiver extends BroadcastReceiver {
+ private boolean mListening;
+
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (listening) {
+ final IntentFilter filter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+ mContext.registerReceiver(this, filter);
+ } else {
+ mContext.unregisterReceiver(this);
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!AudioManager.VOLUME_CHANGED_ACTION.equals(intent.getAction())) return;
+ final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ final int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
+ if (mSeekBar != null && streamType == mStreamType && streamValue != -1) {
+ mUiHandler.postUpdateSlider(streamValue);
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 1e29f8e..2e9077a 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -47,9 +47,22 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import java.lang.ref.WeakReference;
+
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+/**
+ * An active voice interaction session, providing a facility for the implementation
+ * to interact with the user in the voice interaction layer. This interface is no shown
+ * by default, but you can request that it be shown with {@link #showWindow()}, which
+ * will result in a later call to {@link #onCreateContentView()} in which the UI can be
+ * built
+ *
+ * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish}
+ * when done. It can also initiate voice interactions with applications by calling
+ * {@link #startVoiceActivity}</p>.
+ */
public abstract class VoiceInteractionSession implements KeyEvent.Callback {
static final String TAG = "VoiceInteractionSession";
static final boolean DEBUG = true;
@@ -80,11 +93,14 @@
final Insets mTmpInsets = new Insets();
final int[] mTmpLocation = new int[2];
+ final WeakReference<VoiceInteractionSession> mWeakRef
+ = new WeakReference<VoiceInteractionSession>(this);
+
final IVoiceInteractor mInteractor = new IVoiceInteractor.Stub() {
@Override
public IVoiceInteractorRequest startConfirmation(String callingPackage,
- IVoiceInteractorCallback callback, String prompt, Bundle extras) {
- Request request = findRequest(callback, true);
+ IVoiceInteractorCallback callback, CharSequence prompt, Bundle extras) {
+ Request request = newRequest(callback);
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_CONFIRMATION,
new Caller(callingPackage, Binder.getCallingUid()), request,
prompt, extras));
@@ -92,9 +108,19 @@
}
@Override
+ public IVoiceInteractorRequest startAbortVoice(String callingPackage,
+ IVoiceInteractorCallback callback, CharSequence message, Bundle extras) {
+ Request request = newRequest(callback);
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_ABORT_VOICE,
+ new Caller(callingPackage, Binder.getCallingUid()), request,
+ message, extras));
+ return request.mInterface;
+ }
+
+ @Override
public IVoiceInteractorRequest startCommand(String callingPackage,
IVoiceInteractorCallback callback, String command, Bundle extras) {
- Request request = findRequest(callback, true);
+ Request request = newRequest(callback);
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMMAND,
new Caller(callingPackage, Binder.getCallingUid()), request,
command, extras));
@@ -143,29 +169,60 @@
final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() {
@Override
public void cancel() throws RemoteException {
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
+ VoiceInteractionSession session = mSession.get();
+ if (session != null) {
+ session.mHandlerCaller.sendMessage(
+ session.mHandlerCaller.obtainMessageO(MSG_CANCEL, Request.this));
+ }
}
};
final IVoiceInteractorCallback mCallback;
- final HandlerCaller mHandlerCaller;
- Request(IVoiceInteractorCallback callback, HandlerCaller handlerCaller) {
+ final WeakReference<VoiceInteractionSession> mSession;
+
+ Request(IVoiceInteractorCallback callback, VoiceInteractionSession session) {
mCallback = callback;
- mHandlerCaller = handlerCaller;
+ mSession = session.mWeakRef;
+ }
+
+ void finishRequest() {
+ VoiceInteractionSession session = mSession.get();
+ if (session == null) {
+ throw new IllegalStateException("VoiceInteractionSession has been destroyed");
+ }
+ Request req = session.removeRequest(mInterface.asBinder());
+ if (req == null) {
+ throw new IllegalStateException("Request not active: " + this);
+ } else if (req != this) {
+ throw new IllegalStateException("Current active request " + req
+ + " not same as calling request " + this);
+ }
}
public void sendConfirmResult(boolean confirmed, Bundle result) {
try {
if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
+ " confirmed=" + confirmed + " result=" + result);
+ finishRequest();
mCallback.deliverConfirmationResult(mInterface, confirmed, result);
} catch (RemoteException e) {
}
}
+ public void sendAbortVoiceResult(Bundle result) {
+ try {
+ if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
+ + " result=" + result);
+ finishRequest();
+ mCallback.deliverAbortVoiceResult(mInterface, result);
+ } catch (RemoteException e) {
+ }
+ }
+
public void sendCommandResult(boolean complete, Bundle result) {
try {
if (DEBUG) Log.d(TAG, "sendCommandResult: req=" + mInterface
+ " result=" + result);
+ finishRequest();
mCallback.deliverCommandResult(mInterface, complete, result);
} catch (RemoteException e) {
}
@@ -174,6 +231,7 @@
public void sendCancelResult() {
try {
if (DEBUG) Log.d(TAG, "sendCancelResult: req=" + mInterface);
+ finishRequest();
mCallback.deliverCancel(mInterface);
} catch (RemoteException e) {
}
@@ -191,9 +249,10 @@
}
static final int MSG_START_CONFIRMATION = 1;
- static final int MSG_START_COMMAND = 2;
- static final int MSG_SUPPORTS_COMMANDS = 3;
- static final int MSG_CANCEL = 4;
+ static final int MSG_START_ABORT_VOICE = 2;
+ static final int MSG_START_COMMAND = 3;
+ static final int MSG_SUPPORTS_COMMANDS = 4;
+ static final int MSG_CANCEL = 5;
static final int MSG_TASK_STARTED = 100;
static final int MSG_TASK_FINISHED = 101;
@@ -209,9 +268,16 @@
args = (SomeArgs)msg.obj;
if (DEBUG) Log.d(TAG, "onConfirm: req=" + ((Request) args.arg2).mInterface
+ " prompt=" + args.arg3 + " extras=" + args.arg4);
- onConfirm((Caller)args.arg1, (Request)args.arg2, (String)args.arg3,
+ onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3,
(Bundle)args.arg4);
break;
+ case MSG_START_ABORT_VOICE:
+ args = (SomeArgs)msg.obj;
+ if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((Request) args.arg2).mInterface
+ + " message=" + args.arg3 + " extras=" + args.arg4);
+ onAbortVoice((Caller) args.arg1, (Request) args.arg2, (CharSequence) args.arg3,
+ (Bundle) args.arg4);
+ break;
case MSG_START_COMMAND:
args = (SomeArgs)msg.obj;
if (DEBUG) Log.d(TAG, "onCommand: req=" + ((Request) args.arg2).mInterface
@@ -329,18 +395,20 @@
mCallbacks, true);
}
- Request findRequest(IVoiceInteractorCallback callback, boolean newRequest) {
+ Request newRequest(IVoiceInteractorCallback callback) {
synchronized (this) {
- Request req = mActiveRequests.get(callback.asBinder());
+ Request req = new Request(callback, this);
+ mActiveRequests.put(req.mInterface.asBinder(), req);
+ return req;
+ }
+ }
+
+ Request removeRequest(IBinder reqInterface) {
+ synchronized (this) {
+ Request req = mActiveRequests.get(reqInterface);
if (req != null) {
- if (newRequest) {
- throw new IllegalArgumentException("Given request callback " + callback
- + " is already active");
- }
- return req;
+ mActiveRequests.remove(req);
}
- req = new Request(callback, mHandlerCaller);
- mActiveRequests.put(callback.asBinder(), req);
return req;
}
}
@@ -425,6 +493,27 @@
mTheme = theme;
}
+ /**
+ * Ask that a new activity be started for voice interaction. This will create a
+ * new dedicated task in the activity manager for this voice interaction session;
+ * this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK}
+ * will be set for you to make it a new task.
+ *
+ * <p>The newly started activity will be displayed to the user in a special way, as
+ * a layer under the voice interaction UI.</p>
+ *
+ * <p>As the voice activity runs, it can retrieve a {@link android.app.VoiceInteractor}
+ * through which it can perform voice interactions through your session. These requests
+ * for voice interactions will appear as callbacks on {@link #onGetSupportedCommands},
+ * {@link #onConfirm}, {@link #onCommand}, and {@link #onCancel}.
+ *
+ * <p>You will receive a call to {@link #onTaskStarted} when the task starts up
+ * and {@link #onTaskFinished} when the last activity has finished.
+ *
+ * @param intent The Intent to start this voice interaction. The given Intent will
+ * always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since
+ * this is part of a voice interaction.
+ */
public void startVoiceActivity(Intent intent) {
if (mToken == null) {
throw new IllegalStateException("Can't call before onCreate()");
@@ -439,14 +528,23 @@
}
}
+ /**
+ * Convenience for inflating views.
+ */
public LayoutInflater getLayoutInflater() {
return mInflater;
}
+ /**
+ * Retrieve the window being used to show the session's UI.
+ */
public Dialog getWindow() {
return mWindow;
}
+ /**
+ * Finish the session.
+ */
public void finish() {
if (mToken == null) {
throw new IllegalStateException("Can't call before onCreate()");
@@ -458,6 +556,12 @@
}
}
+ /**
+ * Initiatize a new session.
+ *
+ * @param args The arguments that were supplied to
+ * {@link VoiceInteractionService#startSession VoiceInteractionService.startSession}.
+ */
public void onCreate(Bundle args) {
mTheme = mTheme != 0 ? mTheme
: com.android.internal.R.style.Theme_DeviceDefault_VoiceInteractionSession;
@@ -472,9 +576,15 @@
mWindow.setToken(mToken);
}
+ /**
+ * Last callback to the session as it is being finished.
+ */
public void onDestroy() {
}
+ /**
+ * Hook in which to create the session's UI.
+ */
public View onCreateContentView() {
return null;
}
@@ -507,6 +617,11 @@
finish();
}
+ /**
+ * Sessions automatically watch for requests that all system UI be closed (such as when
+ * the user presses HOME), which will appear here. The default implementation always
+ * calls {@link #finish}.
+ */
public void onCloseSystemDialogs() {
finish();
}
@@ -530,15 +645,98 @@
outInsets.touchableRegion.setEmpty();
}
+ /**
+ * Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)}
+ * has actually started.
+ *
+ * @param intent The original {@link Intent} supplied to
+ * {@link #startVoiceActivity(android.content.Intent)}.
+ * @param taskId Unique ID of the now running task.
+ */
public void onTaskStarted(Intent intent, int taskId) {
}
+ /**
+ * Called when the last activity of a task initiated by
+ * {@link #startVoiceActivity(android.content.Intent)} has finished. The default
+ * implementation calls {@link #finish()} on the assumption that this represents
+ * the completion of a voice action. You can override the implementation if you would
+ * like a different behavior.
+ *
+ * @param intent The original {@link Intent} supplied to
+ * {@link #startVoiceActivity(android.content.Intent)}.
+ * @param taskId Unique ID of the finished task.
+ */
public void onTaskFinished(Intent intent, int taskId) {
finish();
}
- public abstract boolean[] onGetSupportedCommands(Caller caller, String[] commands);
- public abstract void onConfirm(Caller caller, Request request, String prompt, Bundle extras);
+ /**
+ * Request to query for what extended commands the session supports.
+ *
+ * @param caller Who is making the request.
+ * @param commands An array of commands that are being queried.
+ * @return Return an array of booleans indicating which of each entry in the
+ * command array is supported. A true entry in the array indicates the command
+ * is supported; false indicates it is not. The default implementation returns
+ * an array of all false entries.
+ */
+ public boolean[] onGetSupportedCommands(Caller caller, String[] commands) {
+ return new boolean[commands.length];
+ }
+
+ /**
+ * Request to confirm with the user before proceeding with an unrecoverable operation,
+ * corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest
+ * VoiceInteractor.ConfirmationRequest}.
+ *
+ * @param caller Who is making the request.
+ * @param request The active request.
+ * @param prompt The prompt informing the user of what will happen, as per
+ * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}.
+ * @param extras Any additional information, as per
+ * {@link android.app.VoiceInteractor.ConfirmationRequest VoiceInteractor.ConfirmationRequest}.
+ */
+ public abstract void onConfirm(Caller caller, Request request, CharSequence prompt,
+ Bundle extras);
+
+ /**
+ * Request to abort the voice interaction session because the voice activity can not
+ * complete its interaction using voice. Corresponds to
+ * {@link android.app.VoiceInteractor.AbortVoiceRequest
+ * VoiceInteractor.AbortVoiceRequest}. The default implementation just sends an empty
+ * confirmation back to allow the activity to exit.
+ *
+ * @param caller Who is making the request.
+ * @param request The active request.
+ * @param message The message informing the user of the problem, as per
+ * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
+ * @param extras Any additional information, as per
+ * {@link android.app.VoiceInteractor.AbortVoiceRequest VoiceInteractor.AbortVoiceRequest}.
+ */
+ public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) {
+ request.sendAbortVoiceResult(null);
+ }
+
+ /**
+ * Process an arbitrary extended command from the caller,
+ * corresponding to a {@link android.app.VoiceInteractor.CommandRequest
+ * VoiceInteractor.CommandRequest}.
+ *
+ * @param caller Who is making the request.
+ * @param request The active request.
+ * @param command The command that is being executed, as per
+ * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
+ * @param extras Any additional information, as per
+ * {@link android.app.VoiceInteractor.CommandRequest VoiceInteractor.CommandRequest}.
+ */
public abstract void onCommand(Caller caller, Request request, String command, Bundle extras);
+
+ /**
+ * Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request}
+ * that was previously delivered to {@link #onConfirm} or {@link #onCommand}.
+ *
+ * @param request The request that is being canceled.
+ */
public abstract void onCancel(Request request);
}
diff --git a/core/java/android/tv/ITvInputClient.aidl b/core/java/android/tv/ITvInputClient.aidl
index ac83356..ef89c68 100644
--- a/core/java/android/tv/ITvInputClient.aidl
+++ b/core/java/android/tv/ITvInputClient.aidl
@@ -17,6 +17,7 @@
package android.tv;
import android.content.ComponentName;
+import android.os.Bundle;
import android.tv.ITvInputSession;
import android.view.InputChannel;
@@ -29,4 +30,6 @@
void onSessionCreated(in String inputId, IBinder token, in InputChannel channel, int seq);
void onAvailabilityChanged(in String inputId, boolean isAvailable);
void onSessionReleased(int seq);
+ void onSessionEvent(in String name, in Bundle args, int seq);
+ void onVideoSizeChanged(int width, int height, int seq);
}
diff --git a/core/java/android/tv/ITvInputSessionCallback.aidl b/core/java/android/tv/ITvInputSessionCallback.aidl
index a2bd0d7..e27b8bf 100644
--- a/core/java/android/tv/ITvInputSessionCallback.aidl
+++ b/core/java/android/tv/ITvInputSessionCallback.aidl
@@ -16,6 +16,7 @@
package android.tv;
+import android.os.Bundle;
import android.tv.ITvInputSession;
/**
@@ -25,4 +26,6 @@
*/
oneway interface ITvInputSessionCallback {
void onSessionCreated(ITvInputSession session);
+ void onSessionEvent(in String name, in Bundle args);
+ void onVideoSizeChanged(int width, int height);
}
diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java
index dfa84f8..d0c2ca6 100644
--- a/core/java/android/tv/TvInputManager.java
+++ b/core/java/android/tv/TvInputManager.java
@@ -18,6 +18,7 @@
import android.graphics.Rect;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -85,6 +86,29 @@
*/
public void onSessionReleased(Session session) {
}
+
+ /**
+ * This is called at the beginning of the playback of a channel and later when the size of
+ * the video has been changed.
+ *
+ * @param session A {@link TvInputManager.Session} associated with this callback
+ * @param width the width of the video
+ * @param height the height of the video
+ * @hide
+ */
+ public void onVideoSizeChanged(Session session, int width, int height) {
+ }
+
+ /**
+ * This is called when a custom event has been sent from this session.
+ *
+ * @param session A {@link TvInputManager.Session} associated with this callback
+ * @param eventType The type of the event.
+ * @param eventArgs Optional arguments of the event.
+ * @hide
+ */
+ public void onSessionEvent(Session session, String eventType, Bundle eventArgs) {
+ }
}
private static final class SessionCallbackRecord {
@@ -116,6 +140,24 @@
}
});
}
+
+ public void postVideoSizeChanged(final int width, final int height) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onVideoSizeChanged(mSession, width, height);
+ }
+ });
+ }
+
+ public void postSessionEvent(final String eventType, final Bundle eventArgs) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onSessionEvent(mSession, eventType, eventArgs);
+ }
+ });
+ }
}
/**
@@ -196,6 +238,30 @@
}
@Override
+ public void onVideoSizeChanged(int width, int height, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postVideoSizeChanged(width, height);
+ }
+ }
+
+ @Override
+ public void onSessionEvent(String eventType, Bundle eventArgs, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postSessionEvent(eventType, eventArgs);
+ }
+ }
+
+ @Override
public void onAvailabilityChanged(String inputId, boolean isAvailable) {
synchronized (mTvInputListenerRecordsMap) {
List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java
index cb0142f..03d24db 100644
--- a/core/java/android/tv/TvInputService.java
+++ b/core/java/android/tv/TvInputService.java
@@ -23,6 +23,7 @@
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -156,6 +157,7 @@
private boolean mOverlayViewEnabled;
private IBinder mWindowToken;
private Rect mOverlayFrame;
+ private ITvInputSessionCallback mSessionCallback;
public TvInputSessionImpl() {
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
@@ -188,6 +190,52 @@
}
/**
+ * Dispatches an event to the application using this session.
+ *
+ * @param eventType The type of the event.
+ * @param eventArgs Optional arguments of the event.
+ * @hide
+ */
+ public void dispatchSessionEvent(final String eventType, final Bundle eventArgs) {
+ if (eventType == null) {
+ throw new IllegalArgumentException("eventType should not be null.");
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "dispatchSessionEvent(" + eventType + ")");
+ mSessionCallback.onSessionEvent(eventType, eventArgs);
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in sending event (event=" + eventType + ")");
+ }
+ }
+ });
+ }
+
+ /**
+ * Sends the change on the size of the video. This is expected to be called at the
+ * beginning of the playback and later when the size has been changed.
+ *
+ * @param width The width of the video.
+ * @param height The height of the video.
+ * @hide
+ */
+ public void dispatchVideoSizeChanged(final int width, final int height) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "dispatchVideoSizeChanged");
+ mSessionCallback.onVideoSizeChanged(width, height);
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in dispatchVideoSizeChanged");
+ }
+ }
+ });
+ }
+
+ /**
* Called when the session is released.
*/
public abstract void onRelease();
@@ -394,9 +442,7 @@
mWindowManager.removeView(mOverlayView);
mOverlayView = null;
}
- if (DEBUG) {
- Log.d(TAG, "create overlay view(" + frame + ")");
- }
+ if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
mWindowToken = windowToken;
mOverlayFrame = frame;
if (!mOverlayViewEnabled) {
@@ -431,9 +477,7 @@
* @param frame A new position of the overlay view.
*/
void relayoutOverlayView(Rect frame) {
- if (DEBUG) {
- Log.d(TAG, "relayout overlay view(" + frame + ")");
- }
+ if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")");
mOverlayFrame = frame;
if (!mOverlayViewEnabled || mOverlayView == null) {
return;
@@ -449,9 +493,7 @@
* Removes the current overlay view.
*/
void removeOverlayView(boolean clearWindowToken) {
- if (DEBUG) {
- Log.d(TAG, "remove overlay view(" + mOverlayView + ")");
- }
+ if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayView + ")");
if (clearWindowToken) {
mWindowToken = null;
mOverlayFrame = null;
@@ -498,6 +540,10 @@
mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
return Session.DISPATCH_IN_PROGRESS;
}
+
+ private void setSessionCallback(ITvInputSessionCallback callback) {
+ mSessionCallback = callback;
+ }
}
private final class ServiceHandler extends Handler {
@@ -517,6 +563,7 @@
// Failed to create a session.
cb.onSessionCreated(null);
} else {
+ sessionImpl.setSessionCallback(cb);
ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
sessionImpl, channel);
cb.onSessionCreated(stub);
diff --git a/core/java/android/tv/TvView.java b/core/java/android/tv/TvView.java
index 59b6386..2d31701 100644
--- a/core/java/android/tv/TvView.java
+++ b/core/java/android/tv/TvView.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.tv.TvInputManager.Session;
@@ -379,5 +380,23 @@
mExternalCallback.onSessionReleased(session);
}
}
+
+ @Override
+ public void onVideoSizeChanged(Session session, int width, int height) {
+ if (DEBUG) {
+ Log.d(TAG, "onVideoSizeChanged(" + width + ", " + height + ")");
+ }
+ if (mExternalCallback != null) {
+ mExternalCallback.onVideoSizeChanged(session, width, height);
+ }
+ }
+
+ @Override
+ public void onSessionEvent(TvInputManager.Session session, String eventType,
+ Bundle eventArgs) {
+ if (mExternalCallback != null) {
+ mExternalCallback.onSessionEvent(session, eventType, eventArgs);
+ }
+ }
}
}
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index e918119..4979059 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -219,6 +219,15 @@
return mTarget;
}
+ /**
+ * WARNING: May only be called once!!!
+ * TODO: Fix above -_-
+ */
+ public void setStartValue(float startValue) {
+ checkMutable();
+ nSetStartValue(mNativePtr.get(), startValue);
+ }
+
@Override
public void setStartDelay(long startDelay) {
checkMutable();
@@ -282,11 +291,12 @@
}
private static native long nCreateAnimator(WeakReference<RenderNodeAnimator> weakThis,
- int property, float deltaValue);
+ int property, float finalValue);
private static native long nCreateCanvasPropertyFloatAnimator(WeakReference<RenderNodeAnimator> weakThis,
- long canvasProperty, float deltaValue);
+ long canvasProperty, float finalValue);
private static native long nCreateCanvasPropertyPaintAnimator(WeakReference<RenderNodeAnimator> weakThis,
- long canvasProperty, int paintField, float deltaValue);
+ long canvasProperty, int paintField, float finalValue);
+ private static native void nSetStartValue(long nativePtr, float startValue);
private static native void nSetDuration(long nativePtr, long duration);
private static native long nGetDuration(long nativePtr);
private static native void nSetStartDelay(long nativePtr, long startDelay);
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index d45d686..2b4677c 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -1199,6 +1199,9 @@
/**
* Notifies the keyguard to start fading out.
+ *
+ * @param startTime the start time of the animation in uptime milliseconds
+ * @param fadeoutDuration the duration of the exit animation, in milliseconds
*/
- public void startKeyguardExitAnimation(long fadeoutDuration);
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index c9eb130..9a46052 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2495,17 +2495,25 @@
}
/**
- * Positions the selector in a way that mimics keyboard focus. If the
- * selector drawable supports hotspots, this manages the focus hotspot.
+ * Positions the selector in a way that mimics keyboard focus.
*/
void positionSelectorLikeFocus(int position, View sel) {
+ // If we're changing position, update the visibility since the selector
+ // is technically being detached from the previous selection.
+ final Drawable selector = mSelector;
+ final boolean manageState = selector != null && mSelectorPosition != position
+ && position != INVALID_POSITION;
+ if (manageState) {
+ selector.setVisible(false, false);
+ }
+
positionSelector(position, sel);
- final Drawable selector = mSelector;
- if (selector != null && position != INVALID_POSITION) {
+ if (manageState) {
final Rect bounds = mSelectorRect;
final float x = bounds.exactCenterX();
final float y = bounds.exactCenterY();
+ selector.setVisible(getVisibility() == VISIBLE, false);
selector.setHotspot(x, y);
}
}
@@ -2520,8 +2528,18 @@
if (sel instanceof SelectionBoundsAdjuster) {
((SelectionBoundsAdjuster)sel).adjustListItemSelectionBounds(selectorRect);
}
- positionSelector(selectorRect.left, selectorRect.top, selectorRect.right,
- selectorRect.bottom);
+
+ // Adjust for selection padding.
+ selectorRect.left -= mSelectionLeftPadding;
+ selectorRect.top -= mSelectionTopPadding;
+ selectorRect.right += mSelectionRightPadding;
+ selectorRect.bottom += mSelectionBottomPadding;
+
+ // Update the selector drawable.
+ final Drawable selector = mSelector;
+ if (selector != null) {
+ selector.setBounds(selectorRect);
+ }
final boolean isChildViewEnabled = mIsChildViewEnabled;
if (sel.isEnabled() != isChildViewEnabled) {
@@ -2532,11 +2550,6 @@
}
}
- private void positionSelector(int l, int t, int r, int b) {
- mSelectorRect.set(l - mSelectionLeftPadding, t - mSelectionTopPadding, r
- + mSelectionRightPadding, b + mSelectionBottomPadding);
- }
-
@Override
protected void dispatchDraw(Canvas canvas) {
int saveCount = 0;
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index a9a5eae..acee592 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -570,9 +570,9 @@
mMenu = new MenuBuilder(context);
mMenu.setCallback(new MenuBuilderCallback());
mPresenter = new ActionMenuPresenter(context);
- mPresenter.setMenuView(this);
mPresenter.setCallback(new ActionMenuPresenterCallback());
mMenu.addMenuPresenter(mPresenter);
+ mPresenter.setMenuView(this);
}
return mMenu;
@@ -652,6 +652,11 @@
return false;
}
+ /** @hide */
+ public void setExpandedActionViewsExclusive(boolean exclusive) {
+ mPresenter.setExpandedActionViewsExclusive(exclusive);
+ }
+
/**
* Interface responsible for receiving menu item click events if the items themselves
* do not have individual item click listeners.
diff --git a/core/java/android/widget/Toolbar.java b/core/java/android/widget/Toolbar.java
index f35ca27..419c582 100644
--- a/core/java/android/widget/Toolbar.java
+++ b/core/java/android/widget/Toolbar.java
@@ -127,6 +127,8 @@
// Clear me after use.
private final ArrayList<View> mTempViews = new ArrayList<View>();
+ private final int[] mTempMargins = new int[2];
+
private OnMenuItemClickListener mOnMenuItemClickListener;
private final ActionMenuView.OnMenuItemClickListener mMenuViewItemClickListener =
@@ -220,7 +222,7 @@
final CharSequence subtitle = a.getText(R.styleable.Toolbar_subtitle);
if (!TextUtils.isEmpty(subtitle)) {
- setSubtitle(title);
+ setSubtitle(subtitle);
}
a.recycle();
}
@@ -703,10 +705,23 @@
* @return The toolbar's Menu
*/
public Menu getMenu() {
- ensureMenuView();
+ ensureMenu();
return mMenuView.getMenu();
}
+ private void ensureMenu() {
+ ensureMenuView();
+ if (mMenuView.peekMenu() == null) {
+ // Initialize a new menu for the first time.
+ final MenuBuilder menu = (MenuBuilder) mMenuView.getMenu();
+ if (mExpandedMenuPresenter == null) {
+ mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter();
+ }
+ mMenuView.setExpandedActionViewsExclusive(true);
+ menu.addMenuPresenter(mExpandedMenuPresenter);
+ }
+ }
+
private void ensureMenuView() {
if (mMenuView == null) {
mMenuView = new ActionMenuView(getContext());
@@ -928,12 +943,49 @@
child.measure(childWidthSpec, childHeightSpec);
}
+ /**
+ * Returns the width + uncollapsed margins
+ */
+ private int measureChildCollapseMargins(View child,
+ int parentWidthMeasureSpec, int widthUsed,
+ int parentHeightMeasureSpec, int heightUsed, int[] collapsingMargins) {
+ final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+
+ final int leftDiff = lp.leftMargin - collapsingMargins[0];
+ final int rightDiff = lp.rightMargin - collapsingMargins[1];
+ final int leftMargin = Math.max(0, leftDiff);
+ final int rightMargin = Math.max(0, rightDiff);
+ final int hMargins = leftMargin + rightMargin;
+ collapsingMargins[0] = Math.max(0, -leftDiff);
+ collapsingMargins[1] = Math.max(0, -rightDiff);
+
+ final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+ mPaddingLeft + mPaddingRight + hMargins + widthUsed, lp.width);
+ final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+ mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ + heightUsed, lp.height);
+
+ child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ return child.getMeasuredWidth() + hMargins;
+ }
+
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = 0;
int height = 0;
int childState = 0;
+ final int[] collapsingMargins = mTempMargins;
+ final int marginStartIndex;
+ final int marginEndIndex;
+ if (isLayoutRtl()) {
+ marginStartIndex = 1;
+ marginEndIndex = 0;
+ } else {
+ marginStartIndex = 0;
+ marginEndIndex = 1;
+ }
+
// System views measure first.
int navWidth = 0;
@@ -956,7 +1008,9 @@
childState = combineMeasuredStates(childState, mCollapseButtonView.getMeasuredState());
}
- width += Math.max(getContentInsetStart(), navWidth);
+ final int contentInsetStart = getContentInsetStart();
+ width += Math.max(contentInsetStart, navWidth);
+ collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth);
int menuWidth = 0;
if (shouldLayout(mMenuView)) {
@@ -968,21 +1022,21 @@
childState = combineMeasuredStates(childState, mMenuView.getMeasuredState());
}
- width += Math.max(getContentInsetEnd(), menuWidth);
+ final int contentInsetEnd = getContentInsetEnd();
+ width += Math.max(contentInsetEnd, menuWidth);
+ collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth);
if (shouldLayout(mExpandedActionView)) {
- measureChildWithMargins(mExpandedActionView, widthMeasureSpec, width,
- heightMeasureSpec, 0);
- width += mExpandedActionView.getMeasuredWidth() +
- getHorizontalMargins(mExpandedActionView);
+ width += measureChildCollapseMargins(mExpandedActionView, widthMeasureSpec, width,
+ heightMeasureSpec, 0, collapsingMargins);
height = Math.max(height, mExpandedActionView.getMeasuredHeight() +
getVerticalMargins(mExpandedActionView));
childState = combineMeasuredStates(childState, mExpandedActionView.getMeasuredState());
}
if (shouldLayout(mLogoView)) {
- measureChildWithMargins(mLogoView, widthMeasureSpec, width, heightMeasureSpec, 0);
- width += mLogoView.getMeasuredWidth() + getHorizontalMargins(mLogoView);
+ width += measureChildCollapseMargins(mLogoView, widthMeasureSpec, width,
+ heightMeasureSpec, 0, collapsingMargins);
height = Math.max(height, mLogoView.getMeasuredHeight() +
getVerticalMargins(mLogoView));
childState = combineMeasuredStates(childState, mLogoView.getMeasuredState());
@@ -993,17 +1047,18 @@
final int titleVertMargins = mTitleMarginTop + mTitleMarginBottom;
final int titleHorizMargins = mTitleMarginStart + mTitleMarginEnd;
if (shouldLayout(mTitleTextView)) {
- measureChildWithMargins(mTitleTextView, widthMeasureSpec, width + titleHorizMargins,
- heightMeasureSpec, titleVertMargins);
+ titleWidth = measureChildCollapseMargins(mTitleTextView, widthMeasureSpec,
+ width + titleHorizMargins, heightMeasureSpec, titleVertMargins,
+ collapsingMargins);
titleWidth = mTitleTextView.getMeasuredWidth() + getHorizontalMargins(mTitleTextView);
titleHeight = mTitleTextView.getMeasuredHeight() + getVerticalMargins(mTitleTextView);
childState = combineMeasuredStates(childState, mTitleTextView.getMeasuredState());
}
if (shouldLayout(mSubtitleTextView)) {
- measureChildWithMargins(mSubtitleTextView, widthMeasureSpec, width + titleHorizMargins,
- heightMeasureSpec, titleHeight + titleVertMargins);
- titleWidth = Math.max(titleWidth, mSubtitleTextView.getMeasuredWidth() +
- getHorizontalMargins(mSubtitleTextView));
+ titleWidth = Math.max(titleWidth, measureChildCollapseMargins(mSubtitleTextView,
+ widthMeasureSpec, width + titleHorizMargins,
+ heightMeasureSpec, titleHeight + titleVertMargins,
+ collapsingMargins));
titleHeight += mSubtitleTextView.getMeasuredHeight() +
getVerticalMargins(mSubtitleTextView);
childState = combineMeasuredStates(childState, mSubtitleTextView.getMeasuredState());
@@ -1021,8 +1076,8 @@
continue;
}
- measureChildWithMargins(child, widthMeasureSpec, width, heightMeasureSpec, 0);
- width += child.getMeasuredWidth() + getHorizontalMargins(child);
+ width += measureChildCollapseMargins(child, widthMeasureSpec, width,
+ heightMeasureSpec, 0, collapsingMargins);
height = Math.max(height, child.getMeasuredHeight() + getVerticalMargins(child));
childState = combineMeasuredStates(childState, child.getMeasuredState());
}
@@ -1053,46 +1108,51 @@
int left = paddingLeft;
int right = width - paddingRight;
+ final int[] collapsingMargins = mTempMargins;
+ collapsingMargins[0] = collapsingMargins[1] = 0;
+
if (shouldLayout(mNavButtonView)) {
if (isRtl) {
- right = layoutChildRight(mNavButtonView, right);
+ right = layoutChildRight(mNavButtonView, right, collapsingMargins);
} else {
- left = layoutChildLeft(mNavButtonView, left);
+ left = layoutChildLeft(mNavButtonView, left, collapsingMargins);
}
}
if (shouldLayout(mCollapseButtonView)) {
if (isRtl) {
- right = layoutChildRight(mCollapseButtonView, right);
+ right = layoutChildRight(mCollapseButtonView, right, collapsingMargins);
} else {
- left = layoutChildLeft(mCollapseButtonView, left);
+ left = layoutChildLeft(mCollapseButtonView, left, collapsingMargins);
}
}
if (shouldLayout(mMenuView)) {
if (isRtl) {
- left = layoutChildLeft(mMenuView, left);
+ left = layoutChildLeft(mMenuView, left, collapsingMargins);
} else {
- right = layoutChildRight(mMenuView, right);
+ right = layoutChildRight(mMenuView, right, collapsingMargins);
}
}
+ collapsingMargins[0] = Math.max(0, getContentInsetLeft() - left);
+ collapsingMargins[1] = Math.max(0, getContentInsetRight() - (width - paddingRight - right));
left = Math.max(left, getContentInsetLeft());
right = Math.min(right, width - paddingRight - getContentInsetRight());
if (shouldLayout(mExpandedActionView)) {
if (isRtl) {
- right = layoutChildRight(mExpandedActionView, right);
+ right = layoutChildRight(mExpandedActionView, right, collapsingMargins);
} else {
- left = layoutChildLeft(mExpandedActionView, left);
+ left = layoutChildLeft(mExpandedActionView, left, collapsingMargins);
}
}
if (shouldLayout(mLogoView)) {
if (isRtl) {
- right = layoutChildRight(mLogoView, right);
+ right = layoutChildRight(mLogoView, right, collapsingMargins);
} else {
- left = layoutChildLeft(mLogoView, left);
+ left = layoutChildLeft(mLogoView, left, collapsingMargins);
}
}
@@ -1141,48 +1201,52 @@
break;
}
if (isRtl) {
+ final int rd = mTitleMarginStart - collapsingMargins[1];
+ right -= Math.max(0, rd);
+ collapsingMargins[1] = Math.max(0, -rd);
int titleRight = right;
int subtitleRight = right;
+
if (layoutTitle) {
final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
- titleRight -= lp.rightMargin + mTitleMarginStart;
final int titleLeft = titleRight - mTitleTextView.getMeasuredWidth();
final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
- titleRight = titleLeft - lp.leftMargin - mTitleMarginEnd;
+ titleRight = titleLeft - mTitleMarginEnd;
titleTop = titleBottom + lp.bottomMargin;
}
if (layoutSubtitle) {
final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
- subtitleRight -= lp.rightMargin + mTitleMarginStart;
titleTop += lp.topMargin;
final int subtitleLeft = subtitleRight - mSubtitleTextView.getMeasuredWidth();
final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
- subtitleRight = subtitleRight - lp.leftMargin - mTitleMarginEnd;
+ subtitleRight = subtitleRight - mTitleMarginEnd;
titleTop = subtitleBottom + lp.bottomMargin;
}
right = Math.max(titleRight, subtitleRight);
} else {
+ final int ld = mTitleMarginStart - collapsingMargins[0];
+ left += Math.max(0, ld);
+ collapsingMargins[0] = Math.max(0, -ld);
int titleLeft = left;
int subtitleLeft = left;
+
if (layoutTitle) {
final LayoutParams lp = (LayoutParams) mTitleTextView.getLayoutParams();
- titleLeft += lp.leftMargin + mTitleMarginStart;
final int titleRight = titleLeft + mTitleTextView.getMeasuredWidth();
final int titleBottom = titleTop + mTitleTextView.getMeasuredHeight();
mTitleTextView.layout(titleLeft, titleTop, titleRight, titleBottom);
- titleLeft = titleRight + lp.rightMargin + mTitleMarginEnd;
+ titleLeft = titleRight + mTitleMarginEnd;
titleTop = titleBottom + lp.bottomMargin;
}
if (layoutSubtitle) {
final LayoutParams lp = (LayoutParams) mSubtitleTextView.getLayoutParams();
- subtitleLeft += lp.leftMargin + mTitleMarginStart;
titleTop += lp.topMargin;
final int subtitleRight = subtitleLeft + mSubtitleTextView.getMeasuredWidth();
final int subtitleBottom = titleTop + mSubtitleTextView.getMeasuredHeight();
mSubtitleTextView.layout(subtitleLeft, titleTop, subtitleRight, subtitleBottom);
- subtitleLeft = subtitleRight + lp.rightMargin + mTitleMarginEnd;
+ subtitleLeft = subtitleRight + mTitleMarginEnd;
titleTop = subtitleBottom + lp.bottomMargin;
}
left = Math.max(titleLeft, subtitleLeft);
@@ -1195,19 +1259,19 @@
addCustomViewsWithGravity(mTempViews, Gravity.LEFT);
final int leftViewsCount = mTempViews.size();
for (int i = 0; i < leftViewsCount; i++) {
- left = layoutChildLeft(mTempViews.get(i), left);
+ left = layoutChildLeft(mTempViews.get(i), left, collapsingMargins);
}
addCustomViewsWithGravity(mTempViews, Gravity.RIGHT);
final int rightViewsCount = mTempViews.size();
for (int i = 0; i < rightViewsCount; i++) {
- right = layoutChildRight(mTempViews.get(i), right);
+ right = layoutChildRight(mTempViews.get(i), right, collapsingMargins);
}
// Centered views try to center with respect to the whole bar, but views pinned
// to the left or right can push the mass of centered views to one side or the other.
addCustomViewsWithGravity(mTempViews, Gravity.CENTER_HORIZONTAL);
- final int centerViewsWidth = getViewListMeasuredWidth(mTempViews);
+ final int centerViewsWidth = getViewListMeasuredWidth(mTempViews, collapsingMargins);
final int parentCenter = paddingLeft + (width - paddingLeft - paddingRight) / 2;
final int halfCenterViewsWidth = centerViewsWidth / 2;
int centerLeft = parentCenter - halfCenterViewsWidth;
@@ -1220,25 +1284,35 @@
final int centerViewsCount = mTempViews.size();
for (int i = 0; i < centerViewsCount; i++) {
- centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft);
+ centerLeft = layoutChildLeft(mTempViews.get(i), centerLeft, collapsingMargins);
}
mTempViews.clear();
}
- private int getViewListMeasuredWidth(List<View> views) {
+ private int getViewListMeasuredWidth(List<View> views, int[] collapsingMargins) {
+ int collapseLeft = collapsingMargins[0];
+ int collapseRight = collapsingMargins[1];
int width = 0;
final int count = views.size();
for (int i = 0; i < count; i++) {
final View v = views.get(i);
final LayoutParams lp = (LayoutParams) v.getLayoutParams();
- width += lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin;
+ final int l = lp.leftMargin - collapseLeft;
+ final int r = lp.rightMargin - collapseRight;
+ final int leftMargin = Math.max(0, l);
+ final int rightMargin = Math.max(0, r);
+ collapseLeft = Math.max(0, -l);
+ collapseRight = Math.max(0, -r);
+ width += leftMargin + v.getMeasuredWidth() + rightMargin;
}
return width;
}
- private int layoutChildLeft(View child, int left) {
+ private int layoutChildLeft(View child, int left, int[] collapsingMargins) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- left += lp.leftMargin;
+ final int l = lp.leftMargin - collapsingMargins[0];
+ left += Math.max(0, l);
+ collapsingMargins[0] = Math.max(0, -l);
final int top = getChildTop(child);
final int childWidth = child.getMeasuredWidth();
child.layout(left, top, left + childWidth, top + child.getMeasuredHeight());
@@ -1246,9 +1320,11 @@
return left;
}
- private int layoutChildRight(View child, int right) {
+ private int layoutChildRight(View child, int right, int[] collapsingMargins) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- right -= lp.rightMargin;
+ final int r = lp.rightMargin - collapsingMargins[1];
+ right -= Math.max(0, r);
+ collapsingMargins[1] = Math.max(0, -r);
final int top = getChildTop(child);
final int childWidth = child.getMeasuredWidth();
child.layout(right - childWidth, top, right, top + child.getMeasuredHeight());
diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl
index 737906a..2900595 100644
--- a/core/java/com/android/internal/app/IVoiceInteractor.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl
@@ -26,7 +26,9 @@
*/
interface IVoiceInteractor {
IVoiceInteractorRequest startConfirmation(String callingPackage,
- IVoiceInteractorCallback callback, String prompt, in Bundle extras);
+ IVoiceInteractorCallback callback, CharSequence prompt, in Bundle extras);
+ IVoiceInteractorRequest startAbortVoice(String callingPackage,
+ IVoiceInteractorCallback callback, CharSequence message, in Bundle extras);
IVoiceInteractorRequest startCommand(String callingPackage,
IVoiceInteractorCallback callback, String command, in Bundle extras);
boolean[] supportsCommands(String callingPackage, in String[] commands);
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
index c6f93e1..8dbf9d4 100644
--- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
@@ -26,6 +26,7 @@
oneway interface IVoiceInteractorCallback {
void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
in Bundle result);
+ void deliverAbortVoiceResult(IVoiceInteractorRequest request, in Bundle result);
void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result);
void deliverCancel(IVoiceInteractorRequest request);
}
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index afb6f7c..6056bf2 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -23,37 +23,50 @@
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.view.ActionMode;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
+import android.view.Window;
import android.widget.SpinnerAdapter;
import android.widget.Toolbar;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.widget.DecorToolbar;
+import com.android.internal.widget.ToolbarWidgetWrapper;
import java.util.ArrayList;
-import java.util.Map;
public class ToolbarActionBar extends ActionBar {
private Toolbar mToolbar;
- private View mCustomView;
-
- private int mDisplayOptions;
-
- private int mNavResId;
- private int mIconResId;
- private int mLogoResId;
- private Drawable mNavDrawable;
- private Drawable mIconDrawable;
- private Drawable mLogoDrawable;
- private int mTitleResId;
- private int mSubtitleResId;
- private CharSequence mTitle;
- private CharSequence mSubtitle;
+ private DecorToolbar mDecorToolbar;
+ private Window.Callback mWindowCallback;
private boolean mLastMenuVisibility;
private ArrayList<OnMenuVisibilityListener> mMenuVisibilityListeners =
new ArrayList<OnMenuVisibilityListener>();
- public ToolbarActionBar(Toolbar toolbar) {
+ private final Runnable mMenuInvalidator = new Runnable() {
+ @Override
+ public void run() {
+ populateOptionsMenu();
+ }
+ };
+
+ private final Toolbar.OnMenuItemClickListener mMenuClicker =
+ new Toolbar.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ return mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, item);
+ }
+ };
+
+ public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) {
mToolbar = toolbar;
+ mDecorToolbar = new ToolbarWidgetWrapper(toolbar);
+ mWindowCallback = windowCallback;
+ toolbar.setOnMenuItemClickListener(mMenuClicker);
+ mDecorToolbar.setWindowTitle(title);
}
@Override
@@ -63,19 +76,8 @@
@Override
public void setCustomView(View view, LayoutParams layoutParams) {
- if (mCustomView != null) {
- mToolbar.removeView(mCustomView);
- }
- mCustomView = view;
- if (view != null) {
- mToolbar.addView(view, generateLayoutParams(layoutParams));
- }
- }
-
- private Toolbar.LayoutParams generateLayoutParams(LayoutParams lp) {
- final Toolbar.LayoutParams result = new Toolbar.LayoutParams(lp);
- result.gravity = lp.gravity;
- return result;
+ view.setLayoutParams(layoutParams);
+ mDecorToolbar.setCustomView(view);
}
@Override
@@ -86,48 +88,22 @@
@Override
public void setIcon(int resId) {
- mIconResId = resId;
- mIconDrawable = null;
- updateToolbarLogo();
+ mDecorToolbar.setIcon(resId);
}
@Override
public void setIcon(Drawable icon) {
- mIconResId = 0;
- mIconDrawable = icon;
- updateToolbarLogo();
+ mDecorToolbar.setIcon(icon);
}
@Override
public void setLogo(int resId) {
- mLogoResId = resId;
- mLogoDrawable = null;
- updateToolbarLogo();
+ mDecorToolbar.setLogo(resId);
}
@Override
public void setLogo(Drawable logo) {
- mLogoResId = 0;
- mLogoDrawable = logo;
- updateToolbarLogo();
- }
-
- private void updateToolbarLogo() {
- Drawable drawable = null;
- if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_HOME) != 0) {
- final int resId;
- if ((mDisplayOptions & ActionBar.DISPLAY_USE_LOGO) != 0) {
- resId = mLogoResId;
- drawable = mLogoDrawable;
- } else {
- resId = mIconResId;
- drawable = mIconDrawable;
- }
- if (resId != 0) {
- drawable = mToolbar.getContext().getDrawable(resId);
- }
- }
- mToolbar.setLogo(drawable);
+ mDecorToolbar.setLogo(logo);
}
@Override
@@ -219,42 +195,22 @@
@Override
public void setTitle(CharSequence title) {
- mTitle = title;
- mTitleResId = 0;
- updateToolbarTitle();
+ mDecorToolbar.setTitle(title);
}
@Override
public void setTitle(int resId) {
- mTitleResId = resId;
- mTitle = null;
- updateToolbarTitle();
+ mDecorToolbar.setTitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
}
@Override
public void setSubtitle(CharSequence subtitle) {
- mSubtitle = subtitle;
- mSubtitleResId = 0;
- updateToolbarTitle();
+ mDecorToolbar.setSubtitle(subtitle);
}
@Override
public void setSubtitle(int resId) {
- mSubtitleResId = resId;
- mSubtitle = null;
- updateToolbarTitle();
- }
-
- private void updateToolbarTitle() {
- final Context context = mToolbar.getContext();
- CharSequence title = null;
- CharSequence subtitle = null;
- if ((mDisplayOptions & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
- title = mTitleResId != 0 ? context.getText(mTitleResId) : mTitle;
- subtitle = mSubtitleResId != 0 ? context.getText(mSubtitleResId) : mSubtitle;
- }
- mToolbar.setTitle(title);
- mToolbar.setSubtitle(subtitle);
+ mDecorToolbar.setSubtitle(resId != 0 ? mDecorToolbar.getContext().getText(resId) : null);
}
@Override
@@ -264,9 +220,8 @@
@Override
public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) {
- final int oldOptions = mDisplayOptions;
- mDisplayOptions = (options & mask) | (mDisplayOptions & ~mask);
- final int optionsChanged = oldOptions ^ mDisplayOptions;
+ mDecorToolbar.setDisplayOptions((options & mask) |
+ mDecorToolbar.getDisplayOptions() & ~mask);
}
@Override
@@ -301,7 +256,7 @@
@Override
public View getCustomView() {
- return mCustomView;
+ return mDecorToolbar.getCustomView();
}
@Override
@@ -327,7 +282,7 @@
@Override
public int getDisplayOptions() {
- return mDisplayOptions;
+ return mDecorToolbar.getDisplayOptions();
}
@Override
@@ -425,6 +380,54 @@
return mToolbar.getVisibility() == View.VISIBLE;
}
+ @Override
+ public boolean openOptionsMenu() {
+ return mToolbar.showOverflowMenu();
+ }
+
+ @Override
+ public boolean invalidateOptionsMenu() {
+ mToolbar.removeCallbacks(mMenuInvalidator);
+ mToolbar.postOnAnimation(mMenuInvalidator);
+ return true;
+ }
+
+ @Override
+ public boolean collapseActionView() {
+ if (mToolbar.hasExpandedActionView()) {
+ mToolbar.collapseActionView();
+ return true;
+ }
+ return false;
+ }
+
+ void populateOptionsMenu() {
+ final Menu menu = mToolbar.getMenu();
+ final MenuBuilder mb = menu instanceof MenuBuilder ? (MenuBuilder) menu : null;
+ if (mb != null) {
+ mb.stopDispatchingItemsChanged();
+ }
+ try {
+ menu.clear();
+ if (!mWindowCallback.onCreatePanelMenu(Window.FEATURE_OPTIONS_PANEL, menu) ||
+ !mWindowCallback.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, null, menu)) {
+ menu.clear();
+ }
+ } finally {
+ if (mb != null) {
+ mb.startDispatchingItemsChanged();
+ }
+ }
+ }
+
+ @Override
+ public boolean onMenuKeyEvent(KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ openOptionsMenu();
+ }
+ return true;
+ }
+
public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
mMenuVisibilityListeners.add(listener);
}
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index f22800c..a5421f5 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -60,6 +60,9 @@
/**
* Notifies that the activity behind has now been drawn and it's safe to remove the wallpaper
* and keyguard flag.
+ *
+ * @param startTime the start time of the animation in uptime milliseconds
+ * @param fadeoutDuration the duration of the exit animation, in milliseconds
*/
- oneway void startKeyguardExitAnimation(long fadeoutDuration);
+ oneway void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
}
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index b8dc307..3e15c32 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -24,6 +24,7 @@
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Parcelable;
+import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
@@ -80,16 +81,20 @@
public ToolbarWidgetWrapper(Toolbar toolbar) {
mToolbar = toolbar;
+ mTitle = toolbar.getTitle();
+ mSubtitle = toolbar.getSubtitle();
+ mTitleSet = !TextUtils.isEmpty(mTitle);
+
final TypedArray a = toolbar.getContext().obtainStyledAttributes(null,
R.styleable.ActionBar, R.attr.actionBarStyle, 0);
final CharSequence title = a.getText(R.styleable.ActionBar_title);
- if (title != null) {
+ if (!TextUtils.isEmpty(title)) {
setTitle(title);
}
final CharSequence subtitle = a.getText(R.styleable.ActionBar_subtitle);
- if (subtitle != null) {
+ if (!TextUtils.isEmpty(subtitle)) {
setSubtitle(subtitle);
}
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index fedb1b2..a2b1ed9 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -23,6 +23,11 @@
#define ENCODING_PCM_16BIT 2
#define ENCODING_PCM_8BIT 3
#define ENCODING_PCM_FLOAT 4
+#define ENCODING_INVALID 0
+#define ENCODING_DEFAULT 1
+
+#define CHANNEL_INVALID 0
+#define CHANNEL_OUT_DEFAULT 1
static inline audio_format_t audioFormatToNative(int audioFormat)
{
@@ -33,9 +38,58 @@
return AUDIO_FORMAT_PCM_8_BIT;
case ENCODING_PCM_FLOAT:
return AUDIO_FORMAT_PCM_FLOAT;
+ case ENCODING_DEFAULT:
+ return AUDIO_FORMAT_DEFAULT;
default:
return AUDIO_FORMAT_INVALID;
}
}
+static inline int audioFormatFromNative(audio_format_t nativeFormat)
+{
+ switch (nativeFormat) {
+ case AUDIO_FORMAT_PCM_16_BIT:
+ return ENCODING_PCM_16BIT;
+ case AUDIO_FORMAT_PCM_8_BIT:
+ return ENCODING_PCM_8BIT;
+ case AUDIO_FORMAT_PCM_FLOAT:
+ return ENCODING_PCM_FLOAT;
+ case AUDIO_FORMAT_DEFAULT:
+ return ENCODING_DEFAULT;
+ default:
+ return ENCODING_INVALID;
+ }
+}
+
+static inline audio_channel_mask_t outChannelMaskToNative(int channelMask)
+{
+ switch (channelMask) {
+ case CHANNEL_OUT_DEFAULT:
+ case CHANNEL_INVALID:
+ return AUDIO_CHANNEL_NONE;
+ default:
+ return (audio_channel_mask_t)(channelMask>>2);
+ }
+}
+
+static inline int outChannelMaskFromNative(audio_channel_mask_t nativeMask)
+{
+ switch (nativeMask) {
+ case AUDIO_CHANNEL_NONE:
+ return CHANNEL_OUT_DEFAULT;
+ default:
+ return (int)nativeMask<<2;
+ }
+}
+
+static inline audio_channel_mask_t inChannelMaskToNative(int channelMask)
+{
+ return (audio_channel_mask_t)channelMask;
+}
+
+static inline int inChannelMaskFromNative(audio_channel_mask_t nativeMask)
+{
+ return (int)nativeMask;
+}
+
#endif // ANDROID_MEDIA_AUDIOFORMAT_H
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index a9a62f8..0f7e140 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -15,7 +15,9 @@
** limitations under the License.
*/
-#define LOG_TAG "AudioSystem"
+//#define LOG_NDEBUG 0
+
+#define LOG_TAG "AudioSystem-JNI"
#include <utils/Log.h>
#include <jni.h>
@@ -26,6 +28,8 @@
#include <system/audio.h>
#include <system/audio_policy.h>
+#include "android_media_AudioFormat.h"
+#include "android_media_AudioErrors.h"
// ----------------------------------------------------------------------------
@@ -33,12 +37,160 @@
static const char* const kClassPathName = "android/media/AudioSystem";
+static jclass gArrayListClass;
+static struct {
+ jmethodID add;
+} gArrayListMethods;
+
+static jclass gAudioHandleClass;
+static jmethodID gAudioHandleCstor;
+static struct {
+ jfieldID mId;
+} gAudioHandleFields;
+
+static jclass gAudioPortClass;
+static jmethodID gAudioPortCstor;
+static struct {
+ jfieldID mHandle;
+ jfieldID mRole;
+ jfieldID mGains;
+ jfieldID mActiveConfig;
+ // other fields unused by JNI
+} gAudioPortFields;
+
+static jclass gAudioPortConfigClass;
+static jmethodID gAudioPortConfigCstor;
+static struct {
+ jfieldID mPort;
+ jfieldID mSamplingRate;
+ jfieldID mChannelMask;
+ jfieldID mFormat;
+ jfieldID mGain;
+ jfieldID mConfigMask;
+} gAudioPortConfigFields;
+
+static jclass gAudioDevicePortClass;
+static jmethodID gAudioDevicePortCstor;
+
+static jclass gAudioDevicePortConfigClass;
+static jmethodID gAudioDevicePortConfigCstor;
+
+static jclass gAudioMixPortClass;
+static jmethodID gAudioMixPortCstor;
+
+static jclass gAudioMixPortConfigClass;
+static jmethodID gAudioMixPortConfigCstor;
+
+static jclass gAudioGainClass;
+static jmethodID gAudioGainCstor;
+
+static jclass gAudioGainConfigClass;
+static jmethodID gAudioGainConfigCstor;
+static struct {
+ jfieldID mIndex;
+ jfieldID mMode;
+ jfieldID mChannelMask;
+ jfieldID mValues;
+ jfieldID mRampDurationMs;
+ // other fields unused by JNI
+} gAudioGainConfigFields;
+
+static jclass gAudioPatchClass;
+static jmethodID gAudioPatchCstor;
+static struct {
+ jfieldID mHandle;
+ // other fields unused by JNI
+} gAudioPatchFields;
+
+static const char* const kEventHandlerClassPathName =
+ "android/media/AudioPortEventHandler";
+static jmethodID gPostEventFromNative;
+
enum AudioError {
kAudioStatusOk = 0,
kAudioStatusError = 1,
kAudioStatusMediaServerDied = 100
};
+enum {
+ AUDIOPORT_EVENT_PORT_LIST_UPDATED = 1,
+ AUDIOPORT_EVENT_PATCH_LIST_UPDATED = 2,
+ AUDIOPORT_EVENT_SERVICE_DIED = 3,
+};
+
+#define MAX_PORT_GENERATION_SYNC_ATTEMPTS 5
+
+// ----------------------------------------------------------------------------
+// ref-counted object for callbacks
+class JNIAudioPortCallback: public AudioSystem::AudioPortCallback
+{
+public:
+ JNIAudioPortCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
+ ~JNIAudioPortCallback();
+
+ virtual void onAudioPortListUpdate();
+ virtual void onAudioPatchListUpdate();
+ virtual void onServiceDied();
+
+private:
+ void sendEvent(int event);
+
+ jclass mClass; // Reference to AudioPortEventHandlerDelegate class
+ jobject mObject; // Weak ref to AudioPortEventHandlerDelegate Java object to call on
+};
+
+JNIAudioPortCallback::JNIAudioPortCallback(JNIEnv* env, jobject thiz, jobject weak_thiz)
+{
+
+ // Hold onto the SoundTriggerModule class for use in calling the static method
+ // that posts events to the application thread.
+ jclass clazz = env->GetObjectClass(thiz);
+ if (clazz == NULL) {
+ ALOGE("Can't find class %s", kEventHandlerClassPathName);
+ return;
+ }
+ mClass = (jclass)env->NewGlobalRef(clazz);
+
+ // We use a weak reference so the SoundTriggerModule object can be garbage collected.
+ // The reference is only used as a proxy for callbacks.
+ mObject = env->NewGlobalRef(weak_thiz);
+}
+
+JNIAudioPortCallback::~JNIAudioPortCallback()
+{
+ // remove global references
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->DeleteGlobalRef(mObject);
+ env->DeleteGlobalRef(mClass);
+}
+
+void JNIAudioPortCallback::sendEvent(int event)
+{
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+
+ env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
+ event, 0, 0, NULL);
+ if (env->ExceptionCheck()) {
+ ALOGW("An exception occurred while notifying an event.");
+ env->ExceptionClear();
+ }
+}
+
+void JNIAudioPortCallback::onAudioPortListUpdate()
+{
+ sendEvent(AUDIOPORT_EVENT_PORT_LIST_UPDATED);
+}
+
+void JNIAudioPortCallback::onAudioPatchListUpdate()
+{
+ sendEvent(AUDIOPORT_EVENT_PATCH_LIST_UPDATED);
+}
+
+void JNIAudioPortCallback::onServiceDied()
+{
+ sendEvent(AUDIOPORT_EVENT_SERVICE_DIED);
+}
+
static int check_AudioSystem_Command(status_t status)
{
switch (status) {
@@ -281,6 +433,854 @@
return (jint) check_AudioSystem_Command(AudioSystem::checkAudioFlinger());
}
+
+static bool useInChannelMask(audio_port_type_t type, audio_port_role_t role)
+{
+ return ((type == AUDIO_PORT_TYPE_DEVICE) && (role == AUDIO_PORT_ROLE_SOURCE)) ||
+ ((type == AUDIO_PORT_TYPE_MIX) && (role == AUDIO_PORT_ROLE_SINK));
+}
+
+static void convertAudioGainConfigToNative(JNIEnv *env,
+ struct audio_gain_config *nAudioGainConfig,
+ const jobject jAudioGainConfig,
+ bool useInMask)
+{
+ nAudioGainConfig->index = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mIndex);
+ nAudioGainConfig->mode = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mMode);
+ ALOGV("convertAudioGainConfigToNative got gain index %d", nAudioGainConfig->index);
+ jint jMask = env->GetIntField(jAudioGainConfig, gAudioGainConfigFields.mChannelMask);
+ audio_channel_mask_t nMask;
+ if (useInMask) {
+ nMask = inChannelMaskToNative(jMask);
+ ALOGV("convertAudioGainConfigToNative IN mask java %x native %x", jMask, nMask);
+ } else {
+ nMask = outChannelMaskToNative(jMask);
+ ALOGV("convertAudioGainConfigToNative OUT mask java %x native %x", jMask, nMask);
+ }
+ nAudioGainConfig->channel_mask = nMask;
+ nAudioGainConfig->ramp_duration_ms = env->GetIntField(jAudioGainConfig,
+ gAudioGainConfigFields.mRampDurationMs);
+ jintArray jValues = (jintArray)env->GetObjectField(jAudioGainConfig,
+ gAudioGainConfigFields.mValues);
+ int *nValues = env->GetIntArrayElements(jValues, NULL);
+ size_t size = env->GetArrayLength(jValues);
+ memcpy(nAudioGainConfig->values, nValues, size * sizeof(int));
+ env->DeleteLocalRef(jValues);
+}
+
+
+static jint convertAudioPortConfigToNative(JNIEnv *env,
+ struct audio_port_config *nAudioPortConfig,
+ const jobject jAudioPortConfig)
+{
+ jobject jAudioPort = env->GetObjectField(jAudioPortConfig, gAudioPortConfigFields.mPort);
+ jobject jHandle = env->GetObjectField(jAudioPort, gAudioPortFields.mHandle);
+ nAudioPortConfig->id = env->GetIntField(jHandle, gAudioHandleFields.mId);
+ nAudioPortConfig->role = (audio_port_role_t)env->GetIntField(jAudioPort,
+ gAudioPortFields.mRole);
+ if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) {
+ nAudioPortConfig->type = AUDIO_PORT_TYPE_DEVICE;
+ } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) {
+ nAudioPortConfig->type = AUDIO_PORT_TYPE_MIX;
+ } else {
+ env->DeleteLocalRef(jAudioPort);
+ env->DeleteLocalRef(jHandle);
+ return (jint)AUDIO_JAVA_ERROR;
+ }
+ ALOGV("convertAudioPortConfigToNative handle %d role %d type %d",
+ nAudioPortConfig->id, nAudioPortConfig->role, nAudioPortConfig->type);
+
+ nAudioPortConfig->sample_rate = env->GetIntField(jAudioPortConfig,
+ gAudioPortConfigFields.mSamplingRate);
+
+ bool useInMask = useInChannelMask(nAudioPortConfig->type, nAudioPortConfig->role);
+ audio_channel_mask_t nMask;
+ jint jMask = env->GetIntField(jAudioPortConfig,
+ gAudioPortConfigFields.mChannelMask);
+ if (useInMask) {
+ nMask = inChannelMaskToNative(jMask);
+ ALOGV("convertAudioPortConfigToNative IN mask java %x native %x", jMask, nMask);
+ } else {
+ nMask = outChannelMaskToNative(jMask);
+ ALOGV("convertAudioPortConfigToNative OUT mask java %x native %x", jMask, nMask);
+ }
+ nAudioPortConfig->channel_mask = nMask;
+
+ jint jFormat = env->GetIntField(jAudioPortConfig, gAudioPortConfigFields.mFormat);
+ audio_format_t nFormat = audioFormatToNative(jFormat);
+ ALOGV("convertAudioPortConfigToNative format %d native %d", jFormat, nFormat);
+ nAudioPortConfig->format = nFormat;
+ jobject jGain = env->GetObjectField(jAudioPortConfig, gAudioPortConfigFields.mGain);
+ if (jGain != NULL) {
+ convertAudioGainConfigToNative(env, &nAudioPortConfig->gain, jGain, useInMask);
+ env->DeleteLocalRef(jGain);
+ } else {
+ ALOGV("convertAudioPortConfigToNative no gain");
+ nAudioPortConfig->gain.index = -1;
+ }
+ nAudioPortConfig->config_mask = env->GetIntField(jAudioPortConfig,
+ gAudioPortConfigFields.mConfigMask);
+
+ env->DeleteLocalRef(jAudioPort);
+ env->DeleteLocalRef(jHandle);
+ return (jint)AUDIO_JAVA_SUCCESS;
+}
+
+static jint convertAudioPortConfigFromNative(JNIEnv *env,
+ jobject jAudioPort,
+ jobject *jAudioPortConfig,
+ const struct audio_port_config *nAudioPortConfig)
+{
+ jint jStatus = AUDIO_JAVA_SUCCESS;
+ jobject jAudioGainConfig = NULL;
+ jobject jAudioGain = NULL;
+ jintArray jGainValues;
+ bool audioportCreated = false;
+
+ ALOGV("convertAudioPortConfigFromNative jAudioPort %p", jAudioPort);
+
+ if (jAudioPort == NULL) {
+ jobject jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+ nAudioPortConfig->id);
+
+ ALOGV("convertAudioPortConfigFromNative handle %d is a %s", nAudioPortConfig->id,
+ nAudioPortConfig->type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix");
+
+ if (jHandle == NULL) {
+ return (jint)AUDIO_JAVA_ERROR;
+ }
+ // create dummy port and port config objects with just the correct handle
+ // and configuration data. The actual AudioPortConfig objects will be
+ // constructed by java code with correct class type (device, mix etc...)
+ // and reference to AudioPort instance in this client
+ jAudioPort = env->NewObject(gAudioPortClass, gAudioPortCstor,
+ jHandle,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ env->DeleteLocalRef(jHandle);
+ if (jAudioPort == NULL) {
+ return (jint)AUDIO_JAVA_ERROR;
+ }
+ ALOGV("convertAudioPortConfigFromNative jAudioPort created for handle %d",
+ nAudioPortConfig->id);
+
+ audioportCreated = true;
+ }
+
+ bool useInMask = useInChannelMask(nAudioPortConfig->type, nAudioPortConfig->role);
+
+ audio_channel_mask_t nMask;
+ jint jMask;
+
+ int gainIndex = nAudioPortConfig->gain.index;
+ if (gainIndex >= 0) {
+ ALOGV("convertAudioPortConfigFromNative gain found with index %d mode %x",
+ gainIndex, nAudioPortConfig->gain.mode);
+ if (audioportCreated) {
+ ALOGV("convertAudioPortConfigFromNative creating gain");
+ jAudioGain = env->NewObject(gAudioGainClass, gAudioGainCstor,
+ gainIndex,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0);
+ if (jAudioGain == NULL) {
+ ALOGV("convertAudioPortConfigFromNative creating gain FAILED");
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ } else {
+ ALOGV("convertAudioPortConfigFromNative reading gain from port");
+ jobjectArray jGains = (jobjectArray)env->GetObjectField(jAudioPort,
+ gAudioPortFields.mGains);
+ if (jGains == NULL) {
+ ALOGV("convertAudioPortConfigFromNative could not get gains from port");
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ jAudioGain = env->GetObjectArrayElement(jGains, gainIndex);
+ env->DeleteLocalRef(jGains);
+ if (jAudioGain == NULL) {
+ ALOGV("convertAudioPortConfigFromNative could not get gain at index %d", gainIndex);
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ }
+ //TODO: replace popcount by audio utils function mask to count
+ int numValues = popcount(nAudioPortConfig->gain.channel_mask);
+ jGainValues = env->NewIntArray(numValues);
+ if (jGainValues == NULL) {
+ ALOGV("convertAudioPortConfigFromNative could not create gain values %d", numValues);
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ env->SetIntArrayRegion(jGainValues, 0, numValues,
+ nAudioPortConfig->gain.values);
+
+ nMask = nAudioPortConfig->gain.channel_mask;
+ if (useInMask) {
+ jMask = inChannelMaskFromNative(nMask);
+ ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask);
+ } else {
+ jMask = outChannelMaskFromNative(nMask);
+ ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
+ }
+
+ jAudioGainConfig = env->NewObject(gAudioGainConfigClass,
+ gAudioGainConfigCstor,
+ gainIndex,
+ jAudioGain,
+ nAudioPortConfig->gain.mode,
+ jMask,
+ jGainValues,
+ nAudioPortConfig->gain.ramp_duration_ms);
+ env->DeleteLocalRef(jGainValues);
+ if (jAudioGainConfig == NULL) {
+ ALOGV("convertAudioPortConfigFromNative could not create gain config");
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ }
+ jclass clazz;
+ jmethodID methodID;
+ if (audioportCreated) {
+ clazz = gAudioPortConfigClass;
+ methodID = gAudioPortConfigCstor;
+ ALOGV("convertAudioPortConfigFromNative building a generic port config");
+ } else {
+ if (env->IsInstanceOf(jAudioPort, gAudioDevicePortClass)) {
+ clazz = gAudioDevicePortConfigClass;
+ methodID = gAudioDevicePortConfigCstor;
+ ALOGV("convertAudioPortConfigFromNative building a device config");
+ } else if (env->IsInstanceOf(jAudioPort, gAudioMixPortClass)) {
+ clazz = gAudioMixPortConfigClass;
+ methodID = gAudioMixPortConfigCstor;
+ ALOGV("convertAudioPortConfigFromNative building a mix config");
+ } else {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ }
+ nMask = nAudioPortConfig->channel_mask;
+ if (useInMask) {
+ jMask = inChannelMaskFromNative(nMask);
+ ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask);
+ } else {
+ jMask = outChannelMaskFromNative(nMask);
+ ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
+ }
+
+ *jAudioPortConfig = env->NewObject(clazz, methodID,
+ jAudioPort,
+ nAudioPortConfig->sample_rate,
+ jMask,
+ audioFormatFromNative(nAudioPortConfig->format),
+ jAudioGainConfig);
+ if (*jAudioPortConfig == NULL) {
+ ALOGV("convertAudioPortConfigFromNative could not create new port config");
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ } else {
+ ALOGV("convertAudioPortConfigFromNative OK");
+ }
+
+exit:
+ if (audioportCreated) {
+ env->DeleteLocalRef(jAudioPort);
+ if (jAudioGain != NULL) {
+ env->DeleteLocalRef(jAudioGain);
+ }
+ }
+ if (jAudioGainConfig != NULL) {
+ env->DeleteLocalRef(jAudioGainConfig);
+ }
+ return jStatus;
+}
+
+static jint convertAudioPortFromNative(JNIEnv *env,
+ jobject *jAudioPort, const struct audio_port *nAudioPort)
+{
+ jint jStatus = (jint)AUDIO_JAVA_SUCCESS;
+ jintArray jSamplingRates = NULL;
+ jintArray jChannelMasks = NULL;
+ jintArray jFormats = NULL;
+ jobjectArray jGains = NULL;
+ jobject jHandle = NULL;
+ bool useInMask;
+
+ ALOGV("convertAudioPortFromNative id %d role %d type %d",
+ nAudioPort->id, nAudioPort->role, nAudioPort->type);
+
+ jSamplingRates = env->NewIntArray(nAudioPort->num_sample_rates);
+ if (jSamplingRates == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ if (nAudioPort->num_sample_rates) {
+ env->SetIntArrayRegion(jSamplingRates, 0, nAudioPort->num_sample_rates,
+ (jint *)nAudioPort->sample_rates);
+ }
+
+ jChannelMasks = env->NewIntArray(nAudioPort->num_channel_masks);
+ if (jChannelMasks == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ useInMask = useInChannelMask(nAudioPort->type, nAudioPort->role);
+
+ jint jMask;
+ for (size_t j = 0; j < nAudioPort->num_channel_masks; j++) {
+ if (useInMask) {
+ jMask = inChannelMaskFromNative(nAudioPort->channel_masks[j]);
+ } else {
+ jMask = outChannelMaskFromNative(nAudioPort->channel_masks[j]);
+ }
+ env->SetIntArrayRegion(jChannelMasks, j, 1, &jMask);
+ }
+
+ jFormats = env->NewIntArray(nAudioPort->num_formats);
+ if (jFormats == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ for (size_t j = 0; j < nAudioPort->num_formats; j++) {
+ jint jFormat = audioFormatFromNative(nAudioPort->formats[j]);
+ env->SetIntArrayRegion(jFormats, j, 1, &jFormat);
+ }
+
+ jGains = env->NewObjectArray(nAudioPort->num_gains,
+ gAudioGainClass, NULL);
+ if (jGains == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ for (size_t j = 0; j < nAudioPort->num_gains; j++) {
+ audio_channel_mask_t nMask = nAudioPort->gains[j].channel_mask;
+ if (useInMask) {
+ jMask = inChannelMaskFromNative(nMask);
+ ALOGV("convertAudioPortConfigFromNative IN mask java %x native %x", jMask, nMask);
+ } else {
+ jMask = outChannelMaskFromNative(nMask);
+ ALOGV("convertAudioPortConfigFromNative OUT mask java %x native %x", jMask, nMask);
+ }
+
+ jobject jGain = env->NewObject(gAudioGainClass, gAudioGainCstor,
+ j,
+ nAudioPort->gains[j].mode,
+ jMask,
+ nAudioPort->gains[j].min_value,
+ nAudioPort->gains[j].max_value,
+ nAudioPort->gains[j].default_value,
+ nAudioPort->gains[j].step_value,
+ nAudioPort->gains[j].min_ramp_ms,
+ nAudioPort->gains[j].max_ramp_ms);
+ if (jGain == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ env->SetObjectArrayElement(jGains, j, jGain);
+ env->DeleteLocalRef(jGain);
+ }
+
+ jHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+ nAudioPort->id);
+ if (jHandle == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+
+ if (nAudioPort->type == AUDIO_PORT_TYPE_DEVICE) {
+ ALOGV("convertAudioPortFromNative is a device %08x", nAudioPort->ext.device.type);
+ jstring jAddress = env->NewStringUTF(nAudioPort->ext.device.address);
+ *jAudioPort = env->NewObject(gAudioDevicePortClass, gAudioDevicePortCstor,
+ jHandle, jSamplingRates, jChannelMasks, jFormats, jGains,
+ nAudioPort->ext.device.type, jAddress);
+ env->DeleteLocalRef(jAddress);
+ } else if (nAudioPort->type == AUDIO_PORT_TYPE_MIX) {
+ ALOGV("convertAudioPortFromNative is a mix");
+ *jAudioPort = env->NewObject(gAudioMixPortClass, gAudioMixPortCstor,
+ jHandle, nAudioPort->role, jSamplingRates, jChannelMasks,
+ jFormats, jGains);
+ } else {
+ ALOGE("convertAudioPortFromNative unknown nAudioPort type %d", nAudioPort->type);
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ if (*jAudioPort == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+
+ jobject jAudioPortConfig;
+ jStatus = convertAudioPortConfigFromNative(env,
+ *jAudioPort,
+ &jAudioPortConfig,
+ &nAudioPort->active_config);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ return jStatus;
+ }
+
+ env->SetObjectField(*jAudioPort, gAudioPortFields.mActiveConfig, jAudioPortConfig);
+
+exit:
+ if (jSamplingRates != NULL) {
+ env->DeleteLocalRef(jSamplingRates);
+ }
+ if (jChannelMasks != NULL) {
+ env->DeleteLocalRef(jChannelMasks);
+ }
+ if (jFormats != NULL) {
+ env->DeleteLocalRef(jFormats);
+ }
+ if (jGains != NULL) {
+ env->DeleteLocalRef(jGains);
+ }
+ if (jHandle != NULL) {
+ env->DeleteLocalRef(jHandle);
+ }
+
+ return jStatus;
+}
+
+
+static jint
+android_media_AudioSystem_listAudioPorts(JNIEnv *env, jobject clazz,
+ jobject jPorts, jintArray jGeneration)
+{
+ ALOGV("listAudioPorts");
+
+ if (jPorts == NULL) {
+ ALOGE("listAudioPorts NULL AudioPort ArrayList");
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+ if (!env->IsInstanceOf(jPorts, gArrayListClass)) {
+ ALOGE("listAudioPorts not an arraylist");
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ status_t status;
+ unsigned int generation1;
+ unsigned int generation;
+ unsigned int numPorts;
+ jint *nGeneration;
+ struct audio_port *nPorts = NULL;
+ int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS;
+
+ // get the port count and all the ports until they both return the same generation
+ do {
+ if (attempts-- < 0) {
+ status = TIMED_OUT;
+ break;
+ }
+
+ numPorts = 0;
+ status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE,
+ AUDIO_PORT_TYPE_NONE,
+ &numPorts,
+ NULL,
+ &generation1);
+ if (status != NO_ERROR || numPorts == 0) {
+ ALOGE_IF(status != NO_ERROR, "AudioSystem::listAudioPorts error %d", status);
+ break;
+ }
+ nPorts = (struct audio_port *)realloc(nPorts, numPorts * sizeof(struct audio_port));
+
+ status = AudioSystem::listAudioPorts(AUDIO_PORT_ROLE_NONE,
+ AUDIO_PORT_TYPE_NONE,
+ &numPorts,
+ nPorts,
+ &generation);
+ ALOGV("listAudioPorts AudioSystem::listAudioPorts numPorts %d generation %d generation1 %d",
+ numPorts, generation, generation1);
+ } while (generation1 != generation && status == NO_ERROR);
+
+ jint jStatus = nativeToJavaStatus(status);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+
+ nGeneration = env->GetIntArrayElements(jGeneration, NULL);
+ if (nGeneration == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ nGeneration[0] = generation1;
+ env->ReleaseIntArrayElements(jGeneration, nGeneration, 0);
+
+ for (size_t i = 0; i < numPorts; i++) {
+ jobject jAudioPort;
+ jStatus = convertAudioPortFromNative(env, &jAudioPort, &nPorts[i]);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+ env->CallBooleanMethod(jPorts, gArrayListMethods.add, jAudioPort);
+ }
+
+exit:
+ free(nPorts);
+ return jStatus;
+}
+
+static int
+android_media_AudioSystem_createAudioPatch(JNIEnv *env, jobject clazz,
+ jobjectArray jPatches, jobjectArray jSources, jobjectArray jSinks)
+{
+ status_t status;
+ jint jStatus;
+
+ ALOGV("createAudioPatch");
+ if (jPatches == NULL || jSources == NULL || jSinks == NULL) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ if (env->GetArrayLength(jPatches) != 1) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+ jint numSources = env->GetArrayLength(jSources);
+ if (numSources == 0 || numSources > AUDIO_PATCH_PORTS_MAX) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ jint numSinks = env->GetArrayLength(jSinks);
+ if (numSinks == 0 || numSinks > AUDIO_PATCH_PORTS_MAX) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ audio_patch_handle_t handle = (audio_patch_handle_t)0;
+ jobject jPatch = env->GetObjectArrayElement(jPatches, 0);
+ jobject jPatchHandle = NULL;
+ if (jPatch != NULL) {
+ if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+ jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle);
+ handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId);
+ }
+
+ struct audio_patch nPatch;
+
+ nPatch.id = handle;
+ nPatch.num_sources = 0;
+ nPatch.num_sinks = 0;
+ jobject jSource = NULL;
+ jobject jSink = NULL;
+
+ for (jint i = 0; i < numSources; i++) {
+ jSource = env->GetObjectArrayElement(jSources, i);
+ if (!env->IsInstanceOf(jSource, gAudioPortConfigClass)) {
+ jStatus = (jint)AUDIO_JAVA_BAD_VALUE;
+ goto exit;
+ }
+ jStatus = convertAudioPortConfigToNative(env, &nPatch.sources[i], jSource);
+ env->DeleteLocalRef(jSource);
+ jSource = NULL;
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+ nPatch.num_sources++;
+ }
+
+ for (jint i = 0; i < numSinks; i++) {
+ jSink = env->GetObjectArrayElement(jSinks, i);
+ if (!env->IsInstanceOf(jSink, gAudioPortConfigClass)) {
+ jStatus = (jint)AUDIO_JAVA_BAD_VALUE;
+ goto exit;
+ }
+ jStatus = convertAudioPortConfigToNative(env, &nPatch.sinks[i], jSink);
+ env->DeleteLocalRef(jSink);
+ jSink = NULL;
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+ nPatch.num_sinks++;
+ }
+
+ ALOGV("AudioSystem::createAudioPatch");
+ status = AudioSystem::createAudioPatch(&nPatch, &handle);
+ ALOGV("AudioSystem::createAudioPatch() returned %d hande %d", status, handle);
+
+ jStatus = nativeToJavaStatus(status);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+
+ if (jPatchHandle == NULL) {
+ jPatchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+ handle);
+ if (jPatchHandle == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor, jPatchHandle, jSources, jSinks);
+ if (jPatch == NULL) {
+ jStatus = (jint)AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ env->SetObjectArrayElement(jPatches, 0, jPatch);
+ } else {
+ env->SetIntField(jPatchHandle, gAudioHandleFields.mId, handle);
+ }
+
+exit:
+ if (jPatchHandle != NULL) {
+ env->DeleteLocalRef(jPatchHandle);
+ }
+ if (jPatch != NULL) {
+ env->DeleteLocalRef(jPatch);
+ }
+ if (jSource != NULL) {
+ env->DeleteLocalRef(jSource);
+ }
+ if (jSink != NULL) {
+ env->DeleteLocalRef(jSink);
+ }
+ return jStatus;
+}
+
+static int
+android_media_AudioSystem_releaseAudioPatch(JNIEnv *env, jobject clazz,
+ jobject jPatch)
+{
+ ALOGV("releaseAudioPatch");
+ if (jPatch == NULL) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ audio_patch_handle_t handle = (audio_patch_handle_t)0;
+ jobject jPatchHandle = NULL;
+ if (!env->IsInstanceOf(jPatch, gAudioPatchClass)) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+ jPatchHandle = env->GetObjectField(jPatch, gAudioPatchFields.mHandle);
+ handle = (audio_patch_handle_t)env->GetIntField(jPatchHandle, gAudioHandleFields.mId);
+ env->DeleteLocalRef(jPatchHandle);
+
+ ALOGV("AudioSystem::releaseAudioPatch");
+ status_t status = AudioSystem::releaseAudioPatch(handle);
+ ALOGV("AudioSystem::releaseAudioPatch() returned %d", status);
+ jint jStatus = nativeToJavaStatus(status);
+ return status;
+}
+
+static jint
+android_media_AudioSystem_listAudioPatches(JNIEnv *env, jobject clazz,
+ jobject jPatches, jintArray jGeneration)
+{
+ ALOGV("listAudioPatches");
+ if (jPatches == NULL) {
+ ALOGE("listAudioPatches NULL AudioPatch ArrayList");
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+ if (!env->IsInstanceOf(jPatches, gArrayListClass)) {
+ ALOGE("listAudioPatches not an arraylist");
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ if (jGeneration == NULL || env->GetArrayLength(jGeneration) != 1) {
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ status_t status;
+ unsigned int generation1;
+ unsigned int generation;
+ unsigned int numPatches;
+ jint *nGeneration;
+ struct audio_patch *nPatches = NULL;
+ jobjectArray jSources = NULL;
+ jobject jSource = NULL;
+ jobjectArray jSinks = NULL;
+ jobject jSink = NULL;
+ jobject jPatch = NULL;
+ int attempts = MAX_PORT_GENERATION_SYNC_ATTEMPTS;
+
+ // get the patch count and all the patches until they both return the same generation
+ do {
+ if (attempts-- < 0) {
+ status = TIMED_OUT;
+ break;
+ }
+
+ numPatches = 0;
+ status = AudioSystem::listAudioPatches(&numPatches,
+ NULL,
+ &generation1);
+ if (status != NO_ERROR || numPatches == 0) {
+ ALOGE_IF(status != NO_ERROR, "listAudioPatches AudioSystem::listAudioPatches error %d",
+ status);
+ break;
+ }
+ nPatches = (struct audio_patch *)realloc(nPatches, numPatches * sizeof(struct audio_patch));
+
+ status = AudioSystem::listAudioPatches(&numPatches,
+ nPatches,
+ &generation);
+ ALOGV("listAudioPatches AudioSystem::listAudioPatches numPatches %d generation %d generation1 %d",
+ numPatches, generation, generation1);
+
+ } while (generation1 != generation && status == NO_ERROR);
+
+ jint jStatus = nativeToJavaStatus(status);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+
+ nGeneration = env->GetIntArrayElements(jGeneration, NULL);
+ if (nGeneration == NULL) {
+ jStatus = AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ nGeneration[0] = generation1;
+ env->ReleaseIntArrayElements(jGeneration, nGeneration, 0);
+
+ for (size_t i = 0; i < numPatches; i++) {
+ jobject patchHandle = env->NewObject(gAudioHandleClass, gAudioHandleCstor,
+ nPatches[i].id);
+ if (patchHandle == NULL) {
+ jStatus = AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ ALOGV("listAudioPatches patch %d num_sources %d num_sinks %d",
+ i, nPatches[i].num_sources, nPatches[i].num_sinks);
+
+ env->SetIntField(patchHandle, gAudioHandleFields.mId, nPatches[i].id);
+
+ // load sources
+ jSources = env->NewObjectArray(nPatches[i].num_sources,
+ gAudioPortConfigClass, NULL);
+ if (jSources == NULL) {
+ jStatus = AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+
+ for (size_t j = 0; j < nPatches[i].num_sources; j++) {
+ jStatus = convertAudioPortConfigFromNative(env,
+ NULL,
+ &jSource,
+ &nPatches[i].sources[j]);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+ env->SetObjectArrayElement(jSources, j, jSource);
+ env->DeleteLocalRef(jSource);
+ jSource = NULL;
+ ALOGV("listAudioPatches patch %d source %d is a %s handle %d",
+ i, j,
+ nPatches[i].sources[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix",
+ nPatches[i].sources[j].id);
+ }
+ // load sinks
+ jSinks = env->NewObjectArray(nPatches[i].num_sinks,
+ gAudioPortConfigClass, NULL);
+ if (jSinks == NULL) {
+ jStatus = AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+
+ for (size_t j = 0; j < nPatches[i].num_sinks; j++) {
+ jStatus = convertAudioPortConfigFromNative(env,
+ NULL,
+ &jSink,
+ &nPatches[i].sinks[j]);
+
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ goto exit;
+ }
+ env->SetObjectArrayElement(jSinks, j, jSink);
+ env->DeleteLocalRef(jSink);
+ jSink = NULL;
+ ALOGV("listAudioPatches patch %d sink %d is a %s handle %d",
+ i, j,
+ nPatches[i].sinks[j].type == AUDIO_PORT_TYPE_DEVICE ? "device" : "mix",
+ nPatches[i].sinks[j].id);
+ }
+
+ jPatch = env->NewObject(gAudioPatchClass, gAudioPatchCstor,
+ patchHandle, jSources, jSinks);
+ env->DeleteLocalRef(jSources);
+ jSources = NULL;
+ env->DeleteLocalRef(jSinks);
+ jSinks = NULL;
+ if (jPatch == NULL) {
+ jStatus = AUDIO_JAVA_ERROR;
+ goto exit;
+ }
+ env->CallBooleanMethod(jPatches, gArrayListMethods.add, jPatch);
+ env->DeleteLocalRef(jPatch);
+ jPatch = NULL;
+ }
+
+exit:
+ if (jSources != NULL) {
+ env->DeleteLocalRef(jSources);
+ }
+ if (jSource != NULL) {
+ env->DeleteLocalRef(jSource);
+ }
+ if (jSinks != NULL) {
+ env->DeleteLocalRef(jSinks);
+ }
+ if (jSink != NULL) {
+ env->DeleteLocalRef(jSink);
+ }
+ if (jPatch != NULL) {
+ env->DeleteLocalRef(jPatch);
+ }
+ free(nPatches);
+ return jStatus;
+}
+
+static jint
+android_media_AudioSystem_setAudioPortConfig(JNIEnv *env, jobject clazz,
+ jobject jAudioPortConfig)
+{
+ ALOGV("setAudioPortConfig");
+ if (jAudioPortConfig == NULL) {
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+ if (!env->IsInstanceOf(jAudioPortConfig, gAudioPortConfigClass)) {
+ return AUDIO_JAVA_BAD_VALUE;
+ }
+ struct audio_port_config nAudioPortConfig;
+ jint jStatus = convertAudioPortConfigToNative(env, &nAudioPortConfig, jAudioPortConfig);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ return jStatus;
+ }
+ status_t status = AudioSystem::setAudioPortConfig(&nAudioPortConfig);
+ ALOGV("AudioSystem::setAudioPortConfig() returned %d", status);
+ jStatus = nativeToJavaStatus(status);
+ return jStatus;
+}
+
+static void
+android_media_AudioSystem_eventHandlerSetup(JNIEnv *env, jobject thiz, jobject weak_this)
+{
+ ALOGV("eventHandlerSetup");
+
+ sp<JNIAudioPortCallback> callback = new JNIAudioPortCallback(env, thiz, weak_this);
+
+ AudioSystem::setAudioPortCallback(callback);
+}
+
+static void
+android_media_AudioSystem_eventHandlerFinalize(JNIEnv *env, jobject thiz)
+{
+ ALOGV("eventHandlerFinalize");
+
+ sp<JNIAudioPortCallback> callback;
+
+ AudioSystem::setAudioPortCallback(callback);
+}
+
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
@@ -309,12 +1309,123 @@
{"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency},
{"setLowRamDevice", "(Z)I", (void *)android_media_AudioSystem_setLowRamDevice},
{"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger},
+ {"listAudioPorts", "(Ljava/util/ArrayList;[I)I",
+ (void *)android_media_AudioSystem_listAudioPorts},
+ {"createAudioPatch", "([Landroid/media/AudioPatch;[Landroid/media/AudioPortConfig;[Landroid/media/AudioPortConfig;)I",
+ (void *)android_media_AudioSystem_createAudioPatch},
+ {"releaseAudioPatch", "(Landroid/media/AudioPatch;)I",
+ (void *)android_media_AudioSystem_releaseAudioPatch},
+ {"listAudioPatches", "(Ljava/util/ArrayList;[I)I",
+ (void *)android_media_AudioSystem_listAudioPatches},
+ {"setAudioPortConfig", "(Landroid/media/AudioPortConfig;)I",
+ (void *)android_media_AudioSystem_setAudioPortConfig},
+};
+
+
+static JNINativeMethod gEventHandlerMethods[] = {
+ {"native_setup",
+ "(Ljava/lang/Object;)V",
+ (void *)android_media_AudioSystem_eventHandlerSetup},
+ {"native_finalize",
+ "()V",
+ (void *)android_media_AudioSystem_eventHandlerFinalize},
};
int register_android_media_AudioSystem(JNIEnv *env)
{
+
+ jclass arrayListClass = env->FindClass("java/util/ArrayList");
+ gArrayListClass = (jclass) env->NewGlobalRef(arrayListClass);
+ gArrayListMethods.add = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
+
+ jclass audioHandleClass = env->FindClass("android/media/AudioHandle");
+ gAudioHandleClass = (jclass) env->NewGlobalRef(audioHandleClass);
+ gAudioHandleCstor = env->GetMethodID(audioHandleClass, "<init>", "(I)V");
+ gAudioHandleFields.mId = env->GetFieldID(audioHandleClass, "mId", "I");
+
+ jclass audioPortClass = env->FindClass("android/media/AudioPort");
+ gAudioPortClass = (jclass) env->NewGlobalRef(audioPortClass);
+ gAudioPortCstor = env->GetMethodID(audioPortClass, "<init>",
+ "(Landroid/media/AudioHandle;I[I[I[I[Landroid/media/AudioGain;)V");
+ gAudioPortFields.mHandle = env->GetFieldID(audioPortClass, "mHandle",
+ "Landroid/media/AudioHandle;");
+ gAudioPortFields.mRole = env->GetFieldID(audioPortClass, "mRole", "I");
+ gAudioPortFields.mGains = env->GetFieldID(audioPortClass, "mGains",
+ "[Landroid/media/AudioGain;");
+ gAudioPortFields.mActiveConfig = env->GetFieldID(audioPortClass, "mActiveConfig",
+ "Landroid/media/AudioPortConfig;");
+
+ jclass audioPortConfigClass = env->FindClass("android/media/AudioPortConfig");
+ gAudioPortConfigClass = (jclass) env->NewGlobalRef(audioPortConfigClass);
+ gAudioPortConfigCstor = env->GetMethodID(audioPortConfigClass, "<init>",
+ "(Landroid/media/AudioPort;IIILandroid/media/AudioGainConfig;)V");
+ gAudioPortConfigFields.mPort = env->GetFieldID(audioPortConfigClass, "mPort",
+ "Landroid/media/AudioPort;");
+ gAudioPortConfigFields.mSamplingRate = env->GetFieldID(audioPortConfigClass,
+ "mSamplingRate", "I");
+ gAudioPortConfigFields.mChannelMask = env->GetFieldID(audioPortConfigClass,
+ "mChannelMask", "I");
+ gAudioPortConfigFields.mFormat = env->GetFieldID(audioPortConfigClass, "mFormat", "I");
+ gAudioPortConfigFields.mGain = env->GetFieldID(audioPortConfigClass, "mGain",
+ "Landroid/media/AudioGainConfig;");
+ gAudioPortConfigFields.mConfigMask = env->GetFieldID(audioPortConfigClass, "mConfigMask", "I");
+
+ jclass audioDevicePortConfigClass = env->FindClass("android/media/AudioDevicePortConfig");
+ gAudioDevicePortConfigClass = (jclass) env->NewGlobalRef(audioDevicePortConfigClass);
+ gAudioDevicePortConfigCstor = env->GetMethodID(audioDevicePortConfigClass, "<init>",
+ "(Landroid/media/AudioDevicePort;IIILandroid/media/AudioGainConfig;)V");
+
+ jclass audioMixPortConfigClass = env->FindClass("android/media/AudioMixPortConfig");
+ gAudioMixPortConfigClass = (jclass) env->NewGlobalRef(audioMixPortConfigClass);
+ gAudioMixPortConfigCstor = env->GetMethodID(audioMixPortConfigClass, "<init>",
+ "(Landroid/media/AudioMixPort;IIILandroid/media/AudioGainConfig;)V");
+
+ jclass audioDevicePortClass = env->FindClass("android/media/AudioDevicePort");
+ gAudioDevicePortClass = (jclass) env->NewGlobalRef(audioDevicePortClass);
+ gAudioDevicePortCstor = env->GetMethodID(audioDevicePortClass, "<init>",
+ "(Landroid/media/AudioHandle;[I[I[I[Landroid/media/AudioGain;ILjava/lang/String;)V");
+
+ jclass audioMixPortClass = env->FindClass("android/media/AudioMixPort");
+ gAudioMixPortClass = (jclass) env->NewGlobalRef(audioMixPortClass);
+ gAudioMixPortCstor = env->GetMethodID(audioMixPortClass, "<init>",
+ "(Landroid/media/AudioHandle;I[I[I[I[Landroid/media/AudioGain;)V");
+
+ jclass audioGainClass = env->FindClass("android/media/AudioGain");
+ gAudioGainClass = (jclass) env->NewGlobalRef(audioGainClass);
+ gAudioGainCstor = env->GetMethodID(audioGainClass, "<init>", "(IIIIIIIII)V");
+
+ jclass audioGainConfigClass = env->FindClass("android/media/AudioGainConfig");
+ gAudioGainConfigClass = (jclass) env->NewGlobalRef(audioGainConfigClass);
+ gAudioGainConfigCstor = env->GetMethodID(audioGainConfigClass, "<init>",
+ "(ILandroid/media/AudioGain;II[II)V");
+ gAudioGainConfigFields.mIndex = env->GetFieldID(gAudioGainConfigClass, "mIndex", "I");
+ gAudioGainConfigFields.mMode = env->GetFieldID(audioGainConfigClass, "mMode", "I");
+ gAudioGainConfigFields.mChannelMask = env->GetFieldID(audioGainConfigClass, "mChannelMask",
+ "I");
+ gAudioGainConfigFields.mValues = env->GetFieldID(audioGainConfigClass, "mValues", "[I");
+ gAudioGainConfigFields.mRampDurationMs = env->GetFieldID(audioGainConfigClass,
+ "mRampDurationMs", "I");
+
+ jclass audioPatchClass = env->FindClass("android/media/AudioPatch");
+ gAudioPatchClass = (jclass) env->NewGlobalRef(audioPatchClass);
+ gAudioPatchCstor = env->GetMethodID(audioPatchClass, "<init>",
+"(Landroid/media/AudioHandle;[Landroid/media/AudioPortConfig;[Landroid/media/AudioPortConfig;)V");
+ gAudioPatchFields.mHandle = env->GetFieldID(audioPatchClass, "mHandle",
+ "Landroid/media/AudioHandle;");
+
+ jclass eventHandlerClass = env->FindClass(kEventHandlerClassPathName);
+ gPostEventFromNative = env->GetStaticMethodID(eventHandlerClass, "postEventFromNative",
+ "(Ljava/lang/Object;IIILjava/lang/Object;)V");
+
+
AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback);
- return AndroidRuntime::registerNativeMethods(env,
+ int status = AndroidRuntime::registerNativeMethods(env,
kClassPathName, gMethods, NELEM(gMethods));
+
+ if (status == 0) {
+ status = AndroidRuntime::registerNativeMethods(env,
+ kEventHandlerClassPathName, gEventHandlerMethods, NELEM(gEventHandlerMethods));
+ }
+ return status;
}
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
index e19ce36..d689864 100644
--- a/core/jni/android_view_RenderNodeAnimator.cpp
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -116,6 +116,11 @@
return reinterpret_cast<jlong>( animator );
}
+static void setStartValue(JNIEnv* env, jobject clazz, jlong animatorPtr, jfloat startValue) {
+ BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
+ animator->setStartValue(startValue);
+}
+
static void setDuration(JNIEnv* env, jobject clazz, jlong animatorPtr, jlong duration) {
LOG_ALWAYS_FATAL_IF(duration < 0, "Duration cannot be negative");
BaseRenderNodeAnimator* animator = reinterpret_cast<BaseRenderNodeAnimator*>(animatorPtr);
@@ -157,6 +162,7 @@
{ "nCreateAnimator", "(Ljava/lang/ref/WeakReference;IF)J", (void*) createAnimator },
{ "nCreateCanvasPropertyFloatAnimator", "(Ljava/lang/ref/WeakReference;JF)J", (void*) createCanvasPropertyFloatAnimator },
{ "nCreateCanvasPropertyPaintAnimator", "(Ljava/lang/ref/WeakReference;JIF)J", (void*) createCanvasPropertyPaintAnimator },
+ { "nSetStartValue", "(JF)V", (void*) setStartValue },
{ "nSetDuration", "(JJ)V", (void*) setDuration },
{ "nGetDuration", "(J)J", (void*) getDuration },
{ "nSetStartDelay", "(JJ)V", (void*) setStartDelay },
diff --git a/core/res/res/anim/input_method_exit.xml b/core/res/res/anim/input_method_exit.xml
index 4c4f6a4..117774a 100644
--- a/core/res/res/anim/input_method_exit.xml
+++ b/core/res/res/anim/input_method_exit.xml
@@ -17,7 +17,7 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
- <translate android:fromYDelta="0" android:toYDelta="-20%"
+ <translate android:fromYDelta="0" android:toYDelta="10%"
android:interpolator="@interpolator/accelerate_quint"
android:duration="@android:integer/config_shortAnimTime"/>
<alpha android:fromAlpha="1.0" android:toAlpha="0.0"
diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml
index 4a956d7..7e212be 100644
--- a/core/res/res/anim/lock_screen_behind_enter.xml
+++ b/core/res/res/anim/lock_screen_behind_enter.xml
@@ -18,10 +18,17 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:background="#ff000000" android:shareInterpolator="false">
+ android:detachWallpaper="true" android:shareInterpolator="false" android:startOffset="60">
<alpha
- android:fromAlpha="1.0" android:toAlpha="1.0"
+ android:fromAlpha="0.0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true"
- android:interpolator="@interpolator/decelerate_quint"
- android:duration="0"/>
+ android:interpolator="@interpolator/linear_out_slow_in"
+ android:duration="@integer/config_shortAnimTime"/>
+ <scale
+ android:fromXScale="0.95" android:toXScale="1.0"
+ android:fromYScale="0.95" android:toYScale="1.0"
+ android:pivotX="50%" android:pivotY="50%"
+ android:fillEnabled="true" android:fillBefore="true"
+ android:interpolator="@interpolator/linear_out_slow_in"
+ android:duration="@integer/config_shortAnimTime" />
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml b/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
deleted file mode 100644
index f7a6a65..0000000
--- a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2007, 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.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:detachWallpaper="true" android:shareInterpolator="false">
- <alpha
- android:fromAlpha="0.0" android:toAlpha="1.0"
- android:fillEnabled="true" android:fillBefore="true"
- android:interpolator="@interpolator/decelerate_quad"
- android:startOffset="@android:integer/config_mediumAnimTime"
- android:duration="@android:integer/config_shortAnimTime"/>
-</set>
diff --git a/core/res/res/color/btn_default_quantum_dark.xml b/core/res/res/color/btn_default_quantum_dark.xml
index f2e772d..ec0f140 100644
--- a/core/res/res/color/btn_default_quantum_dark.xml
+++ b/core/res/res/color/btn_default_quantum_dark.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false" android:alpha="0.5" android:color="@color/quantum_grey_700"/>
- <item android:color="@color/quantum_grey_700"/>
+ <item android:state_enabled="false" android:alpha="0.5" android:color="@color/button_quantum_dark"/>
+ <item android:color="@color/button_quantum_dark"/>
</selector>
diff --git a/core/res/res/color/btn_default_quantum_light.xml b/core/res/res/color/btn_default_quantum_light.xml
index de1bd2c..9536d24 100644
--- a/core/res/res/color/btn_default_quantum_light.xml
+++ b/core/res/res/color/btn_default_quantum_light.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false" android:alpha="0.5" android:color="@color/quantum_grey_300"/>
- <item android:color="@color/quantum_grey_300"/>
+ <item android:state_enabled="false" android:alpha="0.5" android:color="@color/button_quantum_light"/>
+ <item android:color="@color/button_quantum_light"/>
</selector>
diff --git a/core/res/res/drawable-hdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-hdpi/ic_lock_bugreport_alpha.png
deleted file mode 100644
index ba5bd01..0000000
--- a/core/res/res/drawable-hdpi/ic_lock_bugreport_alpha.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-mdpi/ic_lock_bugreport_alpha.png
deleted file mode 100644
index 4e2612d..0000000
--- a/core/res/res/drawable-mdpi/ic_lock_bugreport_alpha.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-xhdpi/ic_lock_bugreport_alpha.png
deleted file mode 100644
index e6ca1ea..0000000
--- a/core/res/res/drawable-xhdpi/ic_lock_bugreport_alpha.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xxhdpi/ic_lock_bugreport_alpha.png b/core/res/res/drawable-xxhdpi/ic_lock_bugreport_alpha.png
deleted file mode 100644
index d6018dd..0000000
--- a/core/res/res/drawable-xxhdpi/ic_lock_bugreport_alpha.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/ic_lock_bugreport.xml b/core/res/res/drawable/ic_lock_bugreport.xml
index a3f82ce..b93a09a 100644
--- a/core/res/res/drawable/ic_lock_bugreport.xml
+++ b/core/res/res/drawable/ic_lock_bugreport.xml
@@ -1,19 +1,28 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!--
+Copyright (C) 2014 The Android Open Source Project
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
+ http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="32dp"
+ android:height="32dp"/>
-<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
- android:src="@drawable/ic_lock_bugreport_alpha"
- android:tint="?attr/colorControlNormal" />
+ <viewport
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"/>
+
+ <path
+ android:fill="?attr/colorControlNormal"
+ android:pathData="M20.0,8.0l-2.8,0.0c-0.5,-0.8 -1.1,-1.5 -1.8,-2.0L17.0,4.4L15.6,3.0l-2.2,2.2C13.0,5.1 12.5,5.0 12.0,5.0s-1.0,0.1 -1.4,0.2L8.4,3.0L7.0,4.4L8.6,6.0C7.9,6.5 7.3,7.2 6.8,8.0L4.0,8.0l0.0,2.0l2.1,0.0C6.0,10.3 6.0,10.7 6.0,11.0l0.0,1.0L4.0,12.0l0.0,2.0l2.0,0.0l0.0,1.0c0.0,0.3 0.0,0.7 0.1,1.0L4.0,16.0l0.0,2.0l2.8,0.0c1.0,1.8 3.0,3.0 5.2,3.0s4.2,-1.2 5.2,-3.0L20.0,18.0l0.0,-2.0l-2.1,0.0c0.1,-0.3 0.1,-0.7 0.1,-1.0l0.0,-1.0l2.0,0.0l0.0,-2.0l-2.0,0.0l0.0,-1.0c0.0,-0.3 0.0,-0.7 -0.1,-1.0L20.0,10.0L20.0,8.0zM14.0,16.0l-4.0,0.0l0.0,-2.0l4.0,0.0L14.0,16.0zM14.0,12.0l-4.0,0.0l0.0,-2.0l4.0,0.0L14.0,12.0z"/>
+</vector>
diff --git a/core/res/res/drawable/list_selector_quantum.xml b/core/res/res/drawable/item_background_borderless_quantum.xml
similarity index 82%
rename from core/res/res/drawable/list_selector_quantum.xml
rename to core/res/res/drawable/item_background_borderless_quantum.xml
index 6cd59e5..c2a1c127 100644
--- a/core/res/res/drawable/list_selector_quantum.xml
+++ b/core/res/res/drawable/item_background_borderless_quantum.xml
@@ -15,8 +15,5 @@
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:tint="?attr/colorControlHighlight">
- <item android:id="@id/mask">
- <color android:color="@color/white" />
- </item>
-</ripple>
+ android:tint="?attr/colorControlHighlight"
+ android:pinned="true" />
diff --git a/core/res/res/drawable/item_background_quantum.xml b/core/res/res/drawable/item_background_quantum.xml
index c2a1c127..039ca51 100644
--- a/core/res/res/drawable/item_background_quantum.xml
+++ b/core/res/res/drawable/item_background_quantum.xml
@@ -15,5 +15,8 @@
-->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
- android:tint="?attr/colorControlHighlight"
- android:pinned="true" />
+ android:tint="?attr/colorControlHighlight">
+ <item android:id="@id/mask">
+ <color android:color="@color/white" />
+ </item>
+</ripple>
\ No newline at end of file
diff --git a/core/res/res/layout/alert_dialog_quantum.xml b/core/res/res/layout/alert_dialog_quantum.xml
index e109425f4..7fd22ad 100644
--- a/core/res/res/layout/alert_dialog_quantum.xml
+++ b/core/res/res/layout/alert_dialog_quantum.xml
@@ -23,7 +23,10 @@
android:orientation="vertical"
android:background="@drawable/dialog_background_quantum"
android:translationZ="@dimen/floating_window_z"
- android:layout_margin="@dimen/floating_window_margin">
+ android:layout_marginLeft="@dimen/floating_window_margin_left"
+ android:layout_marginTop="@dimen/floating_window_margin_top"
+ android:layout_marginRight="@dimen/floating_window_margin_right"
+ android:layout_marginBottom="@dimen/floating_window_margin_bottom">
<LinearLayout android:id="@+id/topPanel"
android:layout_width="match_parent"
@@ -44,7 +47,7 @@
android:scaleType="fitCenter"
android:src="@null" />
<TextView android:id="@+id/alertTitle"
- style="?android:attr/windowTitleStyle"
+ style="?attr/windowTitleStyle"
android:singleLine="true"
android:ellipsize="end"
android:layout_width="match_parent"
@@ -65,7 +68,7 @@
android:layout_height="wrap_content"
android:clipToPadding="false">
<TextView android:id="@+id/message"
- style="?android:attr/textAppearanceMedium"
+ style="?attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dip"
@@ -92,26 +95,24 @@
android:gravity="end"
android:padding="16dip">
<LinearLayout
- style="?android:attr/buttonBarStyle"
+ style="?attr/buttonBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layoutDirection="locale">
<Button android:id="@+id/button3"
- style="?android:attr/buttonBarButtonStyle"
+ style="?attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginRight="8dip"
android:maxLines="2"
android:minHeight="@dimen/alert_dialog_button_bar_height" />
<Button android:id="@+id/button2"
- style="?android:attr/buttonBarButtonStyle"
+ style="?attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:minHeight="@dimen/alert_dialog_button_bar_height" />
<Button android:id="@+id/button1"
- style="?android:attr/buttonBarButtonStyle"
- android:layout_marginLeft="8dip"
+ style="?attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5fec907..c0286f1 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -890,9 +890,12 @@
with the appearance of a singel button broken into segments. -->
<attr name="segmentedButtonStyle" format="reference" />
- <!-- Background drawable for standalone items that need focus/pressed states. -->
+ <!-- Background drawable for bordered standalone items that need focus/pressed states. -->
<attr name="selectableItemBackground" format="reference" />
+ <!-- Background drawable for borderless standalone items that need focus/pressed states. -->
+ <attr name="selectableItemBackgroundBorderless" format="reference" />
+
<!-- Style for buttons without an explicit border, often used in groups. -->
<attr name="borderlessButtonStyle" format="reference" />
@@ -4658,11 +4661,11 @@
<!-- Drawable used to show animated touch feedback. -->
<declare-styleable name="RippleDrawable">
- <!-- The tint to use for feedback ripples. This attribute is required. -->
+ <!-- The tint to use for ripple effects. This attribute is required. -->
<attr name="tint" />
- <!-- Specifies the Porter-Duff blending mode used to apply the tint. The default vlaue is src_atop, which draws over the opaque parts of the drawable. -->
+ <!-- Specifies the Porter-Duff blending mode used to apply the tint. The default value is src_atop, which draws over the opaque parts of the drawable. -->
<attr name="tintMode" />
- <!-- Whether to pin feedback ripples to the center of the drawable. Default value is false. -->
+ <!-- Whether to pin ripple effects to the center of the drawable. Default value is false. -->
<attr name="pinned" format="boolean" />
</declare-styleable>
diff --git a/core/res/res/values/colors_quantum.xml b/core/res/res/values/colors_quantum.xml
index 556463e..976930c0 100644
--- a/core/res/res/values/colors_quantum.xml
+++ b/core/res/res/values/colors_quantum.xml
@@ -16,8 +16,14 @@
<!-- Colors specific to Quantum themes. -->
<resources>
- <color name="background_quantum_dark">#ff303030</color>
- <color name="background_quantum_light">@color/white</color>
+ <color name="background_quantum_dark">#ff414042</color>
+ <color name="background_quantum_light">#fff1f2f2</color>
+
+ <color name="ripple_quantum_dark">#30ffffff</color>
+ <color name="ripple_quantum_light">#30000000</color>
+
+ <color name="button_quantum_dark">#ff5a595b</color>
+ <color name="button_quantum_light">#ffd6d7d7</color>
<color name="bright_foreground_quantum_dark">@color/white</color>
<color name="bright_foreground_quantum_light">@color/black</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e9d8ccc..f6732d3 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1538,6 +1538,7 @@
-->
<string-array translatable="false" name="config_globalActionsList">
<item>power</item>
+ <item>bugreport</item>
<item>users</item>
</string-array>
diff --git a/core/res/res/values/dimens_quantum.xml b/core/res/res/values/dimens_quantum.xml
index 2defee2..b5ba1ca 100644
--- a/core/res/res/values/dimens_quantum.xml
+++ b/core/res/res/values/dimens_quantum.xml
@@ -52,7 +52,10 @@
<dimen name="text_size_small_quantum">14sp</dimen>
<dimen name="floating_window_z">16dp</dimen>
- <dimen name="floating_window_margin">32dp</dimen>
+ <dimen name="floating_window_margin_left">16dp</dimen>
+ <dimen name="floating_window_margin_top">8dp</dimen>
+ <dimen name="floating_window_margin_right">16dp</dimen>
+ <dimen name="floating_window_margin_bottom">32dp</dimen>
<!-- the amount of elevation for pressed button state-->
<dimen name="button_pressed_z">2dp</dimen>
diff --git a/core/res/res/values/donottranslate_quantum.xml b/core/res/res/values/donottranslate_quantum.xml
index 83cc4e5..e53c40e 100644
--- a/core/res/res/values/donottranslate_quantum.xml
+++ b/core/res/res/values/donottranslate_quantum.xml
@@ -27,6 +27,6 @@
<string name="font_family_body_1_quantum">sans-serif</string>
<string name="font_family_caption_quantum">sans-serif</string>
<string name="font_family_menu_quantum">sans-serif-medium</string>
- <string name="font_family_button_quantum">sans-serif</string>
+ <string name="font_family_button_quantum">sans-serif-medium</string>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 2d5477c..e9a2f9f 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2178,6 +2178,7 @@
<public type="attr" name="contentInsetLeft" />
<public type="attr" name="contentInsetRight" />
<public type="attr" name="paddingMode" />
+ <public type="attr" name="selectableItemBackgroundBorderless" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 8286ef9..7234911 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -419,6 +419,8 @@
current device state, to send as an e-mail message. It will take a little
time from starting the bug report until it is ready to be sent; please be
patient.</string>
+ <!-- Format for build summary info [CHAR LIMIT=NONE] -->
+ <string name="bugreport_status" translatable="false">%s (%s)</string>
<!-- label for item that enables silent mode in phone options dialog -->
<string name="global_action_toggle_silent_mode">Silent mode</string>
diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index 6943533..108334fa 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -404,8 +404,10 @@
<item name="textAppearance">?attr/textAppearanceButton</item>
<item name="textColor">?attr/textColorPrimary</item>
<item name="minHeight">48dip</item>
- <item name="minWidth">96dip</item>
- <item name="stateListAnimator">@anim/button_state_list_anim_quantum</item>
+ <item name="minWidth">88dip</item>
+
+ <!-- TODO: Turn this back on when we support inset drawable outlines. -->
+ <!-- <item name="stateListAnimator">@anim/button_state_list_anim_quantum</item> -->
</style>
<!-- Small bordered ink button -->
@@ -434,7 +436,6 @@
<item name="background">@drawable/btn_toggle_quantum</item>
<item name="textOn">@string/capital_on</item>
<item name="textOff">@string/capital_off</item>
- <item name="textAppearance">?attr/textAppearanceSmall</item>
<item name="minHeight">48dip</item>
</style>
@@ -468,34 +469,29 @@
<item name="paddingEnd">8dp</item>
</style>
- <style name="Widget.Quantum.CheckedTextView" parent="Widget.CheckedTextView">
- <item name="drawablePadding">4dip</item>
- </style>
-
+ <style name="Widget.Quantum.CheckedTextView" parent="Widget.CheckedTextView" />
<style name="Widget.Quantum.TextSelectHandle" parent="Widget.TextSelectHandle"/>
<style name="Widget.Quantum.TextSuggestionsPopupWindow" parent="Widget.TextSuggestionsPopupWindow"/>
<style name="Widget.Quantum.AbsListView" parent="Widget.AbsListView"/>
<style name="Widget.Quantum.AutoCompleteTextView" parent="Widget.AutoCompleteTextView">
- <item name="dropDownSelector">@drawable/list_selector_quantum</item>
+ <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
<item name="popupBackground">@drawable/popup_background_quantum</item>
</style>
<style name="Widget.Quantum.CompoundButton" parent="Widget.CompoundButton"/>
<style name="Widget.Quantum.CompoundButton.CheckBox" parent="Widget.CompoundButton.CheckBox">
- <item name="background">?attr/selectableItemBackground</item>
- <item name="drawablePadding">4dip</item>
+ <item name="background">?attr/selectableItemBackgroundBorderless</item>
</style>
<style name="Widget.Quantum.CompoundButton.RadioButton" parent="Widget.CompoundButton.RadioButton">
- <item name="background">?attr/selectableItemBackground</item>
- <item name="drawablePadding">4dip</item>
+ <item name="background">?attr/selectableItemBackgroundBorderless</item>
</style>
<style name="Widget.Quantum.CompoundButton.Star" parent="Widget.CompoundButton.Star">
<item name="button">@drawable/btn_star_quantum</item>
- <item name="background">?attr/selectableItemBackground</item>
+ <item name="background">?attr/selectableItemBackgroundBorderless</item>
</style>
<style name="Widget.Quantum.CompoundButton.Switch">
@@ -507,7 +503,7 @@
<item name="textOff"></item>
<item name="switchMinWidth">4dip</item>
<item name="switchPadding">4dip</item>
- <item name="background">?attr/selectableItemBackground</item>
+ <item name="background">?attr/selectableItemBackgroundBorderless</item>
</style>
<style name="Widget.Quantum.EditText" parent="Widget.EditText"/>
@@ -522,7 +518,10 @@
<style name="Widget.Quantum.ExpandableListView.White"/>
<style name="Widget.Quantum.Gallery" parent="Widget.Gallery"/>
<style name="Widget.Quantum.GestureOverlayView" parent="Widget.GestureOverlayView"/>
- <style name="Widget.Quantum.GridView" parent="Widget.GridView"/>
+
+ <style name="Widget.Quantum.GridView" parent="Widget.GridView">
+ <item name="android:listSelector">?attr/selectableItemBackground</item>
+ </style>
<style name="Widget.Quantum.CalendarView" parent="Widget.CalendarView">
<item name="selectedWeekBackgroundColor">#330099FF</item>
@@ -586,7 +585,7 @@
<style name="Widget.Quantum.PopupWindow" parent="Widget.PopupWindow"/>
<style name="Widget.Quantum.PopupWindow.ActionMode">
- <item name="popupBackground">@color/black</item>
+ <item name="popupBackground">@drawable/popup_background_quantum</item>
<item name="popupAnimationStyle">@style/Animation.PopupWindow.ActionMode</item>
</style>
@@ -626,7 +625,7 @@
<item name="paddingStart">16dip</item>
<item name="paddingEnd">16dip</item>
<item name="mirrorForRtl">true</item>
- <item name="background">?attr/selectableItemBackground</item>
+ <item name="background">?attr/selectableItemBackgroundBorderless</item>
</style>
<style name="Widget.Quantum.RatingBar" parent="Widget.RatingBar">
@@ -653,7 +652,7 @@
<style name="Widget.Quantum.Spinner" parent="Widget.Spinner.DropDown">
<item name="background">@drawable/spinner_background_quantum</item>
- <item name="dropDownSelector">@drawable/list_selector_quantum</item>
+ <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
<item name="popupBackground">@drawable/popup_background_quantum</item>
<item name="dropDownVerticalOffset">0dip</item>
<item name="dropDownHorizontalOffset">0dip</item>
@@ -712,7 +711,7 @@
<style name="Widget.Quantum.QuickContactBadgeSmall.WindowLarge" parent="Widget.QuickContactBadgeSmall.WindowLarge"/>
<style name="Widget.Quantum.ListPopupWindow" parent="Widget.ListPopupWindow">
- <item name="dropDownSelector">@drawable/list_selector_quantum</item>
+ <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item>
<item name="popupBackground">@drawable/popup_background_quantum</item>
<item name="popupAnimationStyle">@style/Animation.Quantum.Popup</item>
<item name="dropDownVerticalOffset">0dip</item>
@@ -809,7 +808,7 @@
</style>
<style name="Widget.Quantum.MediaRouteButton">
- <item name="background">?attr/selectableItemBackground</item>
+ <item name="background">?attr/selectableItemBackgroundBorderless</item>
<item name="externalRouteEnabledDrawable">@drawable/ic_media_route_quantum</item>
<item name="minWidth">56dp</item>
<item name="minHeight">48dp</item>
@@ -879,12 +878,7 @@
<style name="Widget.Quantum.Light.ListView" parent="Widget.Quantum.ListView"/>
<style name="Widget.Quantum.Light.ListView.White" parent="Widget.Quantum.ListView.White"/>
<style name="Widget.Quantum.Light.PopupWindow" parent="Widget.Quantum.PopupWindow"/>
-
- <style name="Widget.Quantum.Light.PopupWindow.ActionMode">
- <item name="popupBackground">@color/white</item>
- <item name="popupAnimationStyle">@style/Animation.PopupWindow.ActionMode</item>
- </style>
-
+ <style name="Widget.Quantum.Light.PopupWindow.ActionMode" parent="Widget.Quantum.PopupWindow.ActionMode"/>
<style name="Widget.Quantum.Light.ProgressBar" parent="Widget.Quantum.ProgressBar"/>
<style name="Widget.Quantum.Light.ProgressBar.Horizontal" parent="Widget.Quantum.ProgressBar.Horizontal"/>
<style name="Widget.Quantum.Light.ProgressBar.Small" parent="Widget.Quantum.ProgressBar.Small"/>
@@ -894,7 +888,6 @@
<style name="Widget.Quantum.Light.ProgressBar.Small.Inverse" parent="Widget.Quantum.ProgressBar.Small.Inverse"/>
<style name="Widget.Quantum.Light.ProgressBar.Large.Inverse" parent="Widget.Quantum.ProgressBar.Large.Inverse"/>
<style name="Widget.Quantum.Light.SeekBar" parent="Widget.Quantum.SeekBar"/>
-
<style name="Widget.Quantum.Light.RatingBar" parent="Widget.Quantum.RatingBar" />
<style name="Widget.Quantum.Light.RatingBar.Indicator" parent="Widget.RatingBar.Indicator">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6e1629b..8b1ca31 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1388,6 +1388,7 @@
<java-symbol type="string" name="android_upgrading_title" />
<java-symbol type="string" name="bugreport_title" />
<java-symbol type="string" name="bugreport_message" />
+ <java-symbol type="string" name="bugreport_status" />
<java-symbol type="string" name="faceunlock_multiple_failures" />
<java-symbol type="string" name="global_action_power_off" />
<java-symbol type="string" name="global_actions_airplane_mode_off_status" />
@@ -1679,7 +1680,6 @@
<java-symbol type="anim" name="push_down_out" />
<java-symbol type="anim" name="push_up_in" />
<java-symbol type="anim" name="push_up_out" />
- <java-symbol type="anim" name="lock_screen_wallpaper_behind_enter" />
<java-symbol type="anim" name="lock_screen_behind_enter" />
<java-symbol type="bool" name="config_alwaysUseCdmaRssi" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 41f4ff8..648660b 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -124,6 +124,7 @@
<item name="buttonStyleToggle">@android:style/Widget.Button.Toggle</item>
<item name="selectableItemBackground">@android:drawable/item_background</item>
+ <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackground</item>
<item name="borderlessButtonStyle">?android:attr/buttonStyle</item>
<item name="homeAsUpIndicator">@android:drawable/ic_ab_back_holo_dark</item>
@@ -1032,6 +1033,7 @@
<item name="mediaRouteButtonStyle">@android:style/Widget.Holo.MediaRouteButton</item>
<item name="selectableItemBackground">@android:drawable/item_background_holo_dark</item>
+ <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackground</item>
<item name="borderlessButtonStyle">@android:style/Widget.Holo.Button.Borderless</item>
<item name="homeAsUpIndicator">@android:drawable/ic_ab_back_holo_dark</item>
@@ -1372,6 +1374,7 @@
<item name="mediaRouteButtonStyle">@android:style/Widget.Holo.Light.MediaRouteButton</item>
<item name="selectableItemBackground">@android:drawable/item_background_holo_light</item>
+ <item name="selectableItemBackgroundBorderless">?android:attr/selectableItemBackground</item>
<item name="borderlessButtonStyle">@android:style/Widget.Holo.Light.Button.Borderless</item>
<item name="homeAsUpIndicator">@android:drawable/ic_ab_back_holo_light</item>
diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml
index 47ba764..cdbd771 100644
--- a/core/res/res/values/themes_quantum.xml
+++ b/core/res/res/values/themes_quantum.xml
@@ -101,6 +101,7 @@
<item name="mediaRouteButtonStyle">@style/Widget.Quantum.MediaRouteButton</item>
<item name="selectableItemBackground">@drawable/item_background_quantum</item>
+ <item name="selectableItemBackgroundBorderless">@drawable/item_background_borderless_quantum</item>
<item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless</item>
<item name="homeAsUpIndicator">@drawable/ic_ab_back_quantum</item>
@@ -125,8 +126,7 @@
<item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum_anim</item>
<item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item>
- <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item>
-
+ <item name="listChoiceBackgroundIndicator">?attr/selectableItemBackground</item>
<item name="activatedBackgroundIndicator">@drawable/activated_background_quantum</item>
<item name="listDividerAlertDialog">@drawable/list_divider_quantum</item>
@@ -308,7 +308,7 @@
<item name="actionModePopupWindowStyle">@style/Widget.Quantum.PopupWindow.ActionMode</item>
<item name="actionBarWidgetTheme">@style/ThemeOverlay.Quantum.ActionBarWidget</item>
<item name="actionBarTheme">@null</item>
- <item name="actionBarItemBackground">@drawable/item_background_quantum</item>
+ <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
<item name="actionModeCutDrawable">@drawable/ic_menu_cut_quantum</item>
<item name="actionModeCopyDrawable">@drawable/ic_menu_copy_quantum</item>
@@ -378,8 +378,8 @@
<item name="colorControlNormal">?attr/textColorSecondary</item>
<item name="colorControlActivated">?attr/colorPrimary</item>
- <item name="colorControlHighlight">#30ffffff</item>
+ <item name="colorControlHighlight">@color/ripple_quantum_dark</item>
<item name="colorButtonNormal">@color/btn_default_quantum_dark</item>
</style>
@@ -446,6 +446,7 @@
<item name="mediaRouteButtonStyle">@style/Widget.Quantum.Light.MediaRouteButton</item>
<item name="selectableItemBackground">@drawable/item_background_quantum</item>
+ <item name="selectableItemBackgroundBorderless">@drawable/item_background_borderless_quantum</item>
<item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item>
<item name="homeAsUpIndicator">@drawable/ic_ab_back_quantum</item>
@@ -470,8 +471,7 @@
<item name="listChoiceIndicatorSingle">@drawable/btn_radio_quantum_anim</item>
<item name="listChoiceIndicatorMultiple">@drawable/btn_check_quantum_anim</item>
- <item name="listChoiceBackgroundIndicator">@drawable/list_selector_quantum</item>
-
+ <item name="listChoiceBackgroundIndicator">?attr/selectableItemBackground</item>
<item name="activatedBackgroundIndicator">@drawable/activated_background_quantum</item>
<item name="expandableListPreferredItemPaddingLeft">40dip</item>
@@ -655,7 +655,7 @@
<item name="actionModePopupWindowStyle">@style/Widget.Quantum.Light.PopupWindow.ActionMode</item>
<item name="actionBarWidgetTheme">@style/ThemeOverlay.Quantum.ActionBarWidget</item>
<item name="actionBarTheme">@null</item>
- <item name="actionBarItemBackground">@drawable/item_background_quantum</item>
+ <item name="actionBarItemBackground">?attr/selectableItemBackgroundBorderless</item>
<item name="actionModeCutDrawable">@drawable/ic_menu_cut_quantum</item>
<item name="actionModeCopyDrawable">@drawable/ic_menu_copy_quantum</item>
@@ -668,7 +668,7 @@
<item name="dividerVertical">?attr/listDivider</item>
<item name="dividerHorizontal">?attr/listDivider</item>
<item name="buttonBarStyle">@style/Widget.Quantum.Light.ButtonBar</item>
- <item name="buttonBarButtonStyle">@style/Widget.Quantum.Light.Button.Borderless.Small</item>
+ <item name="buttonBarButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item>
<item name="segmentedButtonStyle">@style/Widget.Quantum.Light.SegmentedButton</item>
<!-- SearchView attributes -->
@@ -721,8 +721,8 @@
<item name="colorControlNormal">?attr/textColorSecondary</item>
<item name="colorControlActivated">?attr/colorPrimary</item>
- <item name="colorControlHighlight">#30000000</item>
+ <item name="colorControlHighlight">@color/ripple_quantum_light</item>
<item name="colorButtonNormal">@color/btn_default_quantum_light</item>
</style>
@@ -770,7 +770,8 @@
<item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_light</item>
<item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_light</item>
- <item name="colorButtonNormal">@color/quantum_grey_100</item>
+ <item name="colorControlHighlight">@color/ripple_quantum_light</item>
+ <item name="colorButtonNormal">@color/btn_default_quantum_light</item>
</style>
<!-- Theme overlay that replaces colors with their dark versions but preserves
@@ -805,7 +806,8 @@
<item name="fastScrollPreviewBackgroundLeft">@drawable/fastscroll_label_left_holo_dark</item>
<item name="fastScrollPreviewBackgroundRight">@drawable/fastscroll_label_right_holo_dark</item>
- <item name="colorButtonNormal">@color/quantum_grey_700</item>
+ <item name="colorControlHighlight">@color/ripple_quantum_dark</item>
+ <item name="colorButtonNormal">@color/btn_default_quantum_dark</item>
</style>
<!-- Theme overlay that replaces the activated control color (which by default
@@ -1002,7 +1004,7 @@
<item name="colorBackgroundCacheHint">@null</item>
<item name="buttonBarStyle">@style/Widget.Quantum.ButtonBar.AlertDialog</item>
- <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless.Small</item>
+ <item name="borderlessButtonStyle">@style/Widget.Quantum.Button.Borderless</item>
<item name="textAppearance">@style/TextAppearance.Quantum</item>
<item name="textAppearanceInverse">@style/TextAppearance.Quantum.Inverse</item>
@@ -1121,7 +1123,7 @@
<item name="colorBackgroundCacheHint">@null</item>
<item name="buttonBarStyle">@style/Widget.Quantum.Light.ButtonBar.AlertDialog</item>
- <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless.Small</item>
+ <item name="borderlessButtonStyle">@style/Widget.Quantum.Light.Button.Borderless</item>
<item name="textAppearance">@style/TextAppearance.Quantum</item>
<item name="textAppearanceInverse">@style/TextAppearance.Quantum.Inverse</item>
diff --git a/data/fonts/Roboto-Black.ttf b/data/fonts/Roboto-Black.ttf
index 2cdbe43..cb905bc 100644
--- a/data/fonts/Roboto-Black.ttf
+++ b/data/fonts/Roboto-Black.ttf
Binary files differ
diff --git a/data/fonts/Roboto-Bold.ttf b/data/fonts/Roboto-Bold.ttf
index 15c9b4e..68822ca 100644
--- a/data/fonts/Roboto-Bold.ttf
+++ b/data/fonts/Roboto-Bold.ttf
Binary files differ
diff --git a/data/fonts/Roboto-BoldItalic.ttf b/data/fonts/Roboto-BoldItalic.ttf
index a0abf30..aebf8eb 100644
--- a/data/fonts/Roboto-BoldItalic.ttf
+++ b/data/fonts/Roboto-BoldItalic.ttf
Binary files differ
diff --git a/data/fonts/Roboto-Italic.ttf b/data/fonts/Roboto-Italic.ttf
index 67b5394..2041cbc 100644
--- a/data/fonts/Roboto-Italic.ttf
+++ b/data/fonts/Roboto-Italic.ttf
Binary files differ
diff --git a/data/fonts/Roboto-Light.ttf b/data/fonts/Roboto-Light.ttf
index d9fb64a..aa45340 100644
--- a/data/fonts/Roboto-Light.ttf
+++ b/data/fonts/Roboto-Light.ttf
Binary files differ
diff --git a/data/fonts/Roboto-LightItalic.ttf b/data/fonts/Roboto-LightItalic.ttf
index 1fd1d31..a85444f 100644
--- a/data/fonts/Roboto-LightItalic.ttf
+++ b/data/fonts/Roboto-LightItalic.ttf
Binary files differ
diff --git a/data/fonts/Roboto-Medium.ttf b/data/fonts/Roboto-Medium.ttf
index c63c115..a3c1a1f 100644
--- a/data/fonts/Roboto-Medium.ttf
+++ b/data/fonts/Roboto-Medium.ttf
Binary files differ
diff --git a/data/fonts/Roboto-MediumItalic.ttf b/data/fonts/Roboto-MediumItalic.ttf
index cd7c835..a30aa0c 100644
--- a/data/fonts/Roboto-MediumItalic.ttf
+++ b/data/fonts/Roboto-MediumItalic.ttf
Binary files differ
diff --git a/data/fonts/Roboto-Regular.ttf b/data/fonts/Roboto-Regular.ttf
index 9cb4a5a..0e58508 100644
--- a/data/fonts/Roboto-Regular.ttf
+++ b/data/fonts/Roboto-Regular.ttf
Binary files differ
diff --git a/data/fonts/Roboto-Thin.ttf b/data/fonts/Roboto-Thin.ttf
index f02f100..8779333 100644
--- a/data/fonts/Roboto-Thin.ttf
+++ b/data/fonts/Roboto-Thin.ttf
Binary files differ
diff --git a/data/fonts/Roboto-ThinItalic.ttf b/data/fonts/Roboto-ThinItalic.ttf
index 12a2ce0..b79cb26 100644
--- a/data/fonts/Roboto-ThinItalic.ttf
+++ b/data/fonts/Roboto-ThinItalic.ttf
Binary files differ
diff --git a/data/fonts/RobotoCondensed-Bold.ttf b/data/fonts/RobotoCondensed-Bold.ttf
index 1079af6..3e06c7c 100644
--- a/data/fonts/RobotoCondensed-Bold.ttf
+++ b/data/fonts/RobotoCondensed-Bold.ttf
Binary files differ
diff --git a/data/fonts/RobotoCondensed-BoldItalic.ttf b/data/fonts/RobotoCondensed-BoldItalic.ttf
index e7f13c2..aaf9fe0 100644
--- a/data/fonts/RobotoCondensed-BoldItalic.ttf
+++ b/data/fonts/RobotoCondensed-BoldItalic.ttf
Binary files differ
diff --git a/data/fonts/RobotoCondensed-Italic.ttf b/data/fonts/RobotoCondensed-Italic.ttf
index 7fa04481..d2b611f 100644
--- a/data/fonts/RobotoCondensed-Italic.ttf
+++ b/data/fonts/RobotoCondensed-Italic.ttf
Binary files differ
diff --git a/data/fonts/RobotoCondensed-Light.ttf b/data/fonts/RobotoCondensed-Light.ttf
index 96b75dd..d4eb198 100644
--- a/data/fonts/RobotoCondensed-Light.ttf
+++ b/data/fonts/RobotoCondensed-Light.ttf
Binary files differ
diff --git a/data/fonts/RobotoCondensed-LightItalic.ttf b/data/fonts/RobotoCondensed-LightItalic.ttf
index 7a2c164..a08f3f4 100644
--- a/data/fonts/RobotoCondensed-LightItalic.ttf
+++ b/data/fonts/RobotoCondensed-LightItalic.ttf
Binary files differ
diff --git a/data/fonts/RobotoCondensed-Regular.ttf b/data/fonts/RobotoCondensed-Regular.ttf
index 734cc40..b9fc49c 100644
--- a/data/fonts/RobotoCondensed-Regular.ttf
+++ b/data/fonts/RobotoCondensed-Regular.ttf
Binary files differ
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index f82acc3..1512da5 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -612,7 +612,9 @@
case LINE: {
RectF r = mRect;
float y = r.centerY();
- canvas.drawLine(r.left, y, r.right, y, mStrokePaint);
+ if (haveStroke) {
+ canvas.drawLine(r.left, y, r.right, y, mStrokePaint);
+ }
break;
}
case RING:
@@ -1153,6 +1155,10 @@
// Extract the theme attributes, if any.
st.mAttrPadding = a.extractThemeAttrs();
+ if (st.mPadding == null) {
+ st.mPadding = new Rect();
+ }
+
final Rect pad = st.mPadding;
pad.set(a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_left, pad.left),
a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_top, pad.top),
@@ -1424,12 +1430,10 @@
}
final static class GradientState extends ConstantState {
- public final Rect mPadding = new Rect();
-
public int mChangingConfigurations;
public int mShape = RECTANGLE;
public int mGradient = LINEAR_GRADIENT;
- public int mAngle;
+ public int mAngle = 0;
public Orientation mOrientation;
public ColorStateList mColorStateList;
public ColorStateList mStrokeColorStateList;
@@ -1437,11 +1441,12 @@
public int[] mTempColors; // no need to copy
public float[] mTempPositions; // no need to copy
public float[] mPositions;
- public int mStrokeWidth = -1; // if >= 0 use stroking.
- public float mStrokeDashWidth;
- public float mStrokeDashGap;
- public float mRadius; // use this if mRadiusArray is null
- public float[] mRadiusArray;
+ public int mStrokeWidth = -1; // if >= 0 use stroking.
+ public float mStrokeDashWidth = 0.0f;
+ public float mStrokeDashGap = 0.0f;
+ public float mRadius = 0.0f; // use this if mRadiusArray is null
+ public float[] mRadiusArray = null;
+ public Rect mPadding = null;
public int mWidth = -1;
public int mHeight = -1;
public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO;
@@ -1491,7 +1496,7 @@
mRadiusArray = state.mRadiusArray.clone();
}
if (state.mPadding != null) {
- mPadding.set(state.mPadding);
+ mPadding = new Rect(state.mPadding);
}
mWidth = state.mWidth;
mHeight = state.mHeight;
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 2e47d3a..75cb0a00 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -304,7 +304,7 @@
* @param right The right padding of the new layer.
* @param bottom The bottom padding of the new layer.
*/
- private void addLayer(Drawable layer, int[] themeAttrs, int id, int left, int top, int right,
+ void addLayer(Drawable layer, int[] themeAttrs, int id, int left, int top, int right,
int bottom) {
final LayerState st = mLayerState;
final int N = st.mChildren != null ? st.mChildren.length : 0;
diff --git a/graphics/java/android/graphics/drawable/Ripple.java b/graphics/java/android/graphics/drawable/Ripple.java
index 24e8de6..ada741b 100644
--- a/graphics/java/android/graphics/drawable/Ripple.java
+++ b/graphics/java/android/graphics/drawable/Ripple.java
@@ -25,9 +25,10 @@
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
+import android.util.MathUtils;
import android.view.HardwareCanvas;
import android.view.RenderNodeAnimator;
-import android.view.animation.AccelerateInterpolator;
+import android.view.animation.LinearInterpolator;
import java.util.ArrayList;
@@ -35,7 +36,7 @@
* Draws a Quantum Paper ripple.
*/
class Ripple {
- private static final TimeInterpolator INTERPOLATOR = new AccelerateInterpolator();
+ private static final TimeInterpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
private static final float GLOBAL_SPEED = 1.0f;
private static final float WAVE_TOUCH_DOWN_ACCELERATION = 512.0f * GLOBAL_SPEED;
@@ -47,17 +48,23 @@
private final ArrayList<RenderNodeAnimator> mRunningAnimations = new ArrayList<>();
private final ArrayList<RenderNodeAnimator> mPendingAnimations = new ArrayList<>();
- private final Drawable mOwner;
+ private final RippleDrawable mOwner;
/** Bounds used for computing max radius. */
private final Rect mBounds;
/** Full-opacity color for drawing this ripple. */
- private final int mColor;
+ private int mColor;
/** Maximum ripple radius. */
private float mOuterRadius;
+ /** Screen density used to adjust pixel-based velocities. */
+ private float mDensity;
+
+ private float mStartingX;
+ private float mStartingY;
+
// Hardware rendering properties.
private CanvasProperty<Paint> mPropPaint;
private CanvasProperty<Float> mPropRadius;
@@ -78,13 +85,13 @@
// Software rendering properties.
private float mOuterOpacity = 0;
private float mOpacity = 1;
- private float mRadius = 0;
private float mOuterX;
private float mOuterY;
- private float mX;
- private float mY;
- private boolean mFinished;
+ // Values used to tween between the start and end positions.
+ private float mTweenRadius = 0;
+ private float mTweenX = 0;
+ private float mTweenY = 0;
/** Whether we should be drawing hardware animations. */
private boolean mHardwareAnimating;
@@ -92,28 +99,42 @@
/** Whether we can use hardware acceleration for the exit animation. */
private boolean mCanUseHardware;
+ /** Whether we have an explicit maximum radius. */
+ private boolean mHasMaxRadius;
+
/**
* Creates a new ripple.
*/
- public Ripple(Drawable owner, Rect bounds, int color) {
+ public Ripple(RippleDrawable owner, Rect bounds, float startingX, float startingY) {
mOwner = owner;
mBounds = bounds;
+ mStartingX = startingX;
+ mStartingY = startingY;
+ }
+
+ public void setup(int maxRadius, int color, float density) {
mColor = color | 0xFF000000;
- final float halfWidth = bounds.width() / 2.0f;
- final float halfHeight = bounds.height() / 2.0f;
- mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+ if (maxRadius != RippleDrawable.RADIUS_AUTO) {
+ mHasMaxRadius = true;
+ mOuterRadius = maxRadius;
+ } else {
+ final float halfWidth = mBounds.width() / 2.0f;
+ final float halfHeight = mBounds.height() / 2.0f;
+ mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+ }
+
mOuterX = 0;
mOuterY = 0;
+ mDensity = density;
}
- public void setRadius(float r) {
- mRadius = r;
- invalidateSelf();
- }
-
- public float getRadius() {
- return mRadius;
+ public void onHotspotBoundsChanged() {
+ if (!mHasMaxRadius) {
+ final float halfWidth = mBounds.width() / 2.0f;
+ final float halfHeight = mBounds.height() / 2.0f;
+ mOuterRadius = (float) Math.sqrt(halfWidth * halfWidth + halfHeight * halfHeight);
+ }
}
public void setOpacity(float a) {
@@ -134,29 +155,31 @@
return mOuterOpacity;
}
- public void setX(float x) {
- mX = x;
+ public void setRadiusGravity(float r) {
+ mTweenRadius = r;
invalidateSelf();
}
- public float getX() {
- return mX;
+ public float getRadiusGravity() {
+ return mTweenRadius;
}
- public void setY(float y) {
- mY = y;
+ public void setXGravity(float x) {
+ mTweenX = x;
invalidateSelf();
}
- public float getY() {
- return mY;
+ public float getXGravity() {
+ return mTweenX;
}
- /**
- * Returns whether this ripple has finished exiting.
- */
- public boolean isFinished() {
- return mFinished;
+ public void setYGravity(float y) {
+ mTweenY = y;
+ invalidateSelf();
+ }
+
+ public float getYGravity() {
+ return mTweenY;
}
/**
@@ -204,28 +227,27 @@
}
private boolean drawSoftware(Canvas c, Paint p) {
- final float radius = mRadius;
- final float opacity = mOpacity;
- final float outerOpacity = mOuterOpacity;
+ boolean hasContent = false;
// Cache the paint alpha so we can restore it later.
final int paintAlpha = p.getAlpha();
- final int alpha = (int) (255 * opacity + 0.5f);
- final int outerAlpha = (int) (255 * outerOpacity + 0.5f);
- boolean hasContent = false;
-
- if (outerAlpha > 0 && alpha > 0) {
- p.setAlpha(Math.min(alpha, outerAlpha));
+ final int outerAlpha = (int) (255 * mOuterOpacity + 0.5f);
+ if (outerAlpha > 0 && mOuterRadius > 0) {
+ p.setAlpha(outerAlpha);
p.setStyle(Style.FILL);
c.drawCircle(mOuterX, mOuterY, mOuterRadius, p);
hasContent = true;
}
- if (opacity > 0 && radius > 0) {
+ final int alpha = (int) (255 * mOpacity + 0.5f);
+ final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
+ if (alpha > 0 && radius > 0) {
+ final float x = MathUtils.lerp(mStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
+ final float y = MathUtils.lerp(mStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
p.setAlpha(alpha);
p.setStyle(Style.FILL);
- c.drawCircle(mX, mY, radius, p);
+ c.drawCircle(x, y, radius, p);
hasContent = true;
}
@@ -235,45 +257,49 @@
}
/**
- * Returns the maximum bounds for this ripple.
+ * Returns the maximum bounds of the ripple relative to the ripple center.
*/
public void getBounds(Rect bounds) {
final int outerX = (int) mOuterX;
final int outerY = (int) mOuterY;
final int r = (int) mOuterRadius;
bounds.set(outerX - r, outerY - r, outerX + r, outerY + r);
-
- final int x = (int) mX;
- final int y = (int) mY;
- bounds.union(x - r, y - r, x + r, y + r);
}
/**
- * Starts the enter animation at the specified absolute coordinates.
+ * Specifies the starting position relative to the drawable bounds. No-op if
+ * the ripple has already entered.
*/
- public void enter(float x, float y) {
- mX = x - mBounds.exactCenterX();
- mY = y - mBounds.exactCenterY();
+ public void move(float x, float y) {
+ mStartingX = x;
+ mStartingY = y;
+ }
+ /**
+ * Starts the enter animation.
+ */
+ public void enter() {
final int radiusDuration = (int)
- (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION) + 0.5);
+ (1000 * Math.sqrt(mOuterRadius / WAVE_TOUCH_DOWN_ACCELERATION * mDensity) + 0.5);
final int outerDuration = (int) (1000 * 1.0f / WAVE_OUTER_OPACITY_VELOCITY);
- final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radius", 0, mOuterRadius);
+ final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
radius.setAutoCancel(true);
radius.setDuration(radiusDuration);
+ radius.setInterpolator(LINEAR_INTERPOLATOR);
- final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "x", mOuterX);
+ final ObjectAnimator cX = ObjectAnimator.ofFloat(this, "xGravity", 1);
cX.setAutoCancel(true);
cX.setDuration(radiusDuration);
- final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "y", mOuterY);
+ final ObjectAnimator cY = ObjectAnimator.ofFloat(this, "yGravity", 1);
cY.setAutoCancel(true);
cY.setDuration(radiusDuration);
final ObjectAnimator outer = ObjectAnimator.ofFloat(this, "outerOpacity", 0, 1);
outer.setAutoCancel(true);
outer.setDuration(outerDuration);
+ outer.setInterpolator(LINEAR_INTERPOLATOR);
mAnimRadius = radius;
mAnimOuterOpacity = outer;
@@ -295,15 +321,16 @@
public void exit() {
cancelSoftwareAnimations();
+ final float radius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
final float remaining;
if (mAnimRadius != null && mAnimRadius.isRunning()) {
- remaining = mOuterRadius - mRadius;
+ remaining = mOuterRadius - radius;
} else {
remaining = mOuterRadius;
}
final int radiusDuration = (int) (1000 * Math.sqrt(remaining / (WAVE_TOUCH_UP_ACCELERATION
- + WAVE_TOUCH_DOWN_ACCELERATION)) + 0.5);
+ + WAVE_TOUCH_DOWN_ACCELERATION) * mDensity) + 0.5);
final int opacityDuration = (int) (1000 * mOpacity / WAVE_OPACITY_DECAY_VELOCITY + 0.5f);
// Determine at what time the inner and outer opacity intersect.
@@ -325,6 +352,8 @@
int inflectionOpacity) {
mPendingAnimations.clear();
+ final float startX = MathUtils.lerp(mStartingX - mBounds.exactCenterX(), mOuterX, mTweenX);
+ final float startY = MathUtils.lerp(mStartingY - mBounds.exactCenterY(), mOuterY, mTweenY);
final Paint outerPaint = new Paint();
outerPaint.setAntiAlias(true);
outerPaint.setColor(mColor);
@@ -335,58 +364,69 @@
mPropOuterX = CanvasProperty.createFloat(mOuterX);
mPropOuterY = CanvasProperty.createFloat(mOuterY);
+ final float startRadius = MathUtils.lerp(0, mOuterRadius, mTweenRadius);
final Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(mColor);
paint.setAlpha((int) (255 * mOpacity + 0.5f));
paint.setStyle(Style.FILL);
mPropPaint = CanvasProperty.createPaint(paint);
- mPropRadius = CanvasProperty.createFloat(mRadius);
- mPropX = CanvasProperty.createFloat(mX);
- mPropY = CanvasProperty.createFloat(mY);
+ mPropRadius = CanvasProperty.createFloat(startRadius);
+ mPropX = CanvasProperty.createFloat(startX);
+ mPropY = CanvasProperty.createFloat(startY);
- final RenderNodeAnimator radius = new RenderNodeAnimator(mPropRadius, mOuterRadius);
- radius.setDuration(radiusDuration);
+ final RenderNodeAnimator radiusAnim = new RenderNodeAnimator(mPropRadius, mOuterRadius);
+ radiusAnim.setDuration(radiusDuration);
+ radiusAnim.setInterpolator(LINEAR_INTERPOLATOR);
- final RenderNodeAnimator x = new RenderNodeAnimator(mPropX, mOuterX);
- x.setDuration(radiusDuration);
+ final RenderNodeAnimator xAnim = new RenderNodeAnimator(mPropX, mOuterX);
+ xAnim.setDuration(radiusDuration);
+ xAnim.setInterpolator(LINEAR_INTERPOLATOR);
- final RenderNodeAnimator y = new RenderNodeAnimator(mPropY, mOuterY);
- y.setDuration(radiusDuration);
+ final RenderNodeAnimator yAnim = new RenderNodeAnimator(mPropY, mOuterY);
+ yAnim.setDuration(radiusDuration);
+ yAnim.setInterpolator(LINEAR_INTERPOLATOR);
- final RenderNodeAnimator opacity = new RenderNodeAnimator(mPropPaint,
+ final RenderNodeAnimator opacityAnim = new RenderNodeAnimator(mPropPaint,
RenderNodeAnimator.PAINT_ALPHA, 0);
- opacity.setDuration(opacityDuration);
- opacity.addListener(mAnimationListener);
+ opacityAnim.setDuration(opacityDuration);
+ opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
- final RenderNodeAnimator outerOpacity;
+ final RenderNodeAnimator outerOpacityAnim;
if (outerInflection > 0) {
// Outer opacity continues to increase for a bit.
- outerOpacity = new RenderNodeAnimator(
+ outerOpacityAnim = new RenderNodeAnimator(
mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, inflectionOpacity);
- outerOpacity.setDuration(outerInflection);
+ outerOpacityAnim.setDuration(outerInflection);
+ outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
// Chain the outer opacity exit animation.
final int outerDuration = opacityDuration - outerInflection;
if (outerDuration > 0) {
- final RenderNodeAnimator outerFadeOut = new RenderNodeAnimator(
+ final RenderNodeAnimator outerFadeOutAnim = new RenderNodeAnimator(
mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
- outerFadeOut.setDuration(outerDuration);
- outerFadeOut.setStartDelay(outerInflection);
+ outerFadeOutAnim.setDuration(outerDuration);
+ outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
+ outerFadeOutAnim.setStartDelay(outerInflection);
+ outerFadeOutAnim.addListener(mAnimationListener);
- mPendingAnimations.add(outerFadeOut);
+ mPendingAnimations.add(outerFadeOutAnim);
+ } else {
+ outerOpacityAnim.addListener(mAnimationListener);
}
} else {
- outerOpacity = new RenderNodeAnimator(
+ outerOpacityAnim = new RenderNodeAnimator(
mPropOuterPaint, RenderNodeAnimator.PAINT_ALPHA, 0);
- outerOpacity.setDuration(opacityDuration);
+ outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
+ outerOpacityAnim.setDuration(opacityDuration);
+ outerOpacityAnim.addListener(mAnimationListener);
}
- mPendingAnimations.add(radius);
- mPendingAnimations.add(opacity);
- mPendingAnimations.add(outerOpacity);
- mPendingAnimations.add(x);
- mPendingAnimations.add(y);
+ mPendingAnimations.add(radiusAnim);
+ mPendingAnimations.add(opacityAnim);
+ mPendingAnimations.add(outerOpacityAnim);
+ mPendingAnimations.add(xAnim);
+ mPendingAnimations.add(yAnim);
mHardwareAnimating = true;
@@ -394,65 +434,80 @@
}
private void exitSoftware(int radiusDuration, int opacityDuration, int outerInflection,
- float inflectionOpacity) {
- final ObjectAnimator radius = ObjectAnimator.ofFloat(this, "radius", mOuterRadius);
- radius.setAutoCancel(true);
- radius.setDuration(radiusDuration);
+ int inflectionOpacity) {
+ final ObjectAnimator radiusAnim = ObjectAnimator.ofFloat(this, "radiusGravity", 1);
+ radiusAnim.setAutoCancel(true);
+ radiusAnim.setDuration(radiusDuration);
+ radiusAnim.setInterpolator(LINEAR_INTERPOLATOR);
- final ObjectAnimator x = ObjectAnimator.ofFloat(this, "x", mOuterX);
- x.setAutoCancel(true);
- x.setDuration(radiusDuration);
+ final ObjectAnimator xAnim = ObjectAnimator.ofFloat(this, "xGravity", 1);
+ xAnim.setAutoCancel(true);
+ xAnim.setDuration(radiusDuration);
+ xAnim.setInterpolator(LINEAR_INTERPOLATOR);
- final ObjectAnimator y = ObjectAnimator.ofFloat(this, "y", mOuterY);
- y.setAutoCancel(true);
- y.setDuration(radiusDuration);
+ final ObjectAnimator yAnim = ObjectAnimator.ofFloat(this, "yGravity", 1);
+ yAnim.setAutoCancel(true);
+ yAnim.setDuration(radiusDuration);
+ yAnim.setInterpolator(LINEAR_INTERPOLATOR);
- final ObjectAnimator opacity = ObjectAnimator.ofFloat(this, "opacity", 0);
- opacity.setAutoCancel(true);
- opacity.setDuration(opacityDuration);
- opacity.addListener(mAnimationListener);
+ final ObjectAnimator opacityAnim = ObjectAnimator.ofFloat(this, "opacity", 0);
+ opacityAnim.setAutoCancel(true);
+ opacityAnim.setDuration(opacityDuration);
+ opacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
- final ObjectAnimator outerOpacity;
+ final ObjectAnimator outerOpacityAnim;
if (outerInflection > 0) {
// Outer opacity continues to increase for a bit.
- outerOpacity = ObjectAnimator.ofFloat(this, "outerOpacity", inflectionOpacity);
- outerOpacity.setDuration(outerInflection);
+ outerOpacityAnim = ObjectAnimator.ofFloat(this,
+ "outerOpacity", inflectionOpacity / 255.0f);
+ outerOpacityAnim.setAutoCancel(true);
+ outerOpacityAnim.setDuration(outerInflection);
+ outerOpacityAnim.setInterpolator(LINEAR_INTERPOLATOR);
// Chain the outer opacity exit animation.
final int outerDuration = opacityDuration - outerInflection;
- outerOpacity.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- final ObjectAnimator outerFadeOut = ObjectAnimator.ofFloat(Ripple.this,
- "outerOpacity", 0);
- outerFadeOut.setDuration(outerDuration);
+ if (outerDuration > 0) {
+ outerOpacityAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ final ObjectAnimator outerFadeOutAnim = ObjectAnimator.ofFloat(Ripple.this,
+ "outerOpacity", 0);
+ outerFadeOutAnim.setAutoCancel(true);
+ outerFadeOutAnim.setDuration(outerDuration);
+ outerFadeOutAnim.setInterpolator(LINEAR_INTERPOLATOR);
+ outerFadeOutAnim.addListener(mAnimationListener);
- mAnimOuterOpacity = outerFadeOut;
+ mAnimOuterOpacity = outerFadeOutAnim;
- outerFadeOut.start();
- }
+ outerFadeOutAnim.start();
+ }
- @Override
- public void onAnimationCancel(Animator animation) {
- animation.removeListener(this);
- }
- });
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ animation.removeListener(this);
+ }
+ });
+ } else {
+ outerOpacityAnim.addListener(mAnimationListener);
+ }
} else {
- outerOpacity = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
- outerOpacity.setDuration(opacityDuration);
+ outerOpacityAnim = ObjectAnimator.ofFloat(this, "outerOpacity", 0);
+ outerOpacityAnim.setAutoCancel(true);
+ outerOpacityAnim.setDuration(opacityDuration);
+ outerOpacityAnim.addListener(mAnimationListener);
}
- mAnimRadius = radius;
- mAnimOpacity = opacity;
- mAnimOuterOpacity = outerOpacity;
- mAnimX = opacity;
- mAnimY = opacity;
+ mAnimRadius = radiusAnim;
+ mAnimOpacity = opacityAnim;
+ mAnimOuterOpacity = outerOpacityAnim;
+ mAnimX = opacityAnim;
+ mAnimY = opacityAnim;
- radius.start();
- opacity.start();
- outerOpacity.start();
- x.start();
- y.start();
+ radiusAnim.start();
+ opacityAnim.start();
+ outerOpacityAnim.start();
+ xAnim.start();
+ yAnim.start();
}
/**
@@ -498,6 +553,11 @@
runningAnimations.clear();
}
+ private void removeSelf() {
+ // The owner will invalidate itself.
+ mOwner.removeRipple(this);
+ }
+
private void invalidateSelf() {
mOwner.invalidateSelf();
}
@@ -505,12 +565,7 @@
private final AnimatorListenerAdapter mAnimationListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mFinished = true;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mFinished = true;
+ removeSelf();
}
};
}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 1bd7cac..9d7a8b6 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -25,17 +25,14 @@
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PointF;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
-import android.util.SparseArray;
import com.android.internal.R;
-import com.android.org.bouncycastle.util.Arrays;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -71,10 +68,17 @@
public class RippleDrawable extends LayerDrawable {
private static final String LOG_TAG = RippleDrawable.class.getSimpleName();
private static final PorterDuffXfermode DST_IN = new PorterDuffXfermode(Mode.DST_IN);
- private static final PorterDuffXfermode DST_ATOP = new PorterDuffXfermode(Mode.DST_ATOP);
private static final PorterDuffXfermode SRC_ATOP = new PorterDuffXfermode(Mode.SRC_ATOP);
private static final PorterDuffXfermode SRC_OVER = new PorterDuffXfermode(Mode.SRC_OVER);
+ /**
+ * Constant for automatically determining the maximum ripple radius.
+ *
+ * @see #setMaxRadius(int)
+ * @hide
+ */
+ public static final int RADIUS_AUTO = -1;
+
/** The maximum number of ripples supported. */
private static final int MAX_RIPPLES = 10;
@@ -91,17 +95,8 @@
private final RippleState mState;
- /**
- * Lazily-created map of pending hotspot locations. These may be modified by
- * calls to {@link #setHotspot(float, float)}.
- */
- private SparseArray<PointF> mPendingHotspots;
-
- /**
- * Lazily-created map of active hotspot locations. These may be modified by
- * calls to {@link #setHotspot(float, float)}.
- */
- private SparseArray<Ripple> mActiveHotspots;
+ /** The current hotspot. May be actively animating or pending entry. */
+ private Ripple mHotspot;
/**
* Lazily-created array of actively animating ripples. Inactive ripples are
@@ -122,18 +117,46 @@
/** Whether bounds are being overridden. */
private boolean mOverrideBounds;
+ /** Whether the hotspot is currently active (e.g. focused or pressed). */
+ private boolean mActive;
+
RippleDrawable() {
+ this(null, null);
+ }
+
+ /**
+ * Creates a new ripple drawable with the specified content and mask
+ * drawables.
+ *
+ * @param content The content drawable, may be {@code null}
+ * @param mask The mask drawable, may be {@code null}
+ */
+ public RippleDrawable(Drawable content, Drawable mask) {
this(new RippleState(null, null, null), null, null);
+
+ if (content != null) {
+ addLayer(content, null, 0, 0, 0, 0, 0);
+ }
+
+ if (mask != null) {
+ addLayer(content, null, android.R.id.mask, 0, 0, 0, 0);
+ }
+
+ ensurePadding();
}
@Override
public void setAlpha(int alpha) {
-
+ super.setAlpha(alpha);
+
+ // TODO: Should we support this?
}
@Override
public void setColorFilter(ColorFilter cf) {
-
+ super.setColorFilter(cf);
+
+ // TODO: Should we support this?
}
@Override
@@ -146,20 +169,18 @@
protected boolean onStateChange(int[] stateSet) {
super.onStateChange(stateSet);
- final boolean pressed = Arrays.contains(stateSet, R.attr.state_pressed);
- if (!pressed) {
- removeHotspot(R.attr.state_pressed);
- } else {
- activateHotspot(R.attr.state_pressed);
+ boolean active = false;
+ final int N = stateSet.length;
+ for (int i = 0; i < N; i++) {
+ if (stateSet[i] == R.attr.state_focused
+ || stateSet[i] == R.attr.state_pressed) {
+ active = true;
+ break;
+ }
}
+ setActive(active);
- final boolean focused = Arrays.contains(stateSet, R.attr.state_focused);
- if (!focused) {
- removeHotspot(R.attr.state_focused);
- } else {
- activateHotspot(R.attr.state_focused);
- }
-
+ // Update the paint color. Only applicable when animated in software.
if (mRipplePaint != null && mState.mTint != null) {
final ColorStateList stateList = mState.mTint;
final int newColor = stateList.getColorForState(stateSet, 0);
@@ -174,12 +195,25 @@
return false;
}
+ private void setActive(boolean active) {
+ if (mActive != active) {
+ mActive = active;
+
+ if (active) {
+ activateHotspot();
+ } else {
+ removeHotspot();
+ }
+ }
+ }
+
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
if (!mOverrideBounds) {
mHotspotBounds.set(bounds);
+ onHotspotBoundsChanged();
}
invalidateSelf();
@@ -272,7 +306,7 @@
/**
* Initializes the constant state from the values in the typed array.
*/
- private void updateStateFromTypedArray(TypedArray a) {
+ private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
final RippleState state = mState;
// Extract the theme attributes, if any.
@@ -289,6 +323,12 @@
}
mState.mPinned = a.getBoolean(R.styleable.RippleDrawable_pinned, mState.mPinned);
+
+ // If we're not waiting on a theme, verify required attributes.
+ if (state.mTouchThemeAttrs == null && mState.mTint == null) {
+ throw new XmlPullParserException(a.getPositionDescription() +
+ ": <ripple> requires a valid tint attribute");
+ }
}
/**
@@ -314,8 +354,13 @@
final TypedArray a = t.resolveAttributes(state.mTouchThemeAttrs,
R.styleable.RippleDrawable);
- updateStateFromTypedArray(a);
- a.recycle();
+ try {
+ updateStateFromTypedArray(a);
+ } catch (XmlPullParserException e) {
+ throw new RuntimeException(e);
+ } finally {
+ a.recycle();
+ }
}
@Override
@@ -330,36 +375,15 @@
y = mHotspotBounds.exactCenterY();
}
- // TODO: We should only have a single pending/active hotspot.
- final int id = R.attr.state_pressed;
- final int[] stateSet = getState();
- if (!Arrays.contains(stateSet, id)) {
- // The hotspot is not active, so just modify the pending location.
- getOrCreatePendingHotspot(id).set(x, y);
- return;
- }
+ if (mHotspot == null) {
+ mHotspot = new Ripple(this, mHotspotBounds, x, y);
- if (mAnimatingRipplesCount >= MAX_RIPPLES) {
- // This should never happen unless the user is tapping like a maniac
- // or there is a bug that's preventing ripples from being removed.
- Log.d(LOG_TAG, "Max ripple count exceeded", new RuntimeException());
- return;
+ if (mActive) {
+ activateHotspot();
+ }
+ } else {
+ mHotspot.move(x, y);
}
-
- if (mActiveHotspots == null) {
- mActiveHotspots = new SparseArray<Ripple>();
- mAnimatingRipples = new Ripple[MAX_RIPPLES];
- }
-
- final Ripple ripple = mActiveHotspots.get(id);
- if (ripple != null) {
- // The hotspot is active, but we can't move it because it's probably
- // busy animating the center position.
- return;
- }
-
- // The hotspot needs to be made active.
- createActiveHotspot(id, x, y);
}
private boolean circleContains(Rect bounds, float x, float y) {
@@ -374,74 +398,44 @@
return pointRadius < boundsRadius;
}
- private PointF getOrCreatePendingHotspot(int id) {
- final PointF p;
- if (mPendingHotspots == null) {
- mPendingHotspots = new SparseArray<>(2);
- p = null;
- } else {
- p = mPendingHotspots.get(id);
- }
-
- if (p == null) {
- final PointF newPoint = new PointF();
- mPendingHotspots.put(id, newPoint);
- return newPoint;
- } else {
- return p;
- }
- }
-
- /**
- * Moves a hotspot from pending to active.
- */
- private void activateHotspot(int id) {
- final SparseArray<PointF> pendingHotspots = mPendingHotspots;
- if (pendingHotspots != null) {
- final int index = pendingHotspots.indexOfKey(id);
- if (index >= 0) {
- final PointF hotspot = pendingHotspots.valueAt(index);
- pendingHotspots.removeAt(index);
- createActiveHotspot(id, hotspot.x, hotspot.y);
- }
- }
- }
-
/**
* Creates an active hotspot at the specified location.
*/
- private void createActiveHotspot(int id, float x, float y) {
+ private void activateHotspot() {
+ if (mAnimatingRipplesCount >= MAX_RIPPLES) {
+ // This should never happen unless the user is tapping like a maniac
+ // or there is a bug that's preventing ripples from being removed.
+ Log.d(LOG_TAG, "Max ripple count exceeded", new RuntimeException());
+ return;
+ }
+
+ if (mHotspot == null) {
+ final float x = mHotspotBounds.exactCenterX();
+ final float y = mHotspotBounds.exactCenterY();
+ mHotspot = new Ripple(this, mHotspotBounds, x, y);
+ }
+
final int color = mState.mTint.getColorForState(getState(), Color.TRANSPARENT);
- final Ripple newRipple = new Ripple(this, mHotspotBounds, color);
- newRipple.enter(x, y);
+ mHotspot.setup(mState.mMaxRadius, color, mDensity);
+ mHotspot.enter();
if (mAnimatingRipples == null) {
mAnimatingRipples = new Ripple[MAX_RIPPLES];
}
- mAnimatingRipples[mAnimatingRipplesCount++] = newRipple;
-
- if (mActiveHotspots == null) {
- mActiveHotspots = new SparseArray<Ripple>();
- }
- mActiveHotspots.put(id, newRipple);
+ mAnimatingRipples[mAnimatingRipplesCount++] = mHotspot;
}
- private void removeHotspot(int id) {
- if (mActiveHotspots == null) {
- return;
- }
-
- final Ripple ripple = mActiveHotspots.get(id);
- if (ripple != null) {
- ripple.exit();
-
- mActiveHotspots.remove(id);
+ private void removeHotspot() {
+ if (mHotspot != null) {
+ mHotspot.exit();
+ mHotspot = null;
}
}
private void clearHotspots() {
- if (mActiveHotspots != null) {
- mActiveHotspots.clear();
+ if (mHotspot != null) {
+ mHotspot.cancel();
+ mHotspot = null;
}
final int count = mAnimatingRipplesCount;
@@ -459,73 +453,113 @@
public void setHotspotBounds(int left, int top, int right, int bottom) {
mOverrideBounds = true;
mHotspotBounds.set(left, top, right, bottom);
+
+ onHotspotBoundsChanged();
+ }
+
+ /**
+ * Notifies all the animating ripples that the hotspot bounds have changed.
+ */
+ private void onHotspotBoundsChanged() {
+ final int count = mAnimatingRipplesCount;
+ final Ripple[] ripples = mAnimatingRipples;
+ for (int i = 0; i < count; i++) {
+ ripples[i].onHotspotBoundsChanged();
+ }
}
@Override
public void draw(Canvas canvas) {
- final int N = mLayerState.mNum;
- final Rect bounds = getBounds();
- final ChildDrawable[] array = mLayerState.mChildren;
- final boolean maskOnly = mState.mMask != null && N == 1;
+ final Rect bounds = isProjected() ? getDirtyBounds() : getBounds();
- int restoreToCount = drawRippleLayer(canvas, maskOnly);
+ // Draw the content into a layer first.
+ final int contentLayer = drawContentLayer(canvas, bounds, SRC_OVER);
- if (restoreToCount >= 0) {
- // We have a ripple layer that contains ripples. If we also have an
- // explicit mask drawable, apply it now using DST_IN blending.
- if (mState.mMask != null) {
- canvas.saveLayer(bounds.left, bounds.top, bounds.right,
- bounds.bottom, getMaskingPaint(DST_IN));
- mState.mMask.draw(canvas);
- canvas.restoreToCount(restoreToCount);
- restoreToCount = -1;
- }
+ // Next, draw the ripples into a layer.
+ final int rippleLayer = drawRippleLayer(canvas, bounds, mState.mTintXfermode);
- // If there's more content, we need an extra masking layer to merge
- // the ripples over the content.
- if (!maskOnly) {
- final PorterDuffXfermode xfermode = mState.getTintXfermodeInverse();
- final int count = canvas.saveLayer(bounds.left, bounds.top,
- bounds.right, bounds.bottom, getMaskingPaint(xfermode));
- if (restoreToCount < 0) {
- restoreToCount = count;
- }
- }
+ // If we have ripples, draw the masking layer.
+ if (rippleLayer >= 0) {
+ drawMaskingLayer(canvas, bounds, DST_IN);
}
+ // Composite the layers if needed.
+ if (contentLayer >= 0) {
+ canvas.restoreToCount(contentLayer);
+ } else if (rippleLayer >= 0) {
+ canvas.restoreToCount(rippleLayer);
+ }
+ }
+
+ /**
+ * Removes a ripple from the animating ripple list.
+ *
+ * @param ripple the ripple to remove
+ */
+ void removeRipple(Ripple ripple) {
+ // Ripple ripple ripple ripple. Ripple ripple.
+ final Ripple[] ripples = mAnimatingRipples;
+ final int count = mAnimatingRipplesCount;
+ final int index = getRippleIndex(ripple);
+ if (index >= 0) {
+ for (int i = index + 1; i < count; i++) {
+ ripples[i - 1] = ripples[i];
+ }
+ ripples[count - 1] = null;
+ mAnimatingRipplesCount--;
+ invalidateSelf();
+ }
+ }
+
+ private int getRippleIndex(Ripple ripple) {
+ final Ripple[] ripples = mAnimatingRipples;
+ final int count = mAnimatingRipplesCount;
+ for (int i = 0; i < count; i++) {
+ if (ripples[i] == ripple) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private int drawContentLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
+ final int count = mLayerState.mNum;
+ if (count == 0 || (mState.mMask != null && count == 1)) {
+ return -1;
+ }
+
+ final Paint maskingPaint = getMaskingPaint(mode);
+ final int restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
+ bounds.right, bounds.bottom, maskingPaint);
+
// Draw everything except the mask.
- for (int i = 0; i < N; i++) {
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < count; i++) {
if (array[i].mId != R.id.mask) {
array[i].mDrawable.draw(canvas);
}
}
- // Composite the layers if needed.
- if (restoreToCount >= 0) {
- canvas.restoreToCount(restoreToCount);
- }
+ return restoreToCount;
}
- private int drawRippleLayer(Canvas canvas, boolean maskOnly) {
+ private int drawRippleLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
final int count = mAnimatingRipplesCount;
if (count == 0) {
return -1;
}
- final Ripple[] ripples = mAnimatingRipples;
- final boolean projected = isProjected();
- final Rect layerBounds = projected ? getDirtyBounds() : getBounds();
-
// Separate the ripple color and alpha channel. The alpha will be
// applied when we merge the ripples down to the canvas.
- final int rippleColor;
+ final int rippleARGB;
if (mState.mTint != null) {
- rippleColor = mState.mTint.getColorForState(getState(), Color.TRANSPARENT);
+ rippleARGB = mState.mTint.getColorForState(getState(), Color.TRANSPARENT);
} else {
- rippleColor = Color.TRANSPARENT;
+ rippleARGB = Color.TRANSPARENT;
}
- final int rippleAlpha = Color.alpha(rippleColor);
+ final int rippleAlpha = Color.alpha(rippleARGB);
+ final int rippleColor = rippleARGB | (0xFF << 24);
if (mRipplePaint == null) {
mRipplePaint = new Paint();
mRipplePaint.setAntiAlias(true);
@@ -536,36 +570,20 @@
boolean drewRipples = false;
int restoreToCount = -1;
int restoreTranslate = -1;
- int animatingCount = 0;
// Draw ripples and update the animating ripples array.
+ final Ripple[] ripples = mAnimatingRipples;
for (int i = 0; i < count; i++) {
final Ripple ripple = ripples[i];
- // Mark and skip finished ripples.
- if (ripple.isFinished()) {
- ripples[i] = null;
- continue;
- }
-
// If we're masking the ripple layer, make sure we have a layer
// first. This will merge SRC_OVER (directly) onto the canvas.
if (restoreToCount < 0) {
- // If we're projecting or we only have a mask, we want to treat the
- // underlying canvas as our content and merge the ripple layer down
- // using the tint xfermode.
- final PorterDuffXfermode xfermode;
- if (projected || maskOnly) {
- xfermode = mState.getTintXfermode();
- } else {
- xfermode = SRC_OVER;
- }
-
- final Paint layerPaint = getMaskingPaint(xfermode);
- layerPaint.setAlpha(rippleAlpha);
- restoreToCount = canvas.saveLayer(layerBounds.left, layerBounds.top,
- layerBounds.right, layerBounds.bottom, layerPaint);
- layerPaint.setAlpha(255);
+ final Paint maskingPaint = getMaskingPaint(mode);
+ maskingPaint.setAlpha(rippleAlpha);
+ restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
+ bounds.right, bounds.bottom, maskingPaint);
+ maskingPaint.setAlpha(255);
restoreTranslate = canvas.save();
// Translate the canvas to the current hotspot bounds.
@@ -573,13 +591,8 @@
}
drewRipples |= ripple.draw(canvas, ripplePaint);
-
- ripples[animatingCount] = ripples[i];
- animatingCount++;
}
- mAnimatingRipplesCount = animatingCount;
-
// Always restore the translation.
if (restoreTranslate >= 0) {
canvas.restoreToCount(restoreTranslate);
@@ -594,6 +607,20 @@
return restoreToCount;
}
+ private int drawMaskingLayer(Canvas canvas, Rect bounds, PorterDuffXfermode mode) {
+ final Drawable mask = mState.mMask;
+ if (mask == null) {
+ return -1;
+ }
+
+ final int restoreToCount = canvas.saveLayer(bounds.left, bounds.top,
+ bounds.right, bounds.bottom, getMaskingPaint(mode));
+
+ mask.draw(canvas);
+
+ return restoreToCount;
+ }
+
private Paint getMaskingPaint(PorterDuffXfermode xfermode) {
if (mMaskingPaint == null) {
mMaskingPaint = new Paint();
@@ -634,8 +661,8 @@
int[] mTouchThemeAttrs;
ColorStateList mTint = null;
PorterDuffXfermode mTintXfermode = SRC_ATOP;
- PorterDuffXfermode mTintXfermodeInverse = DST_ATOP;
Drawable mMask;
+ int mMaxRadius = RADIUS_AUTO;
boolean mPinned = false;
public RippleState(RippleState orig, RippleDrawable owner, Resources res) {
@@ -645,14 +672,12 @@
mTouchThemeAttrs = orig.mTouchThemeAttrs;
mTint = orig.mTint;
mTintXfermode = orig.mTintXfermode;
- mTintXfermodeInverse = orig.mTintXfermodeInverse;
+ mMaxRadius = orig.mMaxRadius;
mPinned = orig.mPinned;
}
}
public void setTintMode(Mode mode) {
- final Mode invertedMode = RippleState.invertPorterDuffMode(mode);
- mTintXfermodeInverse = new PorterDuffXfermode(invertedMode);
mTintXfermode = new PorterDuffXfermode(mode);
}
@@ -660,10 +685,6 @@
return mTintXfermode;
}
- public PorterDuffXfermode getTintXfermodeInverse() {
- return mTintXfermodeInverse;
- }
-
@Override
public boolean canApplyTheme() {
return mTouchThemeAttrs != null || super.canApplyTheme();
@@ -683,33 +704,36 @@
public Drawable newDrawable(Resources res, Theme theme) {
return new RippleDrawable(this, res, theme);
}
+ }
- /**
- * Inverts SRC and DST in PorterDuff blending modes.
- */
- private static Mode invertPorterDuffMode(Mode src) {
- switch (src) {
- case SRC_ATOP:
- return Mode.DST_ATOP;
- case SRC_IN:
- return Mode.DST_IN;
- case SRC_OUT:
- return Mode.DST_OUT;
- case SRC_OVER:
- return Mode.DST_OVER;
- case DST_ATOP:
- return Mode.SRC_ATOP;
- case DST_IN:
- return Mode.SRC_IN;
- case DST_OUT:
- return Mode.SRC_OUT;
- case DST_OVER:
- return Mode.SRC_OVER;
- default:
- // Everything else is agnostic to SRC versus DST.
- return src;
- }
+ /**
+ * Sets the maximum ripple radius in pixels. The default value of
+ * {@link #RADIUS_AUTO} defines the radius as the distance from the center
+ * of the drawable bounds (or hotspot bounds, if specified) to a corner.
+ *
+ * @param maxRadius the maximum ripple radius in pixels or
+ * {@link #RADIUS_AUTO} to automatically determine the maximum
+ * radius based on the bounds
+ * @see #getMaxRadius()
+ * @see #setHotspotBounds(int, int, int, int)
+ * @hide
+ */
+ public void setMaxRadius(int maxRadius) {
+ if (maxRadius != RADIUS_AUTO && maxRadius < 0) {
+ throw new IllegalArgumentException("maxRadius must be RADIUS_AUTO or >= 0");
}
+
+ mState.mMaxRadius = maxRadius;
+ }
+
+ /**
+ * @return the maximum ripple radius in pixels, or {@link #RADIUS_AUTO} if
+ * the radius is determined automatically
+ * @see #setMaxRadius(int)
+ * @hide
+ */
+ public int getMaxRadius() {
+ return mState.mMaxRadius;
}
private RippleDrawable(RippleState state, Resources res, Theme theme) {
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
index b80f7e9..eff3011 100644
--- a/libs/hwui/Animator.cpp
+++ b/libs/hwui/Animator.cpp
@@ -63,7 +63,6 @@
void BaseRenderNodeAnimator::setupStartValueIfNecessary(RenderNode* target, TreeInfo& info) {
if (mPlayState == NEEDS_START) {
setStartValue(getValue(target));
- mPlayState = PENDING;
}
}
@@ -154,7 +153,8 @@
}
void RenderPropertyAnimator::onAttached(RenderNode* target) {
- if (target->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
+ if (mPlayState == NEEDS_START
+ && target->isPropertyFieldDirty(mPropertyAccess->dirtyMask)) {
setStartValue((target->stagingProperties().*mPropertyAccess->getter)());
}
(target->mutateStagingProperties().*mPropertyAccess->setter)(finalValue());
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
index 7741617..a0c7c55 100644
--- a/libs/hwui/Animator.h
+++ b/libs/hwui/Animator.h
@@ -41,6 +41,7 @@
class BaseRenderNodeAnimator : public VirtualLightRefBase {
PREVENT_COPY_AND_ASSIGN(BaseRenderNodeAnimator);
public:
+ ANDROID_API void setStartValue(float value);
ANDROID_API void setInterpolator(Interpolator* interpolator);
ANDROID_API void setDuration(nsecs_t durationInMs);
ANDROID_API nsecs_t duration() { return mDuration; }
@@ -64,11 +65,9 @@
BaseRenderNodeAnimator(float finalValue);
virtual ~BaseRenderNodeAnimator();
- void setStartValue(float value);
virtual float getValue(RenderNode* target) const = 0;
virtual void setValue(RenderNode* target, float value) = 0;
-private:
void callOnFinishedListener(TreeInfo& info);
enum PlayState {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f4affa0..8ca303b 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -41,6 +41,7 @@
import java.util.HashMap;
import java.util.ArrayList;
+
/**
* AudioManager provides access to volume and ringer mode control.
* <p>
@@ -61,6 +62,7 @@
private final boolean mUseVolumeKeySounds;
private final Binder mToken = new Binder();
private static String TAG = "AudioManager";
+ AudioPortEventHandler mAudioPortEventHandler;
/**
* Broadcast intent, a hint for applications that audio is about to become
@@ -337,6 +339,12 @@
public static final int FLAG_BLUETOOTH_ABS_VOLUME = 1 << 6;
/**
+ * Adjusting the volume was prevented due to silent mode, display a hint in the UI.
+ * @hide
+ */
+ public static final int FLAG_SHOW_SILENT_HINT = 1 << 7;
+
+ /**
* Ringer mode that will be silent and will not vibrate. (This overrides the
* vibrate setting.)
*
@@ -438,6 +446,7 @@
com.android.internal.R.bool.config_useMasterVolume);
mUseVolumeKeySounds = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_useVolumeKeySounds);
+ mAudioPortEventHandler = new AudioPortEventHandler(this);
}
private static IAudioService getService()
@@ -635,7 +644,12 @@
if (mUseMasterVolume) {
service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
} else {
- service.adjustVolume(direction, flags, mContext.getOpPackageName());
+ if (USE_SESSIONS) {
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+ helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
+ } else {
+ service.adjustVolume(direction, flags, mContext.getOpPackageName());
+ }
}
} catch (RemoteException e) {
Log.e(TAG, "Dead object in adjustVolume", e);
@@ -665,8 +679,13 @@
if (mUseMasterVolume) {
service.adjustMasterVolume(direction, flags, mContext.getOpPackageName());
} else {
- service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags,
- mContext.getOpPackageName());
+ if (USE_SESSIONS) {
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext);
+ helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
+ } else {
+ service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags,
+ mContext.getOpPackageName());
+ }
}
} catch (RemoteException e) {
Log.e(TAG, "Dead object in adjustSuggestedStreamVolume", e);
@@ -3003,7 +3022,7 @@
* @hide
*/
public int listAudioPorts(ArrayList<AudioPort> ports) {
- return ERROR_INVALID_OPERATION;
+ return updateAudioPortCache(ports, null);
}
/**
@@ -3012,7 +3031,17 @@
* @hide
*/
public int listAudioDevicePorts(ArrayList<AudioPort> devices) {
- return ERROR_INVALID_OPERATION;
+ ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
+ int status = updateAudioPortCache(ports, null);
+ if (status == SUCCESS) {
+ devices.clear();
+ for (int i = 0; i < ports.size(); i++) {
+ if (ports.get(i) instanceof AudioDevicePort) {
+ devices.add(ports.get(i));
+ }
+ }
+ }
+ return status;
}
/**
@@ -3041,7 +3070,7 @@
public int createAudioPatch(AudioPatch[] patch,
AudioPortConfig[] sources,
AudioPortConfig[] sinks) {
- return ERROR_INVALID_OPERATION;
+ return AudioSystem.createAudioPatch(patch, sources, sinks);
}
/**
@@ -3056,7 +3085,7 @@
* @hide
*/
public int releaseAudioPatch(AudioPatch patch) {
- return ERROR_INVALID_OPERATION;
+ return AudioSystem.releaseAudioPatch(patch);
}
/**
@@ -3065,7 +3094,7 @@
* @hide
*/
public int listAudioPatches(ArrayList<AudioPatch> patches) {
- return ERROR_INVALID_OPERATION;
+ return updateAudioPortCache(null, patches);
}
/**
@@ -3074,7 +3103,14 @@
* @hide
*/
public int setAudioPortGain(AudioPort port, AudioGainConfig gain) {
- return ERROR_INVALID_OPERATION;
+ if (port == null || gain == null) {
+ return ERROR_BAD_VALUE;
+ }
+ AudioPortConfig activeConfig = port.activeConfig();
+ AudioPortConfig config = new AudioPortConfig(port, activeConfig.samplingRate(),
+ activeConfig.channelMask(), activeConfig.format(), gain);
+ config.mConfigMask = AudioPortConfig.GAIN;
+ return AudioSystem.setAudioPortConfig(config);
}
/**
@@ -3102,16 +3138,128 @@
}
/**
- * Register an audio port update listener.
+ * Register an audio port list update listener.
* @hide
*/
public void registerAudioPortUpdateListener(OnAudioPortUpdateListener l) {
+ mAudioPortEventHandler.registerListener(l);
}
/**
- * Unregister an audio port update listener.
+ * Unregister an audio port list update listener.
* @hide
*/
public void unregisterAudioPortUpdateListener(OnAudioPortUpdateListener l) {
+ mAudioPortEventHandler.unregisterListener(l);
+ }
+
+ //
+ // AudioPort implementation
+ //
+
+ static final int AUDIOPORT_GENERATION_INIT = 0;
+ Integer mAudioPortGeneration = new Integer(AUDIOPORT_GENERATION_INIT);
+ ArrayList<AudioPort> mAudioPortsCached = new ArrayList<AudioPort>();
+ ArrayList<AudioPatch> mAudioPatchesCached = new ArrayList<AudioPatch>();
+
+ int resetAudioPortGeneration() {
+ int generation;
+ synchronized (mAudioPortGeneration) {
+ generation = mAudioPortGeneration;
+ mAudioPortGeneration = AUDIOPORT_GENERATION_INIT;
+ }
+ return generation;
+ }
+
+ int updateAudioPortCache(ArrayList<AudioPort> ports, ArrayList<AudioPatch> patches) {
+ synchronized (mAudioPortGeneration) {
+
+ if (mAudioPortGeneration == AUDIOPORT_GENERATION_INIT) {
+ int[] patchGeneration = new int[1];
+ int[] portGeneration = new int[1];
+ int status;
+ ArrayList<AudioPort> newPorts = new ArrayList<AudioPort>();
+ ArrayList<AudioPatch> newPatches = new ArrayList<AudioPatch>();
+
+ do {
+ newPorts.clear();
+ status = AudioSystem.listAudioPorts(newPorts, portGeneration);
+ Log.i(TAG, "updateAudioPortCache AudioSystem.listAudioPorts() status: "+
+ status+" num ports: "+ newPorts.size() +" portGeneration: "+portGeneration[0]);
+ if (status != SUCCESS) {
+ return status;
+ }
+ newPatches.clear();
+ status = AudioSystem.listAudioPatches(newPatches, patchGeneration);
+ Log.i(TAG, "updateAudioPortCache AudioSystem.listAudioPatches() status: "+
+ status+" num patches: "+ newPatches.size() +" patchGeneration: "+patchGeneration[0]);
+ if (status != SUCCESS) {
+ return status;
+ }
+ } while (patchGeneration[0] != portGeneration[0]);
+
+ for (int i = 0; i < newPatches.size(); i++) {
+ for (int j = 0; j < newPatches.get(i).sources().length; j++) {
+ AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sources()[j], newPorts);
+ if (portCfg == null) {
+ return ERROR;
+ }
+ newPatches.get(i).sources()[j] = portCfg;
+ }
+ for (int j = 0; j < newPatches.get(i).sinks().length; j++) {
+ AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sinks()[j], newPorts);
+ if (portCfg == null) {
+ return ERROR;
+ }
+ newPatches.get(i).sinks()[j] = portCfg;
+ }
+ }
+
+ mAudioPortsCached = newPorts;
+ mAudioPatchesCached = newPatches;
+ mAudioPortGeneration = portGeneration[0];
+ }
+ if (ports != null) {
+ ports.clear();
+ ports.addAll(mAudioPortsCached);
+ }
+ if (patches != null) {
+ patches.clear();
+ patches.addAll(mAudioPatchesCached);
+ }
+ }
+ return SUCCESS;
+ }
+
+ AudioPortConfig updatePortConfig(AudioPortConfig portCfg, ArrayList<AudioPort> ports) {
+ AudioPort port = portCfg.port();
+ int k;
+ for (k = 0; k < ports.size(); k++) {
+ // compare handles because the port returned by JNI is not of the correct
+ // subclass
+ if (ports.get(k).handle().equals(port.handle())) {
+ Log.i(TAG, "updatePortConfig match found for port handle: "+
+ port.handle().id()+" port: "+ k);
+ port = ports.get(k);
+ break;
+ }
+ }
+ if (k == ports.size()) {
+ // this hould never happen
+ Log.e(TAG, "updatePortConfig port not found for handle: "+port.handle().id());
+ return null;
+ }
+ AudioGainConfig gainCfg = portCfg.gain();
+ if (gainCfg != null) {
+ AudioGain gain = port.gain(gainCfg.index());
+ gainCfg = gain.buildConfig(gainCfg.mode(),
+ gainCfg.channelMask(),
+ gainCfg.values(),
+ gainCfg.rampDurationMs());
+ }
+ return port.buildConfig(portCfg.samplingRate(),
+ portCfg.channelMask(),
+ portCfg.format(),
+ gainCfg);
}
}
diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java
index 9aeddef..fbd5022 100644
--- a/media/java/android/media/AudioPort.java
+++ b/media/java/android/media/AudioPort.java
@@ -133,6 +133,9 @@
* Get the gain descriptor at a given index
*/
AudioGain gain(int index) {
+ if (index < mGains.length) {
+ return null;
+ }
return mGains[index];
}
diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java
new file mode 100644
index 0000000..782ecd8
--- /dev/null
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.lang.ref.WeakReference;
+
+/**
+ * The AudioPortEventHandler handles AudioManager.OnAudioPortUpdateListener callbacks
+ * posted from JNI
+ * @hide
+ */
+
+class AudioPortEventHandler {
+ private final Handler mHandler;
+ private ArrayList<AudioManager.OnAudioPortUpdateListener> mListeners;
+ private AudioManager mAudioManager;
+
+ private static String TAG = "AudioPortEventHandler";
+
+ private static final int AUDIOPORT_EVENT_PORT_LIST_UPDATED = 1;
+ private static final int AUDIOPORT_EVENT_PATCH_LIST_UPDATED = 2;
+ private static final int AUDIOPORT_EVENT_SERVICE_DIED = 3;
+ private static final int AUDIOPORT_EVENT_NEW_LISTENER = 4;
+
+ AudioPortEventHandler(AudioManager audioManager) {
+ mAudioManager = audioManager;
+ mListeners = new ArrayList<AudioManager.OnAudioPortUpdateListener>();
+
+ // find the looper for our new event handler
+ Looper looper = Looper.myLooper();
+ if (looper == null) {
+ looper = Looper.getMainLooper();
+ }
+
+ if (looper != null) {
+ mHandler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message msg) {
+ Log.i(TAG, "handleMessage: "+msg.what);
+ ArrayList<AudioManager.OnAudioPortUpdateListener> listeners;
+ synchronized (this) {
+ if (msg.what == AUDIOPORT_EVENT_NEW_LISTENER) {
+ listeners = new ArrayList<AudioManager.OnAudioPortUpdateListener>();
+ if (mListeners.contains(msg.obj)) {
+ listeners.add((AudioManager.OnAudioPortUpdateListener)msg.obj);
+ }
+ } else {
+ listeners = mListeners;
+ }
+ }
+ if (listeners.isEmpty()) {
+ return;
+ }
+ // reset audio port cache if the event corresponds to a change coming
+ // from audio policy service or if mediaserver process died.
+ if (msg.what == AUDIOPORT_EVENT_PORT_LIST_UPDATED ||
+ msg.what == AUDIOPORT_EVENT_PATCH_LIST_UPDATED ||
+ msg.what == AUDIOPORT_EVENT_SERVICE_DIED) {
+ mAudioManager.resetAudioPortGeneration();
+ }
+ ArrayList<AudioPort> ports = new ArrayList<AudioPort>();
+ ArrayList<AudioPatch> patches = new ArrayList<AudioPatch>();
+ if (msg.what != AUDIOPORT_EVENT_SERVICE_DIED) {
+ int status = mAudioManager.updateAudioPortCache(ports, patches);
+ if (status != AudioManager.SUCCESS) {
+ return;
+ }
+ }
+
+ switch (msg.what) {
+ case AUDIOPORT_EVENT_NEW_LISTENER:
+ case AUDIOPORT_EVENT_PORT_LIST_UPDATED:
+ AudioPort[] portList = ports.toArray(new AudioPort[0]);
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.get(i).OnAudioPortListUpdate(portList);
+ }
+ if (msg.what == AUDIOPORT_EVENT_PORT_LIST_UPDATED) {
+ break;
+ }
+ // FALL THROUGH
+
+ case AUDIOPORT_EVENT_PATCH_LIST_UPDATED:
+ AudioPatch[] patchList = patches.toArray(new AudioPatch[0]);
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.get(i).OnAudioPatchListUpdate(patchList);
+ }
+ break;
+
+ case AUDIOPORT_EVENT_SERVICE_DIED:
+ for (int i = 0; i < listeners.size(); i++) {
+ listeners.get(i).OnServiceDied();
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ };
+ } else {
+ mHandler = null;
+ }
+
+ native_setup(new WeakReference<AudioPortEventHandler>(this));
+ }
+ private native void native_setup(Object module_this);
+
+ @Override
+ protected void finalize() {
+ native_finalize();
+ }
+ private native void native_finalize();
+
+ void registerListener(AudioManager.OnAudioPortUpdateListener l) {
+ synchronized (this) {
+ mListeners.add(l);
+ }
+ if (mHandler != null) {
+ Message m = mHandler.obtainMessage(AUDIOPORT_EVENT_NEW_LISTENER, 0, 0, l);
+ mHandler.sendMessage(m);
+ }
+ }
+
+ void unregisterListener(AudioManager.OnAudioPortUpdateListener l) {
+ synchronized (this) {
+ mListeners.remove(l);
+ }
+ }
+
+ Handler handler() {
+ return mHandler;
+ }
+
+ @SuppressWarnings("unused")
+ private static void postEventFromNative(Object module_ref,
+ int what, int arg1, int arg2, Object obj) {
+ AudioPortEventHandler eventHandler =
+ (AudioPortEventHandler)((WeakReference)module_ref).get();
+ if (eventHandler == null) {
+ return;
+ }
+
+ if (eventHandler != null) {
+ Handler handler = eventHandler.handler();
+ if (handler != null) {
+ Message m = handler.obtainMessage(what, arg1, arg2, obj);
+ handler.sendMessage(m);
+ }
+ }
+ }
+
+}
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 5b620fd..fd346d5 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -115,6 +115,9 @@
/** Allow volume changes to set ringer mode to silent? */
private static final boolean VOLUME_SETS_RINGER_MODE_SILENT = false;
+ /** In silent mode, are volume adjustments (raises) prevented? */
+ private static final boolean PREVENT_VOLUME_ADJUSTMENT_IF_SILENT = true;
+
/** How long to delay before persisting a change in volume/ringer mode. */
private static final int PERSIST_DELAY = 500;
@@ -126,6 +129,11 @@
*/
public static final int PLAY_SOUND_DELAY = 300;
+ /**
+ * Only used in the result from {@link #checkForRingerModeChange(int, int, int)}
+ */
+ private static final int FLAG_ADJUST_VOLUME = 1;
+
private final Context mContext;
private final ContentResolver mContentResolver;
private final AppOpsManager mAppOps;
@@ -941,7 +949,12 @@
}
// Check if the ringer mode changes with this volume adjustment. If
// it does, it will handle adjusting the volume, so we won't below
- adjustVolume = checkForRingerModeChange(aliasIndex, direction, step);
+ final int result = checkForRingerModeChange(aliasIndex, direction, step);
+ adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;
+ // If suppressing a volume adjustment in silent mode, display the UI hint
+ if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
+ flags |= AudioManager.FLAG_SHOW_SILENT_HINT;
+ }
}
int oldIndex = mStreamStates[streamType].getIndex(device);
@@ -2538,8 +2551,8 @@
* adjusting volume. If so, this will set the proper ringer mode and volume
* indices on the stream states.
*/
- private boolean checkForRingerModeChange(int oldIndex, int direction, int step) {
- boolean adjustVolumeIndex = true;
+ private int checkForRingerModeChange(int oldIndex, int direction, int step) {
+ int result = FLAG_ADJUST_VOLUME;
int ringerMode = getRingerMode();
switch (ringerMode) {
@@ -2578,17 +2591,21 @@
} else if (direction == AudioManager.ADJUST_RAISE) {
ringerMode = RINGER_MODE_NORMAL;
}
- adjustVolumeIndex = false;
+ result &= ~FLAG_ADJUST_VOLUME;
break;
case RINGER_MODE_SILENT:
if (direction == AudioManager.ADJUST_RAISE) {
- if (mHasVibrator) {
- ringerMode = RINGER_MODE_VIBRATE;
+ if (PREVENT_VOLUME_ADJUSTMENT_IF_SILENT) {
+ result |= AudioManager.FLAG_SHOW_SILENT_HINT;
} else {
- ringerMode = RINGER_MODE_NORMAL;
+ if (mHasVibrator) {
+ ringerMode = RINGER_MODE_VIBRATE;
+ } else {
+ ringerMode = RINGER_MODE_NORMAL;
+ }
}
}
- adjustVolumeIndex = false;
+ result &= ~FLAG_ADJUST_VOLUME;
break;
default:
Log.e(TAG, "checkForRingerModeChange() wrong ringer mode: "+ringerMode);
@@ -2599,7 +2616,7 @@
mPrevVolDirection = direction;
- return adjustVolumeIndex;
+ return result;
}
@Override
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 0c45443..af7a3e1 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -16,6 +16,7 @@
package android.media;
+import java.util.ArrayList;
/* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET
* TO UPDATE THE CORRESPONDING NATIVE GLUE AND AudioManager.java.
@@ -458,4 +459,12 @@
public static native int setLowRamDevice(boolean isLowRamDevice);
public static native int checkAudioFlinger();
+
+ public static native int listAudioPorts(ArrayList<AudioPort> ports, int[] generation);
+ public static native int createAudioPatch(AudioPatch[] patch,
+ AudioPortConfig[] sources, AudioPortConfig[] sinks);
+ public static native int releaseAudioPatch(AudioPatch patch);
+ public static native int listAudioPatches(ArrayList<AudioPatch> patches, int[] generation);
+ public static native int setAudioPortConfig(AudioPortConfig config);
}
+
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index c4233c3..1cfc5bc 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -47,4 +47,8 @@
void setMetadata(in MediaMetadata metadata);
void setPlaybackState(in PlaybackState state);
void setRatingType(int type);
+
+ // These commands relate to volume handling
+ void configureVolumeHandling(int type, int arg1, int arg2);
+ void setCurrentVolume(int currentVolume);
}
\ No newline at end of file
diff --git a/media/java/android/media/session/ISessionCallback.aidl b/media/java/android/media/session/ISessionCallback.aidl
index 103c3f1..0316d1fa 100644
--- a/media/java/android/media/session/ISessionCallback.aidl
+++ b/media/java/android/media/session/ISessionCallback.aidl
@@ -45,4 +45,8 @@
void onRewind();
void onSeekTo(long pos);
void onRate(in Rating rating);
+
+ // These callbacks are for volume handling
+ void onAdjustVolumeBy(int delta);
+ void onSetVolumeTo(int value);
}
\ No newline at end of file
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 38b92932..6d9888f 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -29,4 +29,5 @@
ISession createSession(String packageName, in ISessionCallback cb, String tag, int userId);
List<IBinder> getSessions(in ComponentName compName, int userId);
void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock);
+ void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags);
}
\ No newline at end of file
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 90ccf68..7972639 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -124,9 +124,21 @@
*/
public static final int DISCONNECT_REASON_SESSION_DESTROYED = 5;
- private static final String KEY_COMMAND = "command";
- private static final String KEY_EXTRAS = "extras";
- private static final String KEY_CALLBACK = "callback";
+ /**
+ * The session uses local playback. Used for configuring volume handling
+ * with the system.
+ *
+ * @hide
+ */
+ public static final int VOLUME_TYPE_LOCAL = 1;
+
+ /**
+ * The session uses remote playback. Used for configuring volume handling
+ * with the system.
+ *
+ * @hide
+ */
+ public static final int VOLUME_TYPE_REMOTE = 2;
private final Object mLock = new Object();
@@ -143,6 +155,7 @@
= new ArrayMap<String, RouteInterface.EventListener>();
private Route mRoute;
+ private RemoteVolumeProvider mVolumeProvider;
private boolean mActive = false;;
@@ -242,7 +255,11 @@
* @param stream The {@link AudioManager} stream this session is playing on.
*/
public void setPlaybackToLocal(int stream) {
- // TODO
+ try {
+ mBinder.configureVolumeHandling(VOLUME_TYPE_LOCAL, stream, 0);
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
+ }
}
/**
@@ -259,7 +276,14 @@
if (volumeProvider == null) {
throw new IllegalArgumentException("volumeProvider may not be null!");
}
- // TODO
+ mVolumeProvider = volumeProvider;
+
+ try {
+ mBinder.configureVolumeHandling(VOLUME_TYPE_REMOTE, volumeProvider.getVolumeControl(),
+ volumeProvider.getMaxVolume());
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
+ }
}
/**
@@ -942,6 +966,26 @@
}
+ /*
+ * (non-Javadoc)
+ * @see android.media.session.ISessionCallback#onAdjustVolumeBy(int)
+ */
+ @Override
+ public void onAdjustVolumeBy(int delta) throws RemoteException {
+ // TODO(epastern): Auto-generated method stub
+
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see android.media.session.ISessionCallback#onSetVolumeTo(int)
+ */
+ @Override
+ public void onSetVolumeTo(int value) throws RemoteException {
+ // TODO(epastern): Auto-generated method stub
+
+ }
+
}
private class CallbackMessageHandler extends Handler {
diff --git a/media/java/android/media/session/MediaSessionLegacyHelper.java b/media/java/android/media/session/MediaSessionLegacyHelper.java
index c303e77..099f601 100644
--- a/media/java/android/media/session/MediaSessionLegacyHelper.java
+++ b/media/java/android/media/session/MediaSessionLegacyHelper.java
@@ -76,6 +76,13 @@
}
}
+ public void sendAdjustVolumeBy(int suggestedStream, int delta, int flags) {
+ mSessionManager.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
+ if (DEBUG) {
+ Log.d(TAG, "dispatched volume adjustment");
+ }
+ }
+
public void addRccListener(PendingIntent pi,
MediaSession.TransportControlsCallback listener) {
if (pi == null) {
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 8d5e338..9e8b0d3 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -166,4 +166,22 @@
Log.e(TAG, "Failed to send key event.", e);
}
}
+
+ /**
+ * Dispatch an adjust volume request to the system. It will be routed to the
+ * most relevant stream/session.
+ *
+ * @param suggestedStream The stream to fall back to if there isn't a
+ * relevant stream
+ * @param delta The amount to adjust the volume by.
+ * @param flags Any flags to include with the volume change.
+ * @hide
+ */
+ public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags) {
+ try {
+ mService.dispatchAdjustVolumeBy(suggestedStream, delta, flags);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to send adjust volume.", e);
+ }
+ }
}
diff --git a/media/lib/signer/Android.mk b/media/lib/signer/Android.mk
index bca643a..b0d3177 100644
--- a/media/lib/signer/Android.mk
+++ b/media/lib/signer/Android.mk
@@ -23,7 +23,8 @@
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := \
- $(call all-java-files-under, java)
+ $(call all-java-files-under, java) \
+ $(call all-aidl-files-under, java)
include $(BUILD_JAVA_LIBRARY)
diff --git a/media/lib/signer/com.android.mediadrm.signer.xml b/media/lib/signer/com.android.mediadrm.signer.xml
index b5b1f09..fd3a115 100644
--- a/media/lib/signer/com.android.mediadrm.signer.xml
+++ b/media/lib/signer/com.android.mediadrm.signer.xml
@@ -15,6 +15,6 @@
-->
<permissions>
- <library name="com.android.media.drm.signer"
- file="/system/framework/com.android.media.drm.signer.jar" />
+ <library name="com.android.mediadrm.signer"
+ file="/system/framework/com.android.mediadrm.signer.jar" />
</permissions>
diff --git a/packages/SystemUI/res/drawable/heads_up_scrim.xml b/packages/SystemUI/res/drawable/heads_up_scrim.xml
new file mode 100644
index 0000000..59000fc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/heads_up_scrim.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <gradient
+ android:type="linear"
+ android:angle="-90"
+ android:startColor="#55000000"
+ android:endColor="#00000000" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_airplane_off.xml b/packages/SystemUI/res/drawable/ic_qs_airplane_off.xml
index 9f0ec67..c68238f 100644
--- a/packages/SystemUI/res/drawable/ic_qs_airplane_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_airplane_off.xml
@@ -19,17 +19,10 @@
android:height="64dp"/>
<viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
<path
- android:fill="#00000000"
- android:stroke="#CCCCCC"
- android:strokeWidth="1.0"
- android:pathData="M10.2,9.0"/>
- <path
- android:fill="#00000000"
- android:stroke="#CCCCCC"
- android:strokeWidth="1.0"
- android:pathData="M21.0,16.0l0.0,-2.0l-8.0,-5.0L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5L10.0,9.0l-8.0,5.0l0.0,2.0l8.0,-2.5L10.0,19.0l-2.0,1.5L8.0,22.0l3.5,-1.0l3.5,1.0l0.0,-1.5L13.0,19.0l0.0,-5.5L21.0,16.0z"/>
+ android:fill="#4DFFFFFF"
+ android:pathData="M26.0,18.0L26.0,7.0c0.0,-1.7 -1.3,-3.0 -3.0,-3.0c-1.7,0.0 -3.0,1.3 -3.0,3.0l0.0,7.4L35.7,30.0l6.3,2.0l0.0,-4.0L26.0,18.0zM6.0,10.5l10.0,10.0L4.0,28.0l0.0,4.0l16.0,-5.0l0.0,11.0l-4.0,3.0l0.0,3.0l7.0,-2.0l7.0,2.0l0.0,-3.0l-4.0,-3.0l0.0,-7.5L37.5,42.0l2.5,-2.5L8.5,8.0L6.0,10.5z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_airplane_on.xml b/packages/SystemUI/res/drawable/ic_qs_airplane_on.xml
index 95c20bb..c1e3c7e 100644
--- a/packages/SystemUI/res/drawable/ic_qs_airplane_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_airplane_on.xml
@@ -19,13 +19,13 @@
android:height="64dp"/>
<viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
<path
android:fill="#FFFFFFFF"
- android:pathData="M10.2,9.0"/>
+ android:pathData="M20.4,18.0"/>
<path
android:fill="#FFFFFFFF"
- android:pathData="M21.0,16.0l0.0,-2.0l-8.0,-5.0L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5L10.0,9.0l-8.0,5.0l0.0,2.0l8.0,-2.5L10.0,19.0l-2.0,1.5L8.0,22.0l3.5,-1.0l3.5,1.0l0.0,-1.5L13.0,19.0l0.0,-5.5L21.0,16.0z"/>
+ android:pathData="M42.0,32.0l0.0,-4.0L26.0,18.0L26.0,7.0c0.0,-1.7 -1.3,-3.0 -3.0,-3.0c-1.7,0.0 -3.0,1.3 -3.0,3.0l0.0,11.0L4.0,28.0l0.0,4.0l16.0,-5.0l0.0,11.0l-4.0,3.0l0.0,3.0l7.0,-2.0l7.0,2.0l0.0,-3.0l-4.0,-3.0L26.0,27.0L42.0,32.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml
index 61a7777..3957d02 100644
--- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connected.xml
@@ -19,10 +19,10 @@
android:height="64dp"/>
<viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
<path
android:fill="#FFFFFFFF"
- android:pathData="M17.7,7.7L12.0,2.0l-1.0,0.0l0.0,7.6L6.4,5.0L5.0,6.4l5.6,5.6L5.0,17.6L6.4,19.0l4.6,-4.6L11.0,22.0l1.0,0.0l5.7,-5.7L13.4,12.0L17.7,7.7zM13.0,5.8l1.9,1.9L13.0,9.6L13.0,5.8zM14.9,16.3L13.0,18.2l0.0,-3.8L14.9,16.3z"/>
+ android:pathData="M14.0,24.0l-4.0,-4.0l-4.0,4.0l4.0,4.0L14.0,24.0zM35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6zM38.0,20.0l-4.0,4.0l4.0,4.0l4.0,-4.0L38.0,20.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.xml
new file mode 100644
index 0000000..e4038f9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_connecting.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="64dp"
+ android:height="64dp"/>
+
+ <viewport
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
+
+ <path
+ android:fill="#FFFFFFFF"
+ android:pathData="M28.5,24.0l4.6,4.6c0.6,-1.4 0.9,-3.0 0.9,-4.7c0.0,-1.6 -0.3,-3.2 -0.9,-4.6L28.5,24.0zM39.1,13.4L36.5,16.0c1.3,2.4 2.0,5.1 2.0,8.0s-0.7,5.6 -2.0,8.0l2.4,2.4c1.9,-3.1 3.1,-6.7 3.1,-10.6C42.0,20.0 40.9,16.5 39.1,13.4zM31.4,15.4L20.0,4.0l-2.0,0.0l0.0,15.2L8.8,10.0L6.0,12.8L17.2,24.0L6.0,35.2L8.8,38.0l9.2,-9.2L18.0,44.0l2.0,0.0l11.4,-11.4L22.8,24.0L31.4,15.4zM22.0,11.7l3.8,3.8L22.0,19.2L22.0,11.7zM25.8,32.6L22.0,36.3l0.0,-7.5L25.8,32.6z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml
index 7ac1cb9..00c5af8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_off.xml
@@ -19,12 +19,10 @@
android:height="64dp"/>
<viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
<path
- android:fill="#00000000"
- android:stroke="#CCCCCC"
- android:strokeWidth="1.0"
- android:pathData="M17.7,7.7L12.0,2.0l-1.0,0.0l0.0,7.6L6.4,5.0L5.0,6.4l5.6,5.6L5.0,17.6L6.4,19.0l4.6,-4.6L11.0,22.0l1.0,0.0l5.7,-5.7L13.4,12.0L17.7,7.7zM13.0,5.8l1.9,1.9L13.0,9.6L13.0,5.8zM14.9,16.3L13.0,18.2l0.0,-3.8L14.9,16.3z"/>
+ android:fill="#4DFFFFFF"
+ android:pathData="M26.0,11.8l3.8,3.8l-3.2,3.2l2.8,2.8l6.0,-6.0L24.0,4.2l-2.0,0.0l0.0,10.1l4.0,4.0L26.0,11.8zM10.8,8.2L8.0,11.0l13.2,13.2L10.0,35.3l2.8,2.8L22.0,29.0l0.0,15.2l2.0,0.0l8.6,-8.6l4.6,4.6l2.8,-2.8L10.8,8.2zM26.0,36.5L26.0,29.0l3.8,3.8L26.0,36.5z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml
index 61a7777..2b14f33 100644
--- a/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_on.xml
@@ -19,10 +19,10 @@
android:height="64dp"/>
<viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
<path
android:fill="#FFFFFFFF"
- android:pathData="M17.7,7.7L12.0,2.0l-1.0,0.0l0.0,7.6L6.4,5.0L5.0,6.4l5.6,5.6L5.0,17.6L6.4,19.0l4.6,-4.6L11.0,22.0l1.0,0.0l5.7,-5.7L13.4,12.0L17.7,7.7zM13.0,5.8l1.9,1.9L13.0,9.6L13.0,5.8zM14.9,16.3L13.0,18.2l0.0,-3.8L14.9,16.3z"/>
+ android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_off.xml b/packages/SystemUI/res/drawable/ic_qs_cast_off.xml
index 130c639..2a9541e 100644
--- a/packages/SystemUI/res/drawable/ic_qs_cast_off.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_cast_off.xml
@@ -19,12 +19,10 @@
android:height="64dp"/>
<viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
<path
- android:fill="#00000000"
- android:stroke="#CCCCCC"
- android:strokeWidth="1.0"
- android:pathData="M21.0,3.0L3.0,3.0C1.9,3.0 1.0,3.9 1.0,5.0l0.0,3.0l2.0,0.0L3.0,5.0l18.0,0.0l0.0,14.0l-7.0,0.0l0.0,2.0l7.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L23.0,5.0C23.0,3.9 22.1,3.0 21.0,3.0zM1.0,18.0l0.0,3.0l3.0,0.0C4.0,19.3 2.7,18.0 1.0,18.0zM1.0,14.0l0.0,2.0c2.8,0.0 5.0,2.2 5.0,5.0l2.0,0.0C8.0,17.1 4.9,14.0 1.0,14.0zM1.0,10.0l0.0,2.0c5.0,0.0 9.0,4.0 9.0,9.0l2.0,0.0C12.0,14.9 7.1,10.0 1.0,10.0z"/>
+ android:fill="#4DFFFFFF"
+ android:pathData="M42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0zM2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
index 6c82b1c..8dacdc9 100644
--- a/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_cast_on.xml
@@ -19,10 +19,10 @@
android:height="64dp"/>
<viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
<path
android:fill="#FFFFFFFF"
- android:pathData="M21.0,3.0L3.0,3.0C1.9,3.0 1.0,3.9 1.0,5.0l0.0,3.0l2.0,0.0L3.0,5.0l18.0,0.0l0.0,14.0l-7.0,0.0l0.0,2.0l7.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L23.0,5.0C23.0,3.9 22.1,3.0 21.0,3.0zM1.0,18.0l0.0,3.0l3.0,0.0C4.0,19.3 2.7,18.0 1.0,18.0zM1.0,14.0l0.0,2.0c2.8,0.0 5.0,2.2 5.0,5.0l2.0,0.0C8.0,17.1 4.9,14.0 1.0,14.0zM1.0,10.0l0.0,2.0c5.0,0.0 9.0,4.0 9.0,9.0l2.0,0.0C12.0,14.9 7.1,10.0 1.0,10.0z"/>
+ android:pathData="M42.0,6.0L6.0,6.0c-2.2,0.0 -4.0,1.8 -4.0,4.0l0.0,6.0l4.0,0.0l0.0,-6.0l36.0,0.0l0.0,28.0L28.0,38.0l0.0,4.0l14.0,0.0c2.2,0.0 4.0,-1.8 4.0,-4.0L46.0,10.0C46.0,7.8 44.2,6.0 42.0,6.0zM2.0,36.0l0.0,6.0l6.0,0.0C8.0,38.7 5.3,36.0 2.0,36.0zM2.0,28.0l0.0,4.0c5.5,0.0 10.0,4.5 10.0,10.0l4.0,0.0C16.0,34.3 9.7,28.0 2.0,28.0zM2.0,20.0l0.0,4.0c9.9,0.0 18.0,8.1 18.0,18.0l4.0,0.0C24.0,29.8 14.1,20.0 2.0,20.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_zen_off.xml b/packages/SystemUI/res/drawable/ic_qs_zen_off.xml
deleted file mode 100644
index 88a2c3a..0000000
--- a/packages/SystemUI/res/drawable/ic_qs_zen_off.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android" >
- <size
- android:width="64dp"
- android:height="64dp"/>
-
- <viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
-
- <path
- android:fill="#4DFFFFFF"
- android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_zen_on.xml b/packages/SystemUI/res/drawable/ic_qs_zen_on.xml
index 0e933cf..4887b32 100644
--- a/packages/SystemUI/res/drawable/ic_qs_zen_on.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_zen_on.xml
@@ -19,10 +19,10 @@
android:height="64dp"/>
<viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
<path
android:fill="#FFFFFFFF"
- android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
+ android:pathData="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml
index 51be1a8..477c36b 100644
--- a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml
+++ b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml
@@ -19,10 +19,10 @@
android:height="32dp"/>
<viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
<path
android:fill="#4DFFFFFF"
- android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
+ android:pathData="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_on.xml b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml
index c8c217d..0a43a7b 100644
--- a/packages/SystemUI/res/drawable/ic_vol_zen_on.xml
+++ b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml
@@ -19,10 +19,10 @@
android:height="32dp"/>
<viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
<path
android:fill="#FFFFFFFF"
- android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
+ android:pathData="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.0z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml
index 8b104d17..5992470 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_zen.xml
@@ -15,14 +15,14 @@
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" >
<size
- android:width="19dp"
- android:height="19dp"/>
+ android:width="18dp"
+ android:height="18dp"/>
<viewport
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"/>
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
<path
android:fill="#FFFFFFFF"
- android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z"/>
+ android:pathData="M4.0,24.0c0.0,11.0 9.0,20.0 20.0,20.0s20.0,-9.0 20.0,-20.0S35.0,4.0 24.0,4.0S4.0,13.0 4.0,24.0zM36.6,33.8L14.2,11.4C16.9,9.3 20.3,8.0 24.0,8.0c8.8,0.0 16.0,7.2 16.0,16.0C40.0,27.7 38.7,31.1 36.6,33.8zM8.0,24.0c0.0,-3.7 1.3,-7.1 3.4,-9.8L33.8,36.6C31.1,38.7 27.7,40.0 24.0,40.0C15.2,40.0 8.0,32.8 8.0,24.0z"/>
</vector>
diff --git a/packages/SystemUI/res/layout/heads_up.xml b/packages/SystemUI/res/layout/heads_up.xml
index 236fdc3..0e2b6d6 100644
--- a/packages/SystemUI/res/layout/heads_up.xml
+++ b/packages/SystemUI/res/layout/heads_up.xml
@@ -17,13 +17,14 @@
<com.android.systemui.statusbar.policy.HeadsUpNotificationView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
- android:layout_width="match_parent">
+ android:layout_width="match_parent"
+ android:background="@drawable/heads_up_scrim">
<FrameLayout
android:layout_height="wrap_content"
- android:layout_marginStart="@dimen/notification_side_padding"
- android:layout_marginEnd="@dimen/notification_side_padding"
- android:elevation="16dp"
+ android:paddingStart="@dimen/notification_side_padding"
+ android:paddingEnd="@dimen/notification_side_padding"
+ android:elevation="8dp"
android:id="@+id/content_holder"
style="@style/NotificationsQuickSettings" />
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
new file mode 100644
index 0000000..5afa967
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/qs_panel_background"
+ android:translationZ="@dimen/volume_panel_z"
+ android:layout_margin="@dimen/volume_panel_z">
+
+ <include layout="@layout/volume_panel" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_panel_item.xml b/packages/SystemUI/res/layout/volume_panel_item.xml
index 98cb8f4..4a2a0c0 100644
--- a/packages/SystemUI/res/layout/volume_panel_item.xml
+++ b/packages/SystemUI/res/layout/volume_panel_item.xml
@@ -15,28 +15,34 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="80dip"
- android:orientation="horizontal"
- android:layout_marginTop="8dip"
- android:layout_marginBottom="8dip"
- android:gravity="start|center_vertical">
+ android:layout_width="match_parent"
+ android:layout_height="80dip"
+ android:orientation="horizontal"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
+ android:gravity="start|center_vertical">
<ImageView
- android:id="@+id/stream_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingLeft="16dip"
- android:background="?android:attr/selectableItemBackground"
- android:contentDescription="@null" />
-
- <SeekBar
- style="?android:attr/seekBarStyle"
- android:id="@+id/seekbar"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:padding="16dip"
- android:layout_marginEnd="16dip" />
-
+ android:id="@+id/stream_icon"
+ style="@style/BorderlessButton.Tiny"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:padding="16dip"
+ android:contentDescription="@null" />
+ <FrameLayout
+ android:id="@+id/seekbar_container"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+ <SeekBar
+ style="?android:attr/seekBarStyle"
+ android:id="@+id/seekbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="16dip"
+ android:paddingBottom="16dip"
+ android:paddingStart="11dip"
+ android:paddingEnd="11dip"
+ android:layout_marginEnd="16dip" />
+ </FrameLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3646ade..bfbdcf3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -305,6 +305,7 @@
keyguard_clock_height_fraction_* for the difference between min and max.-->
<dimen name="keyguard_clock_notifications_margin_min">22dp</dimen>
<dimen name="keyguard_clock_notifications_margin_max">36dp</dimen>
+ <dimen name="heads_up_window_height">250dp</dimen>
<!-- The minimum amount the user needs to swipe to go to the camera / phone. -->
<dimen name="keyguard_min_swipe_amount">75dp</dimen>
@@ -317,4 +318,7 @@
<!-- Volume panel z depth -->
<dimen name="volume_panel_z">3dp</dimen>
+
+ <!-- Move distance for the hint animations on the lockscreen (unlock, phone, camera)-->
+ <dimen name="hint_move_distance">75dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 59f1efd..373f11f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -290,6 +290,8 @@
<string name="accessibility_desc_off">Off.</string>
<!-- Content description of an item that is connected for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_desc_connected">Connected.</string>
+ <!-- Content description of an item that is connecting for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_desc_connecting">Connecting.</string>
<!-- Content description of the data connection type GPRS for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_data_connection_gprs">GPRS</string>
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 1b12cb0..d153d09 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -51,12 +51,12 @@
private int MAX_DISMISS_VELOCITY = 2000; // dp/sec
private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
- public static float ALPHA_FADE_START = 0f; // fraction of thumbnail width
+ public static float SWIPE_PROGRESS_FADE_START = 0f; // fraction of thumbnail width
// where fade starts
- static final float ALPHA_FADE_END = 0.5f; // fraction of thumbnail width
- // beyond which alpha->0
- private float mMinAlpha = 0f;
- private float mMaxAlpha = 1f;
+ static final float SWIPE_PROGRESS_FADE_END = 0.5f; // fraction of thumbnail width
+ // beyond which swipe progress->0
+ private float mMinSwipeProgress = 0f;
+ private float mMaxSwipeProgress = 1f;
private float mPagingTouchSlop;
private Callback mCallback;
@@ -137,36 +137,39 @@
v.getMeasuredHeight();
}
- public void setMinAlpha(float minAlpha) {
- mMinAlpha = minAlpha;
+ public void setMinSwipeProgress(float minSwipeProgress) {
+ mMinSwipeProgress = minSwipeProgress;
}
- public void setMaxAlpha(float maxAlpha) {
- mMaxAlpha = maxAlpha;
+ public void setMaxSwipeProgress(float maxSwipeProgress) {
+ mMaxSwipeProgress = maxSwipeProgress;
}
- private float getAlphaForOffset(View view) {
+ private float getSwipeProgressForOffset(View view) {
float viewSize = getSize(view);
- final float fadeSize = ALPHA_FADE_END * viewSize;
+ final float fadeSize = SWIPE_PROGRESS_FADE_END * viewSize;
float result = 1.0f;
float pos = getTranslation(view);
- if (pos >= viewSize * ALPHA_FADE_START) {
- result = 1.0f - (pos - viewSize * ALPHA_FADE_START) / fadeSize;
- } else if (pos < viewSize * (1.0f - ALPHA_FADE_START)) {
- result = 1.0f + (viewSize * ALPHA_FADE_START + pos) / fadeSize;
+ if (pos >= viewSize * SWIPE_PROGRESS_FADE_START) {
+ result = 1.0f - (pos - viewSize * SWIPE_PROGRESS_FADE_START) / fadeSize;
+ } else if (pos < viewSize * (1.0f - SWIPE_PROGRESS_FADE_START)) {
+ result = 1.0f + (viewSize * SWIPE_PROGRESS_FADE_START + pos) / fadeSize;
}
- return Math.min(Math.max(mMinAlpha, result), mMaxAlpha);
+ return Math.min(Math.max(mMinSwipeProgress, result), mMaxSwipeProgress);
}
- private void updateAlphaFromOffset(View animView, boolean dismissable) {
- if (FADE_OUT_DURING_SWIPE && dismissable) {
- float alpha = getAlphaForOffset(animView);
- if (alpha != 0f && alpha != 1f) {
- animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- } else {
- animView.setLayerType(View.LAYER_TYPE_NONE, null);
+ private void updateSwipeProgressFromOffset(View animView, boolean dismissable) {
+ float swipeProgress = getSwipeProgressForOffset(animView);
+ if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
+ if (FADE_OUT_DURING_SWIPE && dismissable) {
+ float alpha = swipeProgress;
+ if (alpha != 0f && alpha != 1f) {
+ animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ } else {
+ animView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ animView.setAlpha(getSwipeProgressForOffset(animView));
}
- animView.setAlpha(getAlphaForOffset(animView));
}
invalidateGlobalRegion(animView);
}
@@ -307,7 +310,7 @@
});
anim.addUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
- updateAlphaFromOffset(animView, canAnimViewBeDismissed);
+ updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
}
});
anim.start();
@@ -321,12 +324,12 @@
anim.setDuration(duration);
anim.addUpdateListener(new AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
- updateAlphaFromOffset(animView, canAnimViewBeDismissed);
+ updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
}
});
anim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animator) {
- updateAlphaFromOffset(animView, canAnimViewBeDismissed);
+ updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed);
mCallback.onChildSnappedBack(animView);
}
});
@@ -365,7 +368,7 @@
}
setTranslation(mCurrAnimView, delta);
- updateAlphaFromOffset(mCurrAnimView, mCanCurrViewBeDimissed);
+ updateSwipeProgressFromOffset(mCurrAnimView, mCanCurrViewBeDimissed);
}
break;
case MotionEvent.ACTION_UP:
@@ -415,5 +418,12 @@
void onDragCancelled(View v);
void onChildSnappedBack(View animView);
+
+ /**
+ * Updates the swipe progress on a child.
+ *
+ * @return if true, prevents the default alpha fading.
+ */
+ boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 4c7f3df..b280ab7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -204,9 +204,9 @@
}
@Override
- public void startKeyguardExitAnimation(long fadeoutDuration) {
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
checkPermission();
- mKeyguardViewMediator.startKeyguardExitAnimation(fadeoutDuration);
+ mKeyguardViewMediator.startKeyguardExitAnimation(startTime, fadeoutDuration);
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 7110d8d..f7b4994 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1083,7 +1083,8 @@
handleDismiss();
break;
case START_KEYGUARD_EXIT_ANIM:
- handleStartKeyguardExitAnimation((Long) msg.obj);
+ StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
+ handleStartKeyguardExitAnimation(params.startTime, params.fadeoutDuration);
break;
}
}
@@ -1227,7 +1228,7 @@
}
}
- private void handleStartKeyguardExitAnimation(long fadeoutDuration) {
+ private void handleStartKeyguardExitAnimation(long startTime, long fadeoutDuration) {
synchronized (KeyguardViewMediator.this) {
// only play "unlock" noises if not on a call (since the incall UI
@@ -1236,7 +1237,7 @@
playSounds(false);
}
- mStatusBarKeyguardViewManager.hide();
+ mStatusBarKeyguardViewManager.hide(startTime, fadeoutDuration);
mShowing = false;
mKeyguardDonePending = false;
updateActivityLockScreenState();
@@ -1346,12 +1347,24 @@
return mStatusBarKeyguardViewManager;
}
- public void startKeyguardExitAnimation(long fadeoutDuration) {
- Message msg = mHandler.obtainMessage(START_KEYGUARD_EXIT_ANIM, fadeoutDuration);
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
+ Message msg = mHandler.obtainMessage(START_KEYGUARD_EXIT_ANIM,
+ new StartKeyguardExitAnimParams(startTime, fadeoutDuration));
mHandler.sendMessage(msg);
}
public ViewMediatorCallback getViewMediatorCallback() {
return mViewMediatorCallback;
}
+
+ private static class StartKeyguardExitAnimParams {
+
+ long startTime;
+ long fadeoutDuration;
+
+ private StartKeyguardExitAnimParams(long startTime, long fadeoutDuration) {
+ this.startTime = startTime;
+ this.fadeoutDuration = fadeoutDuration;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index c8cf05d..2bf369a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -152,6 +152,7 @@
}
private void handleShowDetail(TileRecord r, boolean show) {
+ if (r == null) return;
AnimatorListener listener = null;
if (show) {
if (mDetailRecord != null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 7335ab4..d220e1a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -19,6 +19,7 @@
import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback;
import android.content.Intent;
import android.provider.Settings;
+import android.text.TextUtils;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
@@ -70,18 +71,27 @@
final boolean supported = mController.isBluetoothSupported();
final boolean enabled = mController.isBluetoothEnabled();
final boolean connected = mController.isBluetoothConnected();
+ final boolean connecting = mController.isBluetoothConnecting();
state.visible = supported;
state.value = enabled;
final String stateContentDescription;
if (enabled) {
+ state.label = null;
if (connected) {
state.iconId = R.drawable.ic_qs_bluetooth_connected;
stateContentDescription = mContext.getString(R.string.accessibility_desc_connected);
+ state.label = mController.getLastDeviceName();
+ } else if (connecting) {
+ state.iconId = R.drawable.ic_qs_bluetooth_connecting;
+ stateContentDescription = mContext.getString(R.string.accessibility_desc_connecting);
+ state.label = mController.getLastDeviceName();
} else {
state.iconId = R.drawable.ic_qs_bluetooth_on;
stateContentDescription = mContext.getString(R.string.accessibility_desc_on);
}
- state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
+ if (TextUtils.isEmpty(state.label)) {
+ state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
+ }
} else {
state.iconId = R.drawable.ic_qs_bluetooth_off;
state.label = mContext.getString(R.string.quick_settings_bluetooth_off_label);
@@ -91,9 +101,9 @@
R.string.accessibility_quick_settings_bluetooth, stateContentDescription);
}
- private final BluetoothStateChangeCallback mCallback = new BluetoothStateChangeCallback() {
+ private final BluetoothController.Callback mCallback = new BluetoothController.Callback() {
@Override
- public void onBluetoothStateChange(boolean on) {
+ public void onBluetoothStateChange(boolean enabled, boolean connecting) {
refreshState();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
index 0759b8e..72a3341 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
@@ -64,7 +64,7 @@
}
public void setMinSwipeAlpha(float minAlpha) {
- mSwipeHelper.setMinAlpha(minAlpha);
+ mSwipeHelper.setMinSwipeProgress(minAlpha);
}
private int scrollPositionOfMostRecent() {
@@ -221,6 +221,11 @@
public void onChildSnappedBack(View animView) {
}
+ @Override
+ public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+ return false;
+ }
+
public View getChildAtPosition(MotionEvent ev) {
final float x = ev.getX() + getScrollX();
final float y = ev.getY() + getScrollY();
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
index c2dde6a..1213375 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
@@ -65,7 +65,7 @@
}
public void setMinSwipeAlpha(float minAlpha) {
- mSwipeHelper.setMinAlpha(minAlpha);
+ mSwipeHelper.setMinSwipeProgress(minAlpha);
}
private int scrollPositionOfMostRecent() {
@@ -229,6 +229,11 @@
public void onChildSnappedBack(View animView) {
}
+ @Override
+ public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+ return false;
+ }
+
public View getChildAtPosition(MotionEvent ev) {
final float x = ev.getX() + getScrollX();
final float y = ev.getY() + getScrollY();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BounceInterpolator.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BounceInterpolator.java
new file mode 100644
index 0000000..367d326
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BounceInterpolator.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.view.animation.Interpolator;
+
+/**
+ * An implementation of a bouncer interpolator optimized for unlock hinting.
+ */
+public class BounceInterpolator implements Interpolator {
+
+ private final static float SCALE_FACTOR = 7.5625f;
+
+ @Override
+ public float getInterpolation(float t) {
+ if (t < 4f / 11f) {
+ return SCALE_FACTOR * t * t;
+ } else if (t < 8f / 11f) {
+ float t2 = t - 6f / 11f;
+ return SCALE_FACTOR * t2 * t2 + 3f / 4f;
+ } else if (t < 10f / 11f) {
+ float t2 = t - 9f / 11f;
+ return SCALE_FACTOR * t2 * t2 + 15f / 16f;
+ } else {
+ float t2 = t - 21f / 22f;
+ return SCALE_FACTOR * t2 * t2 + 63f / 64f;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 994b329..97aa993 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.MediaStore;
@@ -32,6 +33,10 @@
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.R;
/**
@@ -43,6 +48,11 @@
final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView";
+ private static final Intent SECURE_CAMERA_INTENT =
+ new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
+ .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ private static final Intent INSECURE_CAMERA_INTENT =
+ new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
private ImageView mCameraImageView;
@@ -51,6 +61,7 @@
private ActivityStarter mActivityStarter;
private UnlockMethodCache mUnlockMethodCache;
+ private LockPatternUtils mLockPatternUtils;
public KeyguardBottomAreaView(Context context) {
super(context);
@@ -72,10 +83,11 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mLockPatternUtils = new LockPatternUtils(mContext);
mCameraImageView = (ImageView) findViewById(R.id.camera_button);
mPhoneImageView = (ImageView) findViewById(R.id.phone_button);
mLockIcon = (ImageView) findViewById(R.id.lock_icon);
- watchForDevicePolicyChanges();
+ watchForCameraPolicyChanges();
watchForAccessibilityChanges();
updateCameraVisibility();
updatePhoneVisibility();
@@ -88,8 +100,19 @@
mActivityStarter = activityStarter;
}
+ private Intent getCameraIntent() {
+ KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ boolean currentUserHasTrust = updateMonitor.getUserHasTrust(
+ mLockPatternUtils.getCurrentUser());
+ return mLockPatternUtils.isSecure() && !currentUserHasTrust
+ ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
+ }
+
private void updateCameraVisibility() {
- boolean visible = !isCameraDisabledByDpm();
+ ResolveInfo resolved = mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
+ PackageManager.MATCH_DEFAULT_ONLY,
+ mLockPatternUtils.getCurrentUser());
+ boolean visible = !isCameraDisabledByDpm() && resolved != null;
mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
}
@@ -122,19 +145,12 @@
return false;
}
- private void watchForDevicePolicyChanges() {
+ private void watchForCameraPolicyChanges() {
final IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
- getContext().registerReceiver(new BroadcastReceiver() {
- public void onReceive(Context context, Intent intent) {
- post(new Runnable() {
- @Override
- public void run() {
- updateCameraVisibility();
- }
- });
- }
- }, filter);
+ getContext().registerReceiverAsUser(mDevicePolicyReceiver,
+ UserHandle.ALL, filter, null, null);
+ KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
}
private void watchForAccessibilityChanges() {
@@ -171,9 +187,12 @@
}
public void launchCamera() {
- mContext.startActivityAsUser(
- new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE),
- UserHandle.CURRENT);
+ Intent intent = getCameraIntent();
+ if (intent == SECURE_CAMERA_INTENT) {
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ } else {
+ mActivityStarter.startActivity(intent);
+ }
}
public void launchPhone() {
@@ -186,6 +205,7 @@
super.onVisibilityChanged(changedView, visibility);
if (changedView == this && visibility == VISIBLE) {
updateTrust();
+ updateCameraVisibility();
}
}
@@ -214,5 +234,25 @@
@Override
public void onMethodSecureChanged(boolean methodSecure) {
updateTrust();
+ updateCameraVisibility();
}
+
+ private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ updateCameraVisibility();
+ }
+ });
+ }
+ };
+
+ private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onUserSwitchComplete(int userId) {
+ updateCameraVisibility();
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index b7a7b0a..3aaace4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -17,10 +17,14 @@
package com.android.systemui.statusbar.phone;
import android.content.Context;
+import android.os.SystemClock;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardViewBase;
@@ -43,6 +47,8 @@
private StatusBarWindowManager mWindowManager;
private KeyguardViewBase mKeyguardView;
private ViewGroup mRoot;
+ private Interpolator mFadeOutInterpolator = new LinearInterpolator();
+ private boolean mFadingOut;
public KeyguardBouncer(Context context, ViewMediatorCallback callback,
LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager,
@@ -86,6 +92,29 @@
}
}
+ public void animateHide(long delay, long duration) {
+ if (isShowing()) {
+ mFadingOut = true;
+ mKeyguardView.animate()
+ .alpha(0)
+ .withLayer()
+
+ // Make it disappear faster, as the focus should be on the activity behind.
+ .setDuration(duration / 3)
+ .setInterpolator(mFadeOutInterpolator)
+ .setStartDelay(delay)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mFadingOut = false;
+ hide(true /* destroyView */);
+ }
+ });
+ } else {
+ hide(true /* destroyView */);
+ }
+ }
+
/**
* Reset the state of the view.
*/
@@ -110,7 +139,7 @@
}
public boolean isShowing() {
- return mRoot != null && mRoot.getVisibility() == View.VISIBLE;
+ return mRoot != null && mRoot.getVisibility() == View.VISIBLE && !mFadingOut;
}
public void prepare() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index c8f185c..dfd5a88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -45,12 +45,12 @@
public class NotificationPanelView extends PanelView implements
ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
- View.OnClickListener, KeyguardPageSwipeHelper.Callback {
+ View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
+ KeyguardPageSwipeHelper.Callback {
- private static float EXPANSION_RUBBER_BAND_EXTRA_FACTOR = 0.4f;
+ private static float EXPANSION_RUBBER_BAND_EXTRA_FACTOR = 0.6f;
private KeyguardPageSwipeHelper mPageSwiper;
- PhoneStatusBar mStatusBar;
private StatusBarHeaderView mHeader;
private View mQsContainer;
private View mQsPanel;
@@ -86,6 +86,7 @@
private int mQsPeekHeight;
private float mNotificationTranslation;
private int mStackScrollerIntrinsicPadding;
+ private boolean mStackScrollerOverscrolling;
private boolean mQsExpansionEnabled = true;
private ValueAnimator mQsExpansionAnimator;
private FlingAnimationUtils mFlingAnimationUtils;
@@ -139,6 +140,7 @@
mNotificationStackScroller = (NotificationStackScrollLayout)
findViewById(R.id.notification_stack_scroller);
mNotificationStackScroller.setOnHeightChangedListener(this);
+ mNotificationStackScroller.setOverscrollTopChangedListener(this);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
android.R.interpolator.fast_out_slow_in);
mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
@@ -172,7 +174,9 @@
setQsStackScrollerPadding(mQsMaxExpansionHeight);
}
} else {
- setQsExpansion(mQsMinExpansionHeight);
+ if (!mStackScrollerOverscrolling) {
+ setQsExpansion(mQsMinExpansionHeight);
+ }
positionClockAndNotifications();
mNotificationStackScroller.setStackHeight(getExpandedHeight());
}
@@ -185,7 +189,10 @@
private void positionClockAndNotifications() {
boolean animateClock = mNotificationStackScroller.isAddOrRemoveAnimationPending();
if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) {
- mStackScrollerIntrinsicPadding = mHeader.getBottom() + mQsPeekHeight
+ int bottom = mStackScrollerOverscrolling
+ ? mHeader.getCollapsedHeight()
+ : mHeader.getBottom();
+ mStackScrollerIntrinsicPadding = bottom + mQsPeekHeight
+ mNotificationTopPadding;
mTopPaddingAdjustment = 0;
} else {
@@ -489,6 +496,16 @@
}
}
+
+ @Override
+ public void onOverscrollTopChanged(float amount) {
+ cancelAnimation();
+ float rounded = amount >= 1f ? amount : 0f;
+ mStackScrollerOverscrolling = rounded != 0f;
+ setQsExpansion(mQsMinExpansionHeight + rounded);
+ updateQsState();
+ }
+
private void onQsExpansionStarted() {
onQsExpansionStarted(0);
}
@@ -516,9 +533,10 @@
}
private void updateQsState() {
- mHeader.setExpanded(mQsExpanded);
+ boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling;
+ mHeader.setExpanded(expandVisually, mStackScrollerOverscrolling);
mNotificationStackScroller.setEnabled(!mQsExpanded);
- mQsPanel.setVisibility(mQsExpanded ? View.VISIBLE : View.INVISIBLE);
+ mQsPanel.setVisibility(expandVisually ? View.VISIBLE : View.INVISIBLE);
mQsContainer.setVisibility(mKeyguardShowing && !mQsExpanded
? View.INVISIBLE
: View.VISIBLE);
@@ -528,7 +546,7 @@
private void setQsExpansion(float height) {
height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
mQsFullyExpanded = height == mQsMaxExpansionHeight;
- if (height > mQsMinExpansionHeight && !mQsExpanded) {
+ if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) {
setQsExpanded(true);
} else if (height <= mQsMinExpansionHeight && mQsExpanded) {
setQsExpanded(false);
@@ -536,7 +554,9 @@
mQsExpansionHeight = height;
mHeader.setExpansion(height - mQsPeekHeight);
setQsTranslation(height);
- setQsStackScrollerPadding(height);
+ if (!mStackScrollerOverscrolling) {
+ setQsStackScrollerPadding(height);
+ }
mStatusBar.userActivity();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index c5a9b85..4686933 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -27,10 +27,13 @@
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.StatusBarState;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -44,13 +47,16 @@
Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
}
+ protected PhoneStatusBar mStatusBar;
private float mPeekHeight;
+ private float mHintDistance;
private float mInitialOffsetOnTouch;
private float mExpandedFraction = 0;
private float mExpandedHeight = 0;
private boolean mJustPeeked;
private boolean mClosing;
private boolean mTracking;
+ private boolean mTouchSlopExceeded;
private int mTrackingPointer;
protected int mTouchSlop;
@@ -66,6 +72,9 @@
private float mInitialTouchY;
private float mInitialTouchX;
+ private Interpolator mLinearOutSlowInInterpolator;
+ private Interpolator mBounceInterpolator;
+
protected void onExpandingFinished() {
mBar.onExpandingFinished();
}
@@ -89,6 +98,9 @@
public PanelView(Context context, AttributeSet attrs) {
super(context, attrs);
mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f);
+ mLinearOutSlowInInterpolator =
+ AnimationUtils.loadInterpolator(context, android.R.interpolator.fast_out_slow_in);
+ mBounceInterpolator = new BounceInterpolator();
}
protected void loadDimens() {
@@ -98,6 +110,7 @@
final ViewConfiguration configuration = ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
+ mHintDistance = res.getDimension(R.dimen.hint_move_distance);
}
private void trackMovement(MotionEvent event) {
@@ -138,6 +151,7 @@
mInitialTouchY = y;
mInitialTouchX = x;
mInitialOffsetOnTouch = mExpandedHeight;
+ mTouchSlopExceeded = false;
if (mVelocityTracker == null) {
initVelocityTracker();
}
@@ -170,16 +184,18 @@
case MotionEvent.ACTION_MOVE:
float h = y - mInitialTouchY;
- if (waitForTouchSlop && !mTracking && Math.abs(h) > mTouchSlop
- && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
- mInitialOffsetOnTouch = mExpandedHeight;
- mInitialTouchX = x;
- mInitialTouchY = y;
- if (mHeightAnimator != null) {
- mHeightAnimator.cancel(); // end any outstanding animations
+ if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
+ mTouchSlopExceeded = true;
+ if (waitForTouchSlop && !mTracking) {
+ mInitialOffsetOnTouch = mExpandedHeight;
+ mInitialTouchX = x;
+ mInitialTouchY = y;
+ if (mHeightAnimator != null) {
+ mHeightAnimator.cancel(); // end any outstanding animations
+ }
+ onTrackingStarted();
+ h = 0;
}
- onTrackingStarted();
- h = 0;
}
final float newHeight = h + mInitialOffsetOnTouch;
if (newHeight > mPeekHeight) {
@@ -200,8 +216,15 @@
case MotionEvent.ACTION_CANCEL:
mTrackingPointer = -1;
trackMovement(event);
- boolean expand = flingWithCurrentVelocity();
- onTrackingStopped(expand);
+ if (mTracking && mTouchSlopExceeded) {
+ float vel = getCurrentVelocity();
+ boolean expand = flingExpands(vel);
+ onTrackingStopped(expand);
+ fling(vel, expand);
+ } else {
+ boolean expands = onEmptySpaceClick();
+ onTrackingStopped(expands);
+ }
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
@@ -262,6 +285,7 @@
}
mInitialTouchY = y;
mInitialTouchX = x;
+ mTouchSlopExceeded = false;
initVelocityTracker();
trackMovement(event);
break;
@@ -285,6 +309,7 @@
mInitialTouchY = y;
mInitialTouchX = x;
mTracking = true;
+ mTouchSlopExceeded = true;
onTrackingStarted();
return true;
}
@@ -323,18 +348,15 @@
}
/**
- * @return whether the panel will be expanded after the animation
+ * @param vel the current velocity of the motion
+ * @return whether a fling should expands the panel; contracts otherwise
*/
- private boolean flingWithCurrentVelocity() {
- float vel = getCurrentVelocity();
- boolean expand;
+ private boolean flingExpands(float vel) {
if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
- expand = getExpandedFraction() > 0.5f;
+ return getExpandedFraction() > 0.5f;
} else {
- expand = vel > 0;
+ return vel > 0;
}
- fling(vel, expand);
- return expand;
}
protected void fling(float vel, boolean expand) {
@@ -342,9 +364,10 @@
float target = expand ? getMaxPanelHeight() : 0.0f;
if (target == mExpandedHeight) {
onExpandingFinished();
+ mBar.panelExpansionChanged(this, mExpandedFraction);
return;
}
- ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, target);
+ ValueAnimator animator = createHeightAnimator(target);
if (expand) {
mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
} else {
@@ -356,12 +379,6 @@
animator.setDuration((long) (animator.getDuration() / 1.75f));
}
}
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- setExpandedHeight((Float) animation.getAnimatedValue());
- }
- });
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -430,7 +447,7 @@
public void setExpandedHeightInternal(float h) {
float fh = getMaxPanelHeight();
- mExpandedHeight = Math.min(fh, h);
+ mExpandedHeight = Math.max(0, Math.min(fh, h));
float overExpansion = h - fh;
overExpansion = Math.max(0, overExpansion);
if (overExpansion != mOverExpansion) {
@@ -442,16 +459,14 @@
}
onHeightUpdated(mExpandedHeight);
- mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : h / fh);
+ mExpandedFraction = Math.min(1f, (fh == 0) ? 0 : mExpandedHeight / fh);
}
protected void onOverExpansionChanged(float overExpansion) {
mOverExpansion = overExpansion;
}
- protected void onHeightUpdated(float expandedHeight) {
- requestLayout();
- }
+ protected abstract void onHeightUpdated(float expandedHeight);
/**
* This returns the maximum height of the panel. Children should override this if their
@@ -526,6 +541,101 @@
}
}
+ protected void startUnlockHintAnimation() {
+
+ // We don't need to hint the user if an animation is already running or the user is changing
+ // the expansion.
+ if (mHeightAnimator != null || mTracking) {
+ return;
+ }
+ cancelPeek();
+ onExpandingStarted();
+ startUnlockHintAnimationPhase1();
+ mStatusBar.onUnlockHintStarted();
+ }
+
+ /**
+ * Phase 1: Move everything upwards.
+ */
+ private void startUnlockHintAnimationPhase1() {
+ float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
+ ValueAnimator animator = createHeightAnimator(target);
+ animator.setDuration(250);
+ animator.setInterpolator(mLinearOutSlowInInterpolator);
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCancelled) {
+ mHeightAnimator = null;
+ onExpandingFinished();
+ mStatusBar.onUnlockHintFinished();
+ } else {
+ startUnlockHintAnimationPhase2();
+ }
+ }
+ });
+ animator.start();
+ mHeightAnimator = animator;
+ }
+
+ /**
+ * Phase 2: Bounce down.
+ */
+ private void startUnlockHintAnimationPhase2() {
+ ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
+ animator.setDuration(450);
+ animator.setInterpolator(mBounceInterpolator);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mHeightAnimator = null;
+ onExpandingFinished();
+ mStatusBar.onUnlockHintFinished();
+ }
+ });
+ animator.start();
+ mHeightAnimator = animator;
+ }
+
+ private ValueAnimator createHeightAnimator(float targetHeight) {
+ ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ setExpandedHeight((Float) animation.getAnimatedValue());
+ }
+ });
+ return animator;
+ }
+
+ /**
+ * Gets called when the user performs a click anywhere in the empty area of the panel.
+ *
+ * @return whether the panel will be expanded after the action performed by this method
+ */
+ private boolean onEmptySpaceClick() {
+ switch (mStatusBar.getBarState()) {
+ case StatusBarState.KEYGUARD:
+ startUnlockHintAnimation();
+ return true;
+ case StatusBarState.SHADE_LOCKED:
+ // TODO: Go to Keyguard again.
+ return true;
+ case StatusBarState.SHADE:
+ collapse();
+ return false;
+ default:
+ return true;
+ }
+ }
+
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
+ " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index f9afcf3..b1216e69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -980,8 +980,10 @@
}
private void addHeadsUpView() {
+ int headsUpHeight = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.heads_up_window_height);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT, headsUpHeight,
WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar!
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
@@ -2955,15 +2957,17 @@
}
public void onTrackingStarted() {
- if (mState == StatusBarState.KEYGUARD) {
- mKeyguardIndicationTextView.switchIndication(R.string.keyguard_unlock);
- }
+ }
+
+ public void onUnlockHintStarted() {
+ mKeyguardIndicationTextView.switchIndication(R.string.keyguard_unlock);
+ }
+
+ public void onUnlockHintFinished() {
+ mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase);
}
public void onTrackingStopped(boolean expand) {
- if (mState == StatusBarState.KEYGUARD) {
- mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase);
- }
if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
if (!expand && !mUnlockMethodCache.isMethodInsecure()) {
showBouncer();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 1344703..7c87580 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -95,7 +95,6 @@
mTiles.add(new LocationTile(this));
mTiles.add(new CastTile(this));
mTiles.add(new HotspotTile(this));
- mTiles.add(new BugreportTile(this));
mUserTracker = new CurrentUserTracker(mContext) {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 6156fc3..1264d75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -19,16 +19,12 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.View;
import android.view.ViewTreeObserver;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
-import android.view.animation.LinearInterpolator;
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
@@ -53,6 +49,11 @@
private boolean mAnimateChange;
private boolean mUpdatePending;
private boolean mExpanding;
+ private boolean mAnimateKeyguardFadingOut;
+ private long mDurationOverride = -1;
+ private long mAnimationDelay;
+ private Runnable mOnAnimationFinished;
+ private boolean mAnimationStarted;
private final Interpolator mInterpolator = new DecelerateInterpolator();
@@ -87,14 +88,26 @@
scheduleUpdate();
}
+ public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished) {
+ mAnimateKeyguardFadingOut = true;
+ mDurationOverride = duration;
+ mAnimationDelay = delay;
+ mAnimateChange = true;
+ mOnAnimationFinished = onAnimationFinished;
+ scheduleUpdate();
+ }
+
private void scheduleUpdate() {
if (mUpdatePending) return;
+
+ // Make sure that a frame gets scheduled.
+ mScrimBehind.invalidate();
mScrimBehind.getViewTreeObserver().addOnPreDrawListener(this);
mUpdatePending = true;
}
private void updateScrims() {
- if (!mKeyguardShowing) {
+ if (!mKeyguardShowing || mAnimateKeyguardFadingOut) {
updateScrimNormal();
setScrimInFrontColor(0);
} else {
@@ -170,8 +183,20 @@
}
});
anim.setInterpolator(mInterpolator);
- anim.setDuration(ANIMATION_DURATION);
+ anim.setStartDelay(mAnimationDelay);
+ anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
+ anim.addListener(new AnimatorListenerAdapter() {
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mOnAnimationFinished != null) {
+ mOnAnimationFinished.run();
+ mOnAnimationFinished = null;
+ }
+ }
+ });
anim.start();
+ mAnimationStarted = true;
}
private int getBackgroundAlpha(View scrim) {
@@ -188,6 +213,16 @@
mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
mUpdatePending = false;
updateScrims();
+ mAnimateKeyguardFadingOut = false;
+ mDurationOverride = -1;
+ mAnimationDelay = 0;
+
+ // Make sure that we always call the listener even if we didn't start an animation.
+ if (!mAnimationStarted && mOnAnimationFinished != null) {
+ mOnAnimationFinished.run();
+ mOnAnimationFinished = null;
+ }
+ mAnimationStarted = false;
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index c097e2d..13d3291 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -44,6 +44,7 @@
private static final float EXPANSION_RUBBERBAND_FACTOR = 0.35f;
private boolean mExpanded;
+ private boolean mOverscrolled;
private boolean mKeyguardShowing;
private View mBackground;
@@ -125,10 +126,12 @@
return mExpandedHeight;
}
- public void setExpanded(boolean expanded) {
+ public void setExpanded(boolean expanded, boolean overscrolled) {
boolean changed = expanded != mExpanded;
+ boolean overscrollChanged = overscrolled != mOverscrolled;
mExpanded = expanded;
- if (changed) {
+ mOverscrolled = overscrolled;
+ if (changed || overscrollChanged) {
updateHeights();
updateVisibilities();
updateSystemIconsLayoutParams();
@@ -136,7 +139,7 @@
updateZTranslation();
updateClickTargets();
if (mQSPanel != null) {
- mQSPanel.setExpanded(expanded);
+ mQSPanel.setExpanded(expanded && !overscrolled);
}
}
}
@@ -184,13 +187,13 @@
mDateTime.setVisibility(onKeyguardAndCollapsed ? View.INVISIBLE : View.VISIBLE);
mKeyguardCarrierText.setVisibility(onKeyguardAndCollapsed ? View.VISIBLE : View.GONE);
mDate.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
- mSettingsButton.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
+ mSettingsButton.setVisibility(mExpanded && !mOverscrolled ? View.VISIBLE : View.GONE);
mBrightnessContainer.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
if (mStatusIcons != null) {
- mStatusIcons.setVisibility(!mExpanded ? View.VISIBLE : View.GONE);
+ mStatusIcons.setVisibility(!mExpanded || mOverscrolled ? View.VISIBLE : View.GONE);
}
if (mSignalCluster != null) {
- mSignalCluster.setVisibility(!mExpanded ? View.VISIBLE : View.GONE);
+ mSignalCluster.setVisibility(!mExpanded || mOverscrolled ? View.VISIBLE : View.GONE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index d5551b8..e3145a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Slog;
import android.view.KeyEvent;
import android.view.View;
@@ -183,11 +184,23 @@
/**
* Hides the keyguard view
*/
- public void hide() {
+ public void hide(long startTime, long fadeoutDuration) {
mShowing = false;
mPhoneStatusBar.hideKeyguard();
+ mStatusBarWindowManager.setKeyguardFadingAway(true);
mStatusBarWindowManager.setKeyguardShowing(false);
- mBouncer.hide(true /* destroyView */);
+ long uptimeMillis = SystemClock.uptimeMillis();
+ long delay = startTime - uptimeMillis;
+ if (delay < 0) {
+ delay = 0;
+ }
+ mBouncer.animateHide(delay, fadeoutDuration);
+ mScrimController.animateKeyguardFadingOut(delay, fadeoutDuration, new Runnable() {
+ @Override
+ public void run() {
+ mStatusBarWindowManager.setKeyguardFadingAway(false);
+ }
+ });
mViewMediatorCallback.keyguardGone();
updateStates();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index b7bf6cd..fe57cef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -124,7 +124,8 @@
}
private void applyHeight(State state) {
- boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded;
+ boolean expanded = state.isKeyguardShowingAndNotOccluded() || state.statusBarExpanded
+ || state.keyguardFadingAway;
if (expanded) {
mLp.height = ViewGroup.LayoutParams.MATCH_PARENT;
} else {
@@ -201,6 +202,11 @@
apply(mCurrentState);
}
+ public void setKeyguardFadingAway(boolean keyguardFadingAway) {
+ mCurrentState.keyguardFadingAway = keyguardFadingAway;
+ apply(mCurrentState);
+ }
+
/**
* @param state The {@link StatusBarState} of the status bar.
*/
@@ -217,6 +223,7 @@
boolean statusBarFocusable;
long keyguardUserActivityTimeout;
boolean bouncerShowing;
+ boolean keyguardFadingAway;
/**
* The {@link BaseStatusBar} state from the status bar.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index f4145cd..8e9fb30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -16,14 +16,18 @@
package com.android.systemui.statusbar.policy;
-import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback;
-
public interface BluetoothController {
- void addStateChangedCallback(BluetoothStateChangeCallback callback);
- void removeStateChangedCallback(BluetoothStateChangeCallback callback);
+ void addStateChangedCallback(Callback callback);
+ void removeStateChangedCallback(Callback callback);
boolean isBluetoothSupported();
boolean isBluetoothEnabled();
boolean isBluetoothConnected();
+ boolean isBluetoothConnecting();
+ String getLastDeviceName();
void setBluetoothEnabled(boolean enabled);
+
+ public interface Callback {
+ void onBluetoothStateChange(boolean enabled, boolean connecting);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 5a19881..117bf61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.policy;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -31,14 +30,13 @@
public class BluetoothControllerImpl extends BroadcastReceiver implements BluetoothController {
private static final String TAG = "StatusBar.BluetoothController";
+ private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final Set<BluetoothDevice> mBondedDevices = new HashSet<BluetoothDevice>();
private final BluetoothAdapter mAdapter;
- private boolean mEnabled = false;
-
- private Set<BluetoothDevice> mBondedDevices = new HashSet<BluetoothDevice>();
-
- private ArrayList<BluetoothStateChangeCallback> mChangeCallbacks =
- new ArrayList<BluetoothStateChangeCallback>();
+ private boolean mEnabled;
+ private boolean mConnecting;
+ private BluetoothDevice mLastDevice;
public BluetoothControllerImpl(Context context) {
mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -57,14 +55,14 @@
updateBondedBluetoothDevices();
}
- public void addStateChangedCallback(BluetoothStateChangeCallback cb) {
- mChangeCallbacks.add(cb);
+ public void addStateChangedCallback(Callback cb) {
+ mCallbacks.add(cb);
fireCallback(cb);
}
@Override
- public void removeStateChangedCallback(BluetoothStateChangeCallback cb) {
- mChangeCallbacks.remove(cb);
+ public void removeStateChangedCallback(Callback cb) {
+ mCallbacks.remove(cb);
}
@Override
@@ -79,6 +77,12 @@
}
@Override
+ public boolean isBluetoothConnecting() {
+ return mAdapter != null
+ && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING;
+ }
+
+ @Override
public void setBluetoothEnabled(boolean enabled) {
if (mAdapter != null) {
if (enabled) {
@@ -99,6 +103,13 @@
}
@Override
+ public String getLastDeviceName() {
+ return mLastDevice != null ? mLastDevice.getName()
+ : mBondedDevices.size() == 1 ? mBondedDevices.iterator().next().getName()
+ : null;
+ }
+
+ @Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
@@ -106,6 +117,11 @@
handleAdapterStateChange(
intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR));
}
+ if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
+ mConnecting = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1)
+ == BluetoothAdapter.STATE_CONNECTING;
+ mLastDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ }
fireCallbacks();
updateBondedBluetoothDevices();
}
@@ -131,12 +147,12 @@
}
private void fireCallbacks() {
- for (BluetoothStateChangeCallback cb : mChangeCallbacks) {
+ for (Callback cb : mCallbacks) {
fireCallback(cb);
}
}
- private void fireCallback(BluetoothStateChangeCallback cb) {
- cb.onBluetoothStateChange(mEnabled);
+ private void fireCallback(Callback cb) {
+ cb.onBluetoothStateChange(mEnabled, mConnecting);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index 9271e71..ac26da2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -47,7 +47,7 @@
int[] mTmpTwoArray = new int[2];
private final int mTouchSensitivityDelay;
- private final float mMaxAlpha = 0.95f;
+ private final float mMaxAlpha = 1f;
private SwipeHelper mSwipeHelper;
private EdgeSwipeHelper mEdgeSwipeHelper;
@@ -114,7 +114,7 @@
float pagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop();
float touchSlop = viewConfiguration.getScaledTouchSlop();
mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
- mSwipeHelper.setMaxAlpha(mMaxAlpha);
+ mSwipeHelper.setMaxSwipeProgress(mMaxAlpha);
mEdgeSwipeHelper = new EdgeSwipeHelper(touchSlop);
int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
@@ -184,7 +184,13 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Outline o = new Outline();
- o.setRect(0, 0, mContentHolder.getWidth(), mContentHolder.getHeight());
+
+ // Apply padding to shadow.
+ int outlineLeft = mContentHolder.getPaddingLeft();
+ int outlineTop = mContentHolder.getPaddingTop();
+ o.setRect(outlineLeft, outlineTop,
+ mContentHolder.getWidth() - outlineLeft - mContentHolder.getPaddingRight(),
+ mContentHolder.getHeight() - outlineTop - mContentHolder.getPaddingBottom());
mContentHolder.setOutline(o);
}
@@ -246,6 +252,12 @@
}
@Override
+ public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+ getBackground().setAlpha((int) (255 * swipeProgress));
+ return false;
+ }
+
+ @Override
public View getChildAtPosition(MotionEvent ev) {
return mContentHolder;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 58176b9..6892b85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -18,13 +18,10 @@
import android.content.Context;
import android.content.res.Configuration;
-
import android.graphics.Canvas;
import android.graphics.Paint;
-
import android.util.AttributeSet;
import android.util.Log;
-
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@@ -40,8 +37,8 @@
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.SpeedBumpView;
-import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
import com.android.systemui.statusbar.policy.ScrollAdapter;
+import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
import java.util.ArrayList;
@@ -121,6 +118,7 @@
private float mOverScrolledBottomPixels;
private OnChildLocationsChangedListener mListener;
+ private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
private boolean mNeedsAnimation;
private boolean mTopPaddingNeedsAnimation;
@@ -449,6 +447,11 @@
}
}
+ @Override
+ public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+ return false;
+ }
+
public void onBeginDrag(View v) {
setSwipingInProgress(true);
mAmbientState.onBeginDrag(v);
@@ -870,9 +873,24 @@
setOverScrolledPixels(amount / RUBBER_BAND_FACTOR, onTop);
mAmbientState.setOverScrollAmount(amount, onTop);
requestChildrenUpdate();
+ if (onTop) {
+ float scrollAmount = mOwnScrollY < 0 ? -mOwnScrollY : 0;
+ notifyOverscrollTopListener(scrollAmount + amount);
+ }
}
}
+ private void notifyOverscrollTopListener(float amount) {
+ if (mOverscrollTopChangedListener != null) {
+ mOverscrollTopChangedListener.onOverscrollTopChanged(amount);
+ }
+ }
+
+ public void setOverscrollTopChangedListener(
+ OnOverscrollTopChangedListener overscrollTopChangedListener) {
+ mOverscrollTopChangedListener = overscrollTopChangedListener;
+ }
+
public float getCurrentOverScrollAmount(boolean top) {
return mAmbientState.getOverScrollAmount(top);
}
@@ -908,6 +926,12 @@
onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
invalidateParentIfNeeded();
updateChildren();
+ float overScrollTop = getCurrentOverScrollAmount(true);
+ if (mOwnScrollY < 0) {
+ notifyOverscrollTopListener(-mOwnScrollY + overScrollTop);
+ } else {
+ notifyOverscrollTopListener(overScrollTop);
+ }
}
} else {
customScrollTo(scrollY);
@@ -1591,6 +1615,13 @@
public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
}
+ /**
+ * A listener that gets notified when the overscroll at the top has changed.
+ */
+ public interface OnOverscrollTopChangedListener {
+ public void onOverscrollTopChanged(float amount);
+ }
+
static class AnimationEvent {
static AnimationFilter[] FILTERS = new AnimationFilter[] {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index f41ab3a..2edd7d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -715,6 +715,9 @@
public void animateOverScrollToAmount(float targetAmount, final boolean onTop) {
final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
+ if (targetAmount == startOverScrollAmount) {
+ return;
+ }
cancelOverScrollAnimators(onTop);
ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
targetAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index cbfc641..898b46e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -16,6 +16,8 @@
package com.android.systemui.volume;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver;
@@ -42,12 +44,15 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
-import android.widget.FrameLayout;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
@@ -81,6 +86,7 @@
private static final int FREE_DELAY = 10000;
private static final int TIMEOUT_DELAY = 3000;
private static final int TIMEOUT_DELAY_EXPANDED = 10000;
+ private static final float ICON_PULSE_SCALE = 1.3f;
private static final int MSG_VOLUME_CHANGED = 0;
private static final int MSG_FREE_RESOURCES = 1;
@@ -105,6 +111,7 @@
protected final Context mContext;
private final AudioManager mAudioManager;
private final ZenModeController mZenController;
+ private final Interpolator mFastOutSlowInInterpolator;
private boolean mRingIsSilent;
private boolean mVoiceCapable;
private boolean mZenModeCapable;
@@ -220,6 +227,7 @@
ViewGroup group;
ImageView icon;
SeekBar seekbarView;
+ View seekbarContainer;
int iconRes;
int iconMuteRes;
}
@@ -273,6 +281,8 @@
mParent = parent;
mZenController = zenController;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext,
+ android.R.interpolator.fast_out_slow_in);
// For now, only show master volume if master volume is supported
final Resources res = context.getResources();
@@ -284,7 +294,6 @@
}
}
if (LOGD) Log.d(mTag, String.format("new VolumePanel hasParent=%s", parent != null));
- final int layoutId = com.android.systemui.R.layout.volume_panel;
if (parent == null) {
// dialog mode
mDialog = new Dialog(context) {
@@ -318,15 +327,7 @@
| LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| LayoutParams.FLAG_HARDWARE_ACCELERATED);
mDialog.setCanceledOnTouchOutside(true);
- // temporary workaround for no window shadows
- final FrameLayout layout = new FrameLayout(mContext);
- final int z = res.getDimensionPixelSize(com.android.systemui.R.dimen.volume_panel_z);
- layout.setPadding(z, z, z, z);
- final View v = LayoutInflater.from(mContext).inflate(layoutId, layout, false);
- v.setBackgroundResource(com.android.systemui.R.drawable.qs_panel_background);
- v.setElevation(z);
- layout.addView(v);
- mDialog.setContentView(layout);
+ mDialog.setContentView(com.android.systemui.R.layout.volume_dialog);
mDialog.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
@@ -336,6 +337,7 @@
});
mDialog.create();
+ // temporary workaround, until we support window-level shadows
mDialog.getWindow().setBackgroundDrawable(new ColorDrawable(0x00000000));
mView = window.findViewById(R.id.content);
@@ -350,7 +352,8 @@
} else {
// embedded mode
mDialog = null;
- mView = LayoutInflater.from(mContext).inflate(layoutId, parent, true);
+ mView = LayoutInflater.from(mContext).inflate(
+ com.android.systemui.R.layout.volume_panel, parent, true);
}
mPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.visible_panel);
mSliderPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.slider_panel);
@@ -460,6 +463,26 @@
sc.iconRes = streamRes.iconRes;
sc.iconMuteRes = streamRes.iconMuteRes;
sc.icon.setImageResource(sc.iconRes);
+ sc.icon.setClickable(isNotificationOrRing(streamType));
+ if (sc.icon.isClickable()) {
+ sc.icon.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ resetTimeout();
+ toggle(sc);
+ }
+ });
+ sc.icon.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ resetTimeout();
+ longToggle(sc);
+ return true;
+ }
+ });
+ }
+ sc.seekbarContainer =
+ sc.group.findViewById(com.android.systemui.R.id.seekbar_container);
sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar);
final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
@@ -470,6 +493,26 @@
}
}
+ private void toggle(StreamControl sc) {
+ if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL) {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
+ postVolumeChanged(sc.streamType, AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
+ } else {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND);
+ }
+ }
+
+ private void longToggle(StreamControl sc) {
+ if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+ postVolumeChanged(sc.streamType, AudioManager.FLAG_PLAY_SOUND);
+ } else {
+ mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
+ postVolumeChanged(sc.streamType, AudioManager.FLAG_SHOW_UI); // disable the slider
+ }
+ }
+
private void reorderSliders(int activeStreamType) {
mSliderPanel.removeAllViews();
@@ -493,21 +536,62 @@
// Force reloading the image resource
sc.icon.setImageDrawable(null);
sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
- if (((sc.streamType == AudioManager.STREAM_RING) ||
- (sc.streamType == AudioManager.STREAM_NOTIFICATION)) &&
+ if (isNotificationOrRing(sc.streamType) &&
mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
sc.icon.setImageResource(com.android.systemui.R.drawable.ic_ringer_vibrate);
}
+ updateSliderEnabled(sc, muted, false);
+ }
+
+ private void updateSliderEnabled(final StreamControl sc, boolean muted, boolean fixedVolume) {
+ final boolean wasEnabled = sc.seekbarView.isEnabled();
if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) {
// never disable touch interactions for remote playback, the muting is not tied to
// the state of the phone.
sc.seekbarView.setEnabled(true);
- } else if ((sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
+ } else if (fixedVolume ||
+ (sc.streamType != mAudioManager.getMasterStreamType() && muted) ||
(sConfirmSafeVolumeDialog != null)) {
sc.seekbarView.setEnabled(false);
+ } else if (isNotificationOrRing(sc.streamType)
+ && mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
+ sc.seekbarView.setEnabled(false);
} else {
sc.seekbarView.setEnabled(true);
}
+ // pulse the ringer icon when the disabled slider is touched in silent mode
+ if (sc.icon.isClickable() && wasEnabled != sc.seekbarView.isEnabled()) {
+ if (sc.seekbarView.isEnabled()) {
+ sc.seekbarContainer.setOnTouchListener(null);
+ } else {
+ sc.seekbarContainer.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ resetTimeout();
+ pulseIcon(sc.icon);
+ return false;
+ }
+ });
+ }
+ }
+ }
+
+ private void pulseIcon(final ImageView icon) {
+ if (icon.getScaleX() != 1) return; // already running
+ icon.animate().cancel();
+ icon.animate().scaleX(ICON_PULSE_SCALE).scaleY(ICON_PULSE_SCALE)
+ .setInterpolator(mFastOutSlowInInterpolator)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ icon.animate().scaleX(1).scaleY(1).setListener(null);
+ }
+ });
+ }
+
+ private static boolean isNotificationOrRing(int streamType) {
+ return streamType == AudioManager.STREAM_RING
+ || streamType == AudioManager.STREAM_NOTIFICATION;
}
public void setZenModePanelCallback(ZenModePanel.Callback callback) {
@@ -562,8 +646,7 @@
private void updateZenMode(boolean zen) {
if (mZenModeCapable) {
- final boolean show = mActiveStreamType == AudioManager.STREAM_NOTIFICATION
- || mActiveStreamType == AudioManager.STREAM_RING;
+ final boolean show = isNotificationOrRing(mActiveStreamType);
mExpandButton.setVisibility(show ? View.VISIBLE : View.GONE);
mExpandDivider.setVisibility(show ? View.VISIBLE : View.GONE);
mExpandButton.setImageResource(zen ? com.android.systemui.R.drawable.ic_vol_zen_on
@@ -576,7 +659,7 @@
public void postZenModeChanged(boolean zen) {
removeMessages(MSG_ZEN_MODE_CHANGED);
- obtainMessage(MSG_ZEN_MODE_CHANGED, zen ? 1 : 0).sendToTarget();
+ obtainMessage(MSG_ZEN_MODE_CHANGED, zen ? 1 : 0, 0).sendToTarget();
}
public void postVolumeChanged(int streamType, int flags) {
@@ -654,7 +737,7 @@
public void postLayoutDirection(int layoutDirection) {
removeMessages(MSG_LAYOUT_DIRECTION);
- obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection).sendToTarget();
+ obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection, 0).sendToTarget();
}
/**
@@ -790,15 +873,8 @@
}
sc.seekbarView.setProgress(index);
- if (((flags & AudioManager.FLAG_FIXED_VOLUME) != 0) ||
- (streamType != mAudioManager.getMasterStreamType() &&
- streamType != AudioService.STREAM_REMOTE_MUSIC &&
- isMuted(streamType)) ||
- sConfirmSafeVolumeDialog != null) {
- sc.seekbarView.setEnabled(false);
- } else {
- sc.seekbarView.setEnabled(true);
- }
+ updateSliderEnabled(sc, isMuted(streamType),
+ (flags & AudioManager.FLAG_FIXED_VOLUME) != 0);
}
if (!isShowing()) {
@@ -822,6 +898,11 @@
mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
sendMessageDelayed(obtainMessage(MSG_VIBRATE), VIBRATE_DELAY);
}
+
+ // Pulse the slider icon if an adjustment was suppressed due to silent mode.
+ if (sc != null && (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
+ pulseIcon(sc.icon);
+ }
}
private boolean isShowing() {
@@ -1173,7 +1254,7 @@
private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
public void onZenChanged(boolean zen) {
- updateZenMode(zen);
+ postZenModeChanged(zen);
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 77d267e..c338563 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -45,6 +45,7 @@
private static final int[] MINUTES = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 };
public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS);
+ private final Context mContext;
private final LayoutInflater mInflater;
private final HashSet<RadioButton> mRadioButtons = new HashSet<RadioButton>();
private final H mHandler = new H();
@@ -56,6 +57,7 @@
public ZenModePanel(Context context, AttributeSet attrs) {
super(context, attrs);
+ mContext = context;
mInflater = LayoutInflater.from(new ContextThemeWrapper(context, R.style.QSWhiteTheme));
}
diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java
index 673ce0b..0c16b78 100644
--- a/policy/src/com/android/internal/policy/impl/GlobalActions.java
+++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java
@@ -35,6 +35,7 @@
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.ConnectivityManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -51,6 +52,7 @@
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import android.util.TypedValue;
@@ -185,7 +187,8 @@
// If we only have 1 item and it's a simple press action, just do this action.
if (mAdapter.getCount() == 1
- && mAdapter.getItem(0) instanceof SinglePressAction) {
+ && mAdapter.getItem(0) instanceof SinglePressAction
+ && !(mAdapter.getItem(0) instanceof LongPressAction)) {
((SinglePressAction) mAdapter.getItem(0)).onPress();
} else {
WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
@@ -262,7 +265,7 @@
continue;
}
if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
- mItems.add(getPowerAction());
+ mItems.add(new PowerAction());
} else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
mItems.add(mAirplaneModeOn);
} else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)
@@ -300,7 +303,11 @@
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
long id) {
- return mAdapter.getItem(position).onLongPress();
+ final Action action = mAdapter.getItem(position);
+ if (action instanceof LongPressAction) {
+ return ((LongPressAction) action).onLongPress();
+ }
+ return false;
}
});
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
@@ -310,34 +317,38 @@
return dialog;
}
- private Action getPowerAction() {
- return new SinglePressAction(
- com.android.internal.R.drawable.ic_lock_power_off,
- R.string.global_action_power_off) {
+ private final class PowerAction extends SinglePressAction implements LongPressAction {
+ private PowerAction() {
+ super(com.android.internal.R.drawable.ic_lock_power_off,
+ R.string.global_action_power_off);
+ }
- public void onPress() {
- // shutdown by making sure radio and power are handled accordingly.
- mWindowManagerFuncs.shutdown(true);
- }
+ @Override
+ public boolean onLongPress() {
+ mWindowManagerFuncs.rebootSafeMode(true);
+ return true;
+ }
- public boolean onLongPress() {
- mWindowManagerFuncs.rebootSafeMode(true);
- return true;
- }
+ @Override
+ public boolean showDuringKeyguard() {
+ return true;
+ }
- public boolean showDuringKeyguard() {
- return true;
- }
+ @Override
+ public boolean showBeforeProvisioning() {
+ return true;
+ }
- public boolean showBeforeProvisioning() {
- return true;
- }
- };
+ @Override
+ public void onPress() {
+ // shutdown by making sure radio and power are handled accordingly.
+ mWindowManagerFuncs.shutdown(false /* confirm */);
+ }
}
private Action getBugReportAction() {
- return new SinglePressAction(com.android.internal.R.drawable.stat_sys_adb,
- R.string.global_action_bug_report) {
+ return new SinglePressAction(com.android.internal.R.drawable.ic_lock_bugreport,
+ R.string.bugreport_title) {
public void onPress() {
AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
@@ -367,10 +378,6 @@
dialog.show();
}
- public boolean onLongPress() {
- return false;
- }
-
public boolean showDuringKeyguard() {
return true;
}
@@ -378,6 +385,14 @@
public boolean showBeforeProvisioning() {
return false;
}
+
+ @Override
+ public String getStatus() {
+ return mContext.getString(
+ com.android.internal.R.string.bugreport_status,
+ Build.VERSION.RELEASE,
+ Build.ID);
+ }
};
}
@@ -393,11 +408,6 @@
}
@Override
- public boolean onLongPress() {
- return false;
- }
-
- @Override
public boolean showDuringKeyguard() {
return true;
}
@@ -583,8 +593,6 @@
void onPress();
- public boolean onLongPress();
-
/**
* @return whether this action should appear in the dialog when the keygaurd
* is showing.
@@ -601,6 +609,13 @@
}
/**
+ * An action that also supports long press.
+ */
+ private interface LongPressAction extends Action {
+ boolean onLongPress();
+ }
+
+ /**
* A single press action maintains no state, just responds to a press
* and takes an action.
*/
@@ -635,12 +650,12 @@
return true;
}
- abstract public void onPress();
-
- public boolean onLongPress() {
- return false;
+ public String getStatus() {
+ return null;
}
+ abstract public void onPress();
+
public View create(
Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
View v = inflater.inflate(R.layout.global_actions_item, parent, false);
@@ -648,7 +663,13 @@
ImageView icon = (ImageView) v.findViewById(R.id.icon);
TextView messageView = (TextView) v.findViewById(R.id.message);
- v.findViewById(R.id.status).setVisibility(View.GONE);
+ TextView statusView = (TextView) v.findViewById(R.id.status);
+ final String status = getStatus();
+ if (!TextUtils.isEmpty(status)) {
+ statusView.setText(status);
+ } else {
+ statusView.setVisibility(View.GONE);
+ }
if (mIcon != null) {
icon.setImageDrawable(mIcon);
icon.setScaleType(ScaleType.CENTER_CROP);
@@ -769,10 +790,6 @@
changeStateFromPress(nowOn);
}
- public boolean onLongPress() {
- return false;
- }
-
public boolean isEnabled() {
return !mState.inTransition();
}
@@ -862,10 +879,6 @@
public void onPress() {
}
- public boolean onLongPress() {
- return false;
- }
-
public boolean showDuringKeyguard() {
return true;
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index c483836..30282dd 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -1914,9 +1914,8 @@
@Override
public Animation createForceHideEnterAnimation(boolean onWallpaper) {
- return AnimationUtils.loadAnimation(mContext, onWallpaper
- ? com.android.internal.R.anim.lock_screen_wallpaper_behind_enter
- : com.android.internal.R.anim.lock_screen_behind_enter);
+ return AnimationUtils.loadAnimation(mContext,
+ com.android.internal.R.anim.lock_screen_behind_enter);
}
private static void awakenDreams() {
@@ -4571,14 +4570,9 @@
}
@Override
- public void startKeyguardExitAnimation(final long fadeoutDuration) {
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
if (mKeyguardDelegate != null) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mKeyguardDelegate.startKeyguardExitAnimation(fadeoutDuration);
- }
- });
+ mKeyguardDelegate.startKeyguardExitAnimation(startTime, fadeoutDuration);
}
}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
index faf7020..63a5850 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceDelegate.java
@@ -274,9 +274,9 @@
mKeyguardState.currentUser = newUserId;
}
- public void startKeyguardExitAnimation(long fadeoutDuration) {
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
if (mKeyguardService != null) {
- mKeyguardService.startKeyguardExitAnimation(fadeoutDuration);
+ mKeyguardService.startKeyguardExitAnimation(startTime, fadeoutDuration);
}
}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
index f236ce7..5096bd3 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardServiceWrapper.java
@@ -190,9 +190,9 @@
}
}
- public void startKeyguardExitAnimation(long fadeoutDuration) {
+ public void startKeyguardExitAnimation(long startTime, long fadeoutDuration) {
try {
- mService.startKeyguardExitAnimation(fadeoutDuration);
+ mService.startKeyguardExitAnimation(startTime, fadeoutDuration);
} catch (RemoteException e) {
Slog.w(TAG , "Remote Exception", e);
}
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index d5f045e..d31fb60 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -2372,6 +2372,18 @@
}
}
+ voldPath = maybeTranslatePathForVold(appPath,
+ userEnv.buildExternalStorageAppMediaDirs(callingPkg),
+ userEnv.buildExternalStorageAppMediaDirsForVold(callingPkg));
+ if (voldPath != null) {
+ try {
+ mConnector.execute("volume", "mkdirs", voldPath);
+ return 0;
+ } catch (NativeDaemonConnectorException e) {
+ return e.getCode();
+ }
+ }
+
throw new SecurityException("Invalid mkdirs path: " + appPath);
}
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
new file mode 100644
index 0000000..579f89f
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.util.Slog;
+
+import com.android.internal.util.Preconditions;
+import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Feature action that handles device discovery sequences.
+ * Device discovery is launched when TV device is woken from "Standby" state
+ * or enabled "Control for Hdmi" from disabled state.
+ *
+ * <p>Device discovery goes through the following steps.
+ * <ol>
+ * <li>Poll all non-local devices by sending <Polling Message>
+ * <li>Gather "Physical address" and "device type" of all acknowledged devices
+ * <li>Gather "OSD (display) name" of all acknowledge devices
+ * <li>Gather "Vendor id" of all acknowledge devices
+ * </ol>
+ */
+final class DeviceDiscoveryAction extends FeatureAction {
+ private static final String TAG = "DeviceDiscoveryAction";
+
+ // State in which the action is waiting for device polling.
+ private static final int STATE_WAITING_FOR_DEVICE_POLLING = 1;
+ // State in which the action is waiting for gathering physical address of non-local devices.
+ private static final int STATE_WAITING_FOR_PHYSICAL_ADDRESS = 2;
+ // State in which the action is waiting for gathering osd name of non-local devices.
+ private static final int STATE_WAITING_FOR_OSD_NAME = 3;
+ // State in which the action is waiting for gathering vendor id of non-local devices.
+ private static final int STATE_WAITING_FOR_VENDOR_ID = 4;
+
+ private static final int DEVICE_POLLING_RETRY = 1;
+
+ // TODO: Move this to common place
+ private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
+
+ /**
+ * Interface used to report result of device discovery.
+ */
+ interface DeviceDiscoveryCallback {
+ /**
+ * Called when device discovery is done.
+ *
+ * @param deviceInfos a list of all non-local devices. It can be empty list.
+ */
+ void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos);
+ }
+
+ // An internal container used to keep track of device information during
+ // this action.
+ private static final class DeviceInfo {
+ private final int mLogicalAddress;
+
+ private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+ private int mVendorId = HdmiCec.UNKNOWN_VENDOR_ID;
+ private String mDisplayName = "";
+ private int mDeviceType = HdmiCec.DEVICE_INACTIVE;
+
+ private DeviceInfo(int logicalAddress) {
+ mLogicalAddress = logicalAddress;
+ }
+
+ private HdmiCecDeviceInfo toHdmiCecDeviceInfo() {
+ return new HdmiCecDeviceInfo(mLogicalAddress, mPhysicalAddress, mDeviceType, mVendorId,
+ mDisplayName);
+ }
+ }
+
+ private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
+ private final DeviceDiscoveryCallback mCallback;
+ private int mProcessedDeviceCount = 0;
+
+ /**
+ * @Constructor
+ *
+ * @param service
+ * @param sourceAddress
+ */
+ DeviceDiscoveryAction(HdmiControlService service, int sourceAddress,
+ DeviceDiscoveryCallback callback) {
+ super(service, sourceAddress);
+ mCallback = Preconditions.checkNotNull(callback);
+ }
+
+ @Override
+ boolean start() {
+ mDevices.clear();
+ mState = STATE_WAITING_FOR_DEVICE_POLLING;
+
+ mService.pollDevices(new DevicePollingCallback() {
+ @Override
+ public void onPollingFinished(List<Integer> ackedAddress) {
+ if (ackedAddress.isEmpty()) {
+ Slog.v(TAG, "No device is detected.");
+ finish();
+ return;
+ }
+
+ Slog.v(TAG, "Device detected: " + ackedAddress);
+ allocateDevices(ackedAddress);
+ startPhysicalAddressStage();
+ }
+ }, DEVICE_POLLING_RETRY);
+ return true;
+ }
+
+ private void allocateDevices(List<Integer> addresses) {
+ for (Integer i : addresses) {
+ DeviceInfo info = new DeviceInfo(i);
+ mDevices.add(info);
+ }
+ }
+
+ private void startPhysicalAddressStage() {
+ Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
+ mProcessedDeviceCount = 0;
+ mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS;
+
+ checkAndProceedStage();
+ }
+
+ private boolean verifyValidLogicalAddress(int address) {
+ return address >= HdmiCec.ADDR_TV && address < HdmiCec.ADDR_UNREGISTERED;
+ }
+
+ private void queryPhysicalAddress(int address) {
+ if (!verifyValidLogicalAddress(address)) {
+ checkAndProceedStage();
+ return;
+ }
+
+ mActionTimer.clearTimerMessage();
+ sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(mSourceAddress, address));
+ addTimer(mState, TIMEOUT_MS);
+ }
+
+ private void startOsdNameStage() {
+ Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
+ mProcessedDeviceCount = 0;
+ mState = STATE_WAITING_FOR_OSD_NAME;
+
+ checkAndProceedStage();
+ }
+
+ private void queryOsdName(int address) {
+ if (!verifyValidLogicalAddress(address)) {
+ checkAndProceedStage();
+ return;
+ }
+
+ mActionTimer.clearTimerMessage();
+ sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(mSourceAddress, address));
+ addTimer(mState, TIMEOUT_MS);
+ }
+
+ private void startVendorIdStage() {
+ Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size());
+
+ mProcessedDeviceCount = 0;
+ mState = STATE_WAITING_FOR_VENDOR_ID;
+
+ checkAndProceedStage();
+ }
+
+ private void queryVendorId(int address) {
+ if (!verifyValidLogicalAddress(address)) {
+ checkAndProceedStage();
+ return;
+ }
+
+ mActionTimer.clearTimerMessage();
+ sendCommand(HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(mSourceAddress, address));
+ addTimer(mState, TIMEOUT_MS);
+ }
+
+ @Override
+ boolean processCommand(HdmiCecMessage cmd) {
+ switch (mState) {
+ case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
+ if (cmd.getOpcode() == HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
+ handleReportPhysicalAddress(cmd);
+ return true;
+ }
+ return false;
+ case STATE_WAITING_FOR_OSD_NAME:
+ if (cmd.getOpcode() == HdmiCec.MESSAGE_SET_OSD_NAME) {
+ handleSetOsdName(cmd);
+ return true;
+ }
+ return false;
+ case STATE_WAITING_FOR_VENDOR_ID:
+ if (cmd.getOpcode() == HdmiCec.MESSAGE_DEVICE_VENDOR_ID) {
+ handleVendorId(cmd);
+ return true;
+ }
+ return false;
+ case STATE_WAITING_FOR_DEVICE_POLLING:
+ // Fall through.
+ default:
+ return false;
+ }
+ }
+
+ private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
+ Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
+
+ DeviceInfo current = mDevices.get(mProcessedDeviceCount);
+ if (current.mLogicalAddress != cmd.getSource()) {
+ Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
+ cmd.getSource());
+ return;
+ }
+
+ byte params[] = cmd.getParams();
+ if (params.length == 3) {
+ current.mPhysicalAddress = ((params[0] & 0xFF) << 8) | (params[1] & 0xFF);
+ current.mDeviceType = params[2] & 0xFF;
+
+ increaseProcessedDeviceCount();
+ checkAndProceedStage();
+ } else {
+ // Physical address is a critical element in device info.
+ // If failed, remove device from device list and proceed to the next device.
+ removeDevice(mProcessedDeviceCount);
+ checkAndProceedStage();
+ }
+ }
+
+ private void handleSetOsdName(HdmiCecMessage cmd) {
+ Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
+
+ DeviceInfo current = mDevices.get(mProcessedDeviceCount);
+ if (current.mLogicalAddress != cmd.getSource()) {
+ Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
+ cmd.getSource());
+ return;
+ }
+
+ String displayName = null;
+ try {
+ displayName = new String(cmd.getParams(), "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
+ // If failed to get display name, use the default name of device.
+ displayName = HdmiCec.getDefaultDeviceName(current.mLogicalAddress);
+ }
+ current.mDisplayName = displayName;
+ increaseProcessedDeviceCount();
+ checkAndProceedStage();
+ }
+
+ private void handleVendorId(HdmiCecMessage cmd) {
+ Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
+
+ DeviceInfo current = mDevices.get(mProcessedDeviceCount);
+ if (current.mLogicalAddress != cmd.getSource()) {
+ Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
+ cmd.getSource());
+ return;
+ }
+
+ byte[] params = cmd.getParams();
+ if (params.length == 3) {
+ int vendorId = ((params[0] & 0xFF) << 16)
+ | ((params[1] & 0xFF) << 8)
+ | (params[2] & 0xFF);
+ current.mVendorId = vendorId;
+ } else {
+ Slog.w(TAG, "Invalid vendor id: " + cmd.toString());
+ }
+ increaseProcessedDeviceCount();
+ checkAndProceedStage();
+ }
+
+ private void increaseProcessedDeviceCount() {
+ mProcessedDeviceCount++;
+ }
+
+ private void removeDevice(int index) {
+ mDevices.remove(index);
+ }
+
+ private void wrapUpAndFinish() {
+ Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------");
+ ArrayList<HdmiCecDeviceInfo> result = new ArrayList<>();
+ for (DeviceInfo info : mDevices) {
+ HdmiCecDeviceInfo cecDeviceInfo = info.toHdmiCecDeviceInfo();
+ Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo);
+ result.add(cecDeviceInfo);
+ }
+ Slog.v(TAG, "--------------------------------------------");
+ mCallback.onDeviceDiscoveryDone(result);
+ finish();
+ }
+
+ private void checkAndProceedStage() {
+ if (mDevices.isEmpty()) {
+ wrapUpAndFinish();
+ return;
+ }
+
+ // If finished current stage, move on to next stage.
+ if (mProcessedDeviceCount == mDevices.size()) {
+ mProcessedDeviceCount = 0;
+ switch (mState) {
+ case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
+ startOsdNameStage();
+ return;
+ case STATE_WAITING_FOR_OSD_NAME:
+ startVendorIdStage();
+ return;
+ case STATE_WAITING_FOR_VENDOR_ID:
+ wrapUpAndFinish();
+ return;
+ default:
+ return;
+ }
+ } else {
+ int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
+ switch (mState) {
+ case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
+ queryPhysicalAddress(address);
+ return;
+ case STATE_WAITING_FOR_OSD_NAME:
+ queryOsdName(address);
+ return;
+ case STATE_WAITING_FOR_VENDOR_ID:
+ queryVendorId(address);
+ default:
+ return;
+ }
+ }
+ }
+
+ @Override
+ void handleTimerEvent(int state) {
+ if (mState == STATE_NONE || mState != state) {
+ return;
+ }
+
+ Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
+ removeDevice(mProcessedDeviceCount);
+ checkAndProceedStage();
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
index 1bc278d..0ba7773 100644
--- a/services/core/java/com/android/server/hdmi/FeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/FeatureAction.java
@@ -133,7 +133,8 @@
@Override
public void sendTimerMessage(int state, long delayMillis) {
- sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state), delayMillis);
+ // The third argument(0) is not used.
+ sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state, 0), delayMillis);
}
@Override
@@ -164,8 +165,13 @@
mActionTimer.sendTimerMessage(state, delayMillis);
}
- protected final boolean sendCommand(HdmiCecMessage cmd) {
- return mService.sendCecCommand(cmd);
+ protected final void sendCommand(HdmiCecMessage cmd) {
+ mService.sendCecCommand(cmd);
+ }
+
+ protected final void sendCommand(HdmiCecMessage cmd,
+ HdmiControlService.SendMessageCallback callback) {
+ mService.sendCecCommand(cmd, callback);
}
/**
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index f99a01d..d0b716d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -21,15 +21,15 @@
import android.hardware.hdmi.HdmiCecMessage;
import android.os.Handler;
import android.os.Looper;
-import android.os.Message;
+import android.os.MessageQueue;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseIntArray;
+
+import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
import libcore.util.EmptyArray;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -37,6 +37,9 @@
* and pass it to CEC HAL so that it sends message to other device. For incoming
* message it translates the message and delegates it to proper module.
*
+ * <p>It should be careful to access member variables on IO thread because
+ * it can be accessed from system thread as well.
+ *
* <p>It can be created only by {@link HdmiCecController#create}
*
* <p>Declared as package-private, accessed by {@link HdmiControlService} only.
@@ -58,8 +61,7 @@
private static final int NUM_LOGICAL_ADDRESS = 16;
- // TODO: define other constants for errors.
- private static final int ERROR_SUCCESS = 0;
+ private static final int RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION = 3;
// Handler instance to process synchronous I/O (mainly send) message.
private Handler mIoHandler;
@@ -70,22 +72,19 @@
// Stores the pointer to the native implementation of the service that
// interacts with HAL.
- private long mNativePtr;
+ private volatile long mNativePtr;
private HdmiControlService mService;
- // Map-like container of all cec devices. A logical address of device is
- // used as key of container.
- private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos =
- new SparseArray<HdmiCecDeviceInfo>();
- // Set-like container for all local devices' logical address.
- // Key and value are same.
- private final SparseIntArray mLocalAddresses = new SparseIntArray();
+ // Map-like container of all cec devices including local ones.
+ // A logical address of device is used as key of container.
+ private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = new SparseArray<>();
+
+ // Stores the local CEC devices in the system. Device type is used for key.
+ private final SparseArray<HdmiCecLocalDevice> mLocalDevices = new SparseArray<>();
// Private constructor. Use HdmiCecController.create().
private HdmiCecController() {
- // TODO: Consider restoring the local device addresses from persistent storage
- // to allocate the same addresses again if possible.
}
/**
@@ -99,53 +98,43 @@
* returns {@code null}.
*/
static HdmiCecController create(HdmiControlService service) {
- HdmiCecController handler = new HdmiCecController();
- long nativePtr = nativeInit(handler);
+ HdmiCecController controller = new HdmiCecController();
+ long nativePtr = nativeInit(controller, service.getServiceLooper().getQueue());
if (nativePtr == 0L) {
- handler = null;
+ controller = null;
return null;
}
- handler.init(service, nativePtr);
- return handler;
+ controller.init(service, nativePtr);
+ return controller;
+ }
+
+ private void init(HdmiControlService service, long nativePtr) {
+ mService = service;
+ mIoHandler = new Handler(service.getServiceLooper());
+ mControlHandler = new Handler(service.getServiceLooper());
+ mNativePtr = nativePtr;
}
/**
- * Initialize {@link #mLocalAddresses} by allocating logical addresses for each hosted type.
+ * Perform initialization for each hosted device.
*
- * @param deviceTypes local device types
+ * @param deviceTypes array of device types
*/
- void initializeLocalDevices(int[] deviceTypes) {
- for (int deviceType : deviceTypes) {
- int preferred = getPreferredAddress(deviceType);
- allocateLogicalAddress(deviceType, preferred, new AllocateLogicalAddressCallback() {
- @Override
- public void onAllocated(int deviceType, int logicalAddress) {
- addLogicalAddress(logicalAddress);
- }
- });
- }
- }
-
- /**
- * Get the preferred address for a given type.
- *
- * @param deviceType logical device type to get the address for
- * @return preferred address; {@link HdmiCec#ADDR_UNREGISTERED} if not available.
- */
- private int getPreferredAddress(int deviceType) {
- // Uses the data restored from persistent memory at boot up if they are available.
- // Otherwise we return UNREGISTERED indicating there is no preferred address.
- // Note that for address SPECIFIC_USE(14), HdmiCec.getTypeFromAddress() returns DEVICE_TV,
- // meaning that we do not support device type video processor yet.
- for (int i = 0; i < mLocalAddresses.size(); ++i) {
- int address = mLocalAddresses.keyAt(i);
- int type = HdmiCec.getTypeFromAddress(address);
- if (type == deviceType) {
- return address;
+ void initializeLocalDevices(int[] deviceTypes,
+ HdmiCecLocalDevice.AddressAllocationCallback callback) {
+ assertRunOnServiceThread();
+ for (int type : deviceTypes) {
+ HdmiCecLocalDevice device = HdmiCecLocalDevice.create(this, type, callback);
+ if (device == null) {
+ continue;
}
+ // TODO: Consider restoring the local device addresses from persistent storage
+ // to allocate the same addresses again if possible.
+ device.setPreferredAddress(HdmiCec.ADDR_UNREGISTERED);
+ mLocalDevices.put(type, device);
+ device.init();
}
- return HdmiCec.ADDR_UNREGISTERED;
}
/**
@@ -176,13 +165,55 @@
* Otherwise, scan address will start from {@code preferredAddress}
* @param callback callback interface to report allocated logical address to caller
*/
- void allocateLogicalAddress(int deviceType, int preferredAddress,
- AllocateLogicalAddressCallback callback) {
- Message msg = mIoHandler.obtainMessage(MSG_ALLOCATE_LOGICAL_ADDRESS);
- msg.arg1 = deviceType;
- msg.arg2 = preferredAddress;
- msg.obj = callback;
- mIoHandler.sendMessage(msg);
+ void allocateLogicalAddress(final int deviceType, final int preferredAddress,
+ final AllocateLogicalAddressCallback callback) {
+ assertRunOnServiceThread();
+
+ runOnIoThread(new Runnable() {
+ @Override
+ public void run() {
+ handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
+ }
+ });
+ }
+
+ private void handleAllocateLogicalAddress(final int deviceType, int preferredAddress,
+ final AllocateLogicalAddressCallback callback) {
+ assertRunOnIoThread();
+ int startAddress = preferredAddress;
+ // If preferred address is "unregistered", start address will be the smallest
+ // address matched with the given device type.
+ if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) {
+ for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
+ if (deviceType == HdmiCec.getTypeFromAddress(i)) {
+ startAddress = i;
+ break;
+ }
+ }
+ }
+
+ int logicalAddress = HdmiCec.ADDR_UNREGISTERED;
+ // Iterates all possible addresses which has the same device type.
+ for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
+ int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
+ if (curAddress != HdmiCec.ADDR_UNREGISTERED
+ && deviceType == HdmiCec.getTypeFromAddress(curAddress)) {
+ if (!sendPollMessage(curAddress, RETRY_COUNT_FOR_LOGICAL_ADDRESS_ALLOCATION)) {
+ logicalAddress = curAddress;
+ break;
+ }
+ }
+ }
+
+ final int assignedAddress = logicalAddress;
+ if (callback != null) {
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ callback.onAllocated(deviceType, assignedAddress);
+ }
+ });
+ }
}
private static byte[] buildBody(int opcode, byte[] params) {
@@ -192,101 +223,6 @@
return body;
}
- private final class IoHandler extends Handler {
- private IoHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SEND_CEC_COMMAND:
- HdmiCecMessage cecMessage = (HdmiCecMessage) msg.obj;
- byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
- nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
- cecMessage.getDestination(), body);
- break;
- case MSG_ALLOCATE_LOGICAL_ADDRESS:
- int deviceType = msg.arg1;
- int preferredAddress = msg.arg2;
- AllocateLogicalAddressCallback callback =
- (AllocateLogicalAddressCallback) msg.obj;
- handleAllocateLogicalAddress(deviceType, preferredAddress, callback);
- break;
- default:
- Slog.w(TAG, "Unsupported CEC Io request:" + msg.what);
- break;
- }
- }
-
- private void handleAllocateLogicalAddress(int deviceType, int preferredAddress,
- AllocateLogicalAddressCallback callback) {
- int startAddress = preferredAddress;
- // If preferred address is "unregistered", start_index will be the smallest
- // address matched with the given device type.
- if (preferredAddress == HdmiCec.ADDR_UNREGISTERED) {
- for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
- if (deviceType == HdmiCec.getTypeFromAddress(i)) {
- startAddress = i;
- break;
- }
- }
- }
-
- int logcialAddress = HdmiCec.ADDR_UNREGISTERED;
- // Iterates all possible addresses which has the same device type.
- for (int i = 0; i < NUM_LOGICAL_ADDRESS; ++i) {
- int curAddress = (startAddress + i) % NUM_LOGICAL_ADDRESS;
- if (curAddress != HdmiCec.ADDR_UNREGISTERED
- && deviceType == HdmiCec.getTypeFromAddress(i)) {
- // <Polling Message> is a message which has empty body and
- // uses same address for both source and destination address.
- // If sending <Polling Message> failed (NAK), it becomes
- // new logical address for the device because no device uses
- // it as logical address of the device.
- int error = nativeSendCecCommand(mNativePtr, curAddress, curAddress,
- EMPTY_BODY);
- if (error != ERROR_SUCCESS) {
- logcialAddress = curAddress;
- break;
- }
- }
- }
-
- Message msg = mControlHandler.obtainMessage(MSG_REPORT_LOGICAL_ADDRESS);
- msg.arg1 = deviceType;
- msg.arg2 = logcialAddress;
- msg.obj = callback;
- mControlHandler.sendMessage(msg);
- }
- }
-
- private final class ControlHandler extends Handler {
- private ControlHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_RECEIVE_CEC_COMMAND:
- // TODO: delegate it to HdmiControl service.
- onReceiveCommand((HdmiCecMessage) msg.obj);
- break;
- case MSG_REPORT_LOGICAL_ADDRESS:
- int deviceType = msg.arg1;
- int logicalAddress = msg.arg2;
- AllocateLogicalAddressCallback callback =
- (AllocateLogicalAddressCallback) msg.obj;
- callback.onAllocated(deviceType, logicalAddress);
- break;
- default:
- Slog.i(TAG, "Unsupported message type:" + msg.what);
- break;
- }
- }
- }
-
/**
* Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same
* logical address as new device info's.
@@ -298,6 +234,7 @@
* that has the same logical address as new one has.
*/
HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) {
+ assertRunOnServiceThread();
HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress());
if (oldDeviceInfo != null) {
removeDeviceInfo(deviceInfo.getLogicalAddress());
@@ -316,6 +253,7 @@
* @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null}
*/
HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) {
+ assertRunOnServiceThread();
HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress);
if (deviceInfo != null) {
mDeviceInfos.remove(logicalAddress);
@@ -324,17 +262,23 @@
}
/**
- * Return a list of all {@HdmiCecDeviceInfo}.
+ * Clear all device info.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ */
+ void clearDeviceInfoList() {
+ assertRunOnServiceThread();
+ mDeviceInfos.clear();
+ }
+
+ /**
+ * Return a list of all {@link HdmiCecDeviceInfo}.
*
* <p>Declared as package-private. accessed by {@link HdmiControlService} only.
*/
List<HdmiCecDeviceInfo> getDeviceInfoList() {
- List<HdmiCecDeviceInfo> deviceInfoList = new ArrayList<HdmiCecDeviceInfo>(
- mDeviceInfos.size());
- for (int i = 0; i < mDeviceInfos.size(); ++i) {
- deviceInfoList.add(mDeviceInfos.valueAt(i));
- }
- return deviceInfoList;
+ assertRunOnServiceThread();
+ return sparseArrayToList(mDeviceInfos);
}
/**
@@ -347,10 +291,22 @@
* Returns null if no logical address matched
*/
HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
+ assertRunOnServiceThread();
return mDeviceInfos.get(logicalAddress);
}
/**
+ * Return the locally hosted logical device of a given type.
+ *
+ * @param deviceType logical device type
+ * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available;
+ * otherwise null.
+ */
+ HdmiCecLocalDevice getLocalDevice(int deviceType) {
+ return mLocalDevices.get(deviceType);
+ }
+
+ /**
* Add a new logical address to the device. Device's HW should be notified
* when a new logical address is assigned to a device, so that it can accept
* a command having available destinations.
@@ -361,8 +317,8 @@
* @return 0 on success. Otherwise, returns negative value
*/
int addLogicalAddress(int newLogicalAddress) {
+ assertRunOnServiceThread();
if (HdmiCec.isValidAddress(newLogicalAddress)) {
- mLocalAddresses.put(newLogicalAddress, newLogicalAddress);
return nativeAddLogicalAddress(mNativePtr, newLogicalAddress);
} else {
return -1;
@@ -375,9 +331,12 @@
* <p>Declared as package-private. accessed by {@link HdmiControlService} only.
*/
void clearLogicalAddress() {
+ assertRunOnServiceThread();
// TODO: consider to backup logical address so that new logical address
// allocation can use it as preferred address.
- mLocalAddresses.clear();
+ for (int i = 0; i < mLocalDevices.size(); ++i) {
+ mLocalDevices.valueAt(i).clearAddress();
+ }
nativeClearLogicalAddress(mNativePtr);
}
@@ -390,6 +349,7 @@
* is between 0x0000 and 0xFFFF. If failed it returns -1
*/
int getPhysicalAddress() {
+ assertRunOnServiceThread();
return nativeGetPhysicalAddress(mNativePtr);
}
@@ -399,6 +359,7 @@
* <p>Declared as package-private. accessed by {@link HdmiControlService} only.
*/
int getVersion() {
+ assertRunOnServiceThread();
return nativeGetVersion(mNativePtr);
}
@@ -408,57 +369,180 @@
* <p>Declared as package-private. accessed by {@link HdmiControlService} only.
*/
int getVendorId() {
+ assertRunOnServiceThread();
return nativeGetVendorId(mNativePtr);
}
- private void init(HdmiControlService service, long nativePtr) {
- mService = service;
- mIoHandler = new IoHandler(service.getServiceLooper());
- mControlHandler = new ControlHandler(service.getServiceLooper());
- mNativePtr = nativePtr;
+ /**
+ * Poll all remote devices. It sends <Polling Message> to all remote
+ * devices.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ *
+ * @param callback an interface used to get a list of all remote devices' address
+ * @param retryCount the number of retry used to send polling message to remote devices
+ */
+ void pollDevices(DevicePollingCallback callback, int retryCount) {
+ assertRunOnServiceThread();
+ // Extract polling candidates. No need to poll against local devices.
+ ArrayList<Integer> pollingCandidates = new ArrayList<>();
+ for (int i = HdmiCec.ADDR_SPECIFIC_USE; i >= HdmiCec.ADDR_TV; --i) {
+ if (!isAllocatedLocalDeviceAddress(i)) {
+ pollingCandidates.add(i);
+ }
+ }
+
+ runDevicePolling(pollingCandidates, retryCount, callback);
+ }
+
+ /**
+ * Return a list of all {@link HdmiCecLocalDevice}s.
+ *
+ * <p>Declared as package-private. accessed by {@link HdmiControlService} only.
+ */
+ List<HdmiCecLocalDevice> getLocalDeviceList() {
+ assertRunOnServiceThread();
+ return sparseArrayToList(mLocalDevices);
+ }
+
+ private static <T> List<T> sparseArrayToList(SparseArray<T> array) {
+ ArrayList<T> list = new ArrayList<>();
+ for (int i = 0; i < array.size(); ++i) {
+ list.add(array.valueAt(i));
+ }
+ return list;
+ }
+
+ private boolean isAllocatedLocalDeviceAddress(int address) {
+ for (int i = 0; i < mLocalDevices.size(); ++i) {
+ if (mLocalDevices.valueAt(i).isAddressOf(address)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void runDevicePolling(final List<Integer> candidates, final int retryCount,
+ final DevicePollingCallback callback) {
+ assertRunOnServiceThread();
+ runOnIoThread(new Runnable() {
+ @Override
+ public void run() {
+ final ArrayList<Integer> allocated = new ArrayList<>();
+ for (Integer address : candidates) {
+ if (sendPollMessage(address, retryCount)) {
+ allocated.add(address);
+ }
+ }
+ if (callback != null) {
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ callback.onPollingFinished(allocated);
+ }
+ });
+ }
+ }
+ });
+ }
+
+ private boolean sendPollMessage(int address, int retryCount) {
+ assertRunOnIoThread();
+ for (int i = 0; i < retryCount; ++i) {
+ // <Polling Message> is a message which has empty body and
+ // uses same address for both source and destination address.
+ // If sending <Polling Message> failed (NAK), it becomes
+ // new logical address for the device because no device uses
+ // it as logical address of the device.
+ if (nativeSendCecCommand(mNativePtr, address, address, EMPTY_BODY)
+ == HdmiControlService.SEND_RESULT_SUCCESS) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void assertRunOnIoThread() {
+ if (Looper.myLooper() != mIoHandler.getLooper()) {
+ throw new IllegalStateException("Should run on io thread.");
+ }
+ }
+
+ private void assertRunOnServiceThread() {
+ if (Looper.myLooper() != mControlHandler.getLooper()) {
+ throw new IllegalStateException("Should run on service thread.");
+ }
+ }
+
+ // Run a Runnable on IO thread.
+ // It should be careful to access member variables on IO thread because
+ // it can be accessed from system thread as well.
+ private void runOnIoThread(Runnable runnable) {
+ mIoHandler.post(runnable);
+ }
+
+ private void runOnServiceThread(Runnable runnable) {
+ mControlHandler.post(runnable);
}
private boolean isAcceptableAddress(int address) {
- // Can access command targeting devices available in local device or
- // broadcast command.
- return address == HdmiCec.ADDR_BROADCAST
- || mLocalAddresses.indexOfKey(address) < 0;
+ // Can access command targeting devices available in local device or broadcast command.
+ if (address == HdmiCec.ADDR_BROADCAST) {
+ return true;
+ }
+ return isAllocatedLocalDeviceAddress(address);
}
private void onReceiveCommand(HdmiCecMessage message) {
- if (isAcceptableAddress(message.getDestination()) &&
- mService.handleCecCommand(message)) {
+ assertRunOnServiceThread();
+ if (isAcceptableAddress(message.getDestination())
+ && mService.handleCecCommand(message)) {
return;
}
- // TODO: Use device's source address for broadcast message.
- int sourceAddress = message.getDestination() != HdmiCec.ADDR_BROADCAST ?
- message.getDestination() : 0;
- // Reply <Feature Abort> to initiator (source) for all requests.
- HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand
- (sourceAddress, message.getSource(), message.getOpcode(),
- HdmiCecMessageBuilder.ABORT_REFUSED);
- sendCommand(cecMessage);
-
+ if (message.getDestination() != HdmiCec.ADDR_BROADCAST) {
+ int sourceAddress = message.getDestination();
+ // Reply <Feature Abort> to initiator (source) for all requests.
+ HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ sourceAddress, message.getSource(), message.getOpcode(),
+ HdmiCecMessageBuilder.ABORT_REFUSED);
+ sendCommand(cecMessage, null);
+ }
}
- boolean sendCommand(HdmiCecMessage cecMessage) {
- Message message = mIoHandler.obtainMessage(MSG_SEND_CEC_COMMAND, cecMessage);
- return mIoHandler.sendMessage(message);
+ void sendCommand(HdmiCecMessage cecMessage) {
+ sendCommand(cecMessage, null);
+ }
+
+ void sendCommand(final HdmiCecMessage cecMessage,
+ final HdmiControlService.SendMessageCallback callback) {
+ runOnIoThread(new Runnable() {
+ @Override
+ public void run() {
+ byte[] body = buildBody(cecMessage.getOpcode(), cecMessage.getParams());
+ final int error = nativeSendCecCommand(mNativePtr, cecMessage.getSource(),
+ cecMessage.getDestination(), body);
+ if (error != HdmiControlService.SEND_RESULT_SUCCESS) {
+ Slog.w(TAG, "Failed to send " + cecMessage);
+ }
+ if (callback != null) {
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ callback.onSendCompleted(error);
+ }
+ });
+ }
+ }
+ });
}
/**
* Called by native when incoming CEC message arrived.
*/
private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
- byte opcode = body[0];
- byte params[] = Arrays.copyOfRange(body, 1, body.length);
- HdmiCecMessage cecMessage = new HdmiCecMessage(srcAddress, dstAddress, opcode, params);
-
- // Delegate message to main handler so that it handles in main thread.
- Message message = mControlHandler.obtainMessage(
- MSG_RECEIVE_CEC_COMMAND, cecMessage);
- mControlHandler.sendMessage(message);
+ assertRunOnServiceThread();
+ onReceiveCommand(HdmiCecMessageBuilder.of(srcAddress, dstAddress, body));
}
/**
@@ -470,7 +554,7 @@
mService.onHotplug(0, connected);
}
- private static native long nativeInit(HdmiCecController handler);
+ private static native long nativeInit(HdmiCecController handler, MessageQueue messageQueue);
private static native int nativeSendCecCommand(long controllerPtr, int srcAddress,
int dstAddress, byte[] body);
private static native int nativeAddLogicalAddress(long controllerPtr, int logicalAddress);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
new file mode 100644
index 0000000..aac2a15
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import com.android.server.hdmi.HdmiCecController.AllocateLogicalAddressCallback;
+
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecDeviceInfo;
+
+/**
+ * Class that models a logical CEC device hosted in this system. Handles initialization,
+ * CEC commands that call for actions customized per device type.
+ */
+abstract class HdmiCecLocalDevice {
+
+ protected final HdmiCecController mController;
+ protected final int mDeviceType;
+ protected final AddressAllocationCallback mAllocationCallback;
+ protected int mAddress;
+ protected int mPreferredAddress;
+ protected HdmiCecDeviceInfo mDeviceInfo;
+
+ /**
+ * Callback interface to notify newly allocated logical address of the given
+ * local device.
+ */
+ interface AddressAllocationCallback {
+ /**
+ * Called when a logical address of the given device is allocated.
+ *
+ * @param deviceType original device type
+ * @param logicalAddress newly allocated logical address
+ */
+ void onAddressAllocated(int deviceType, int logicalAddress);
+ }
+
+ protected HdmiCecLocalDevice(HdmiCecController controller, int deviceType,
+ AddressAllocationCallback callback) {
+ mController = controller;
+ mDeviceType = deviceType;
+ mAllocationCallback = callback;
+ mAddress = HdmiCec.ADDR_UNREGISTERED;
+ }
+
+ // Factory method that returns HdmiCecLocalDevice of corresponding type.
+ static HdmiCecLocalDevice create(HdmiCecController controller, int deviceType,
+ AddressAllocationCallback callback) {
+ switch (deviceType) {
+ case HdmiCec.DEVICE_TV:
+ return new HdmiCecLocalDeviceTv(controller, callback);
+ case HdmiCec.DEVICE_PLAYBACK:
+ return new HdmiCecLocalDevicePlayback(controller, callback);
+ default:
+ return null;
+ }
+ }
+
+ abstract void init();
+
+ /**
+ * Called when a logical address of the local device is allocated.
+ * Note that internal variables are updated before it's called.
+ */
+ protected abstract void onAddressAllocated(int logicalAddress);
+
+ protected void allocateAddress(int type) {
+ mController.allocateLogicalAddress(type, mPreferredAddress,
+ new AllocateLogicalAddressCallback() {
+ @Override
+ public void onAllocated(int deviceType, int logicalAddress) {
+ mAddress = mPreferredAddress = logicalAddress;
+
+ // Create and set device info.
+ HdmiCecDeviceInfo deviceInfo = createDeviceInfo(mAddress, deviceType);
+ setDeviceInfo(deviceInfo);
+ mController.addDeviceInfo(deviceInfo);
+
+ mController.addLogicalAddress(logicalAddress);
+ onAddressAllocated(logicalAddress);
+ if (mAllocationCallback != null) {
+ mAllocationCallback.onAddressAllocated(deviceType, logicalAddress);
+ }
+ }
+ });
+ }
+
+ private final HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
+ int vendorId = mController.getVendorId();
+ int physicalAddress = mController.getPhysicalAddress();
+ // TODO: get device name read from system configuration.
+ String displayName = HdmiCec.getDefaultDeviceName(logicalAddress);
+ return new HdmiCecDeviceInfo(logicalAddress,
+ physicalAddress, deviceType, vendorId, displayName);
+ }
+
+ HdmiCecDeviceInfo getDeviceInfo() {
+ return mDeviceInfo;
+ }
+
+ void setDeviceInfo(HdmiCecDeviceInfo info) {
+ mDeviceInfo = info;
+ }
+
+ // Returns true if the logical address is same as the argument.
+ boolean isAddressOf(int addr) {
+ return addr == mAddress;
+ }
+
+ // Resets the logical address to unregistered(15), meaning the logical device is invalid.
+ void clearAddress() {
+ mAddress = HdmiCec.ADDR_UNREGISTERED;
+ }
+
+ void setPreferredAddress(int addr) {
+ mPreferredAddress = addr;
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
new file mode 100644
index 0000000..3347725
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+
+/**
+ * Represent a logical device of type Playback residing in Android system.
+ */
+final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
+
+ HdmiCecLocalDevicePlayback(HdmiCecController controller, AddressAllocationCallback callback) {
+ super(controller, HdmiCec.DEVICE_PLAYBACK, callback);
+ }
+
+ @Override
+ void init() {
+ allocateAddress(mDeviceType);
+ }
+
+ @Override
+ protected void onAddressAllocated(int logicalAddress) {
+ mController.sendCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ mAddress, mController.getPhysicalAddress(), mDeviceType));
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
new file mode 100644
index 0000000..93761ab
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiCec;
+
+/**
+ * Represent a logical device of type TV residing in Android system.
+ */
+final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
+
+ HdmiCecLocalDeviceTv(HdmiCecController controller, AddressAllocationCallback callback) {
+ super(controller, HdmiCec.DEVICE_TV, callback);
+ }
+
+ @Override
+ void init() {
+ allocateAddress(mDeviceType);
+ }
+
+ @Override
+ protected void onAddressAllocated(int logicalAddress) {
+ // TODO: vendor-specific initialization here.
+
+ mController.sendCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ mAddress, mController.getPhysicalAddress(), mDeviceType));
+ mController.sendCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
+ mAddress, mController.getVendorId()));
+
+ // TODO: Start routing control action, device discovery action.
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index be270b9..9a76734 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -20,6 +20,7 @@
import android.hardware.hdmi.HdmiCecMessage;
import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
/**
* A helper class to build {@link HdmiCecMessage} from various cec commands.
@@ -39,6 +40,20 @@
private HdmiCecMessageBuilder() {}
/**
+ * Build {@link HdmiCecMessage} from raw data.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @param body body of message. It includes opcode.
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage of(int src, int dest, byte[] body) {
+ byte opcode = body[0];
+ byte params[] = Arrays.copyOfRange(body, 1, body.length);
+ return new HdmiCecMessage(src, dest, opcode, params);
+ }
+
+ /**
* Build <Feature Abort> command. <Feature Abort> consists of
* 1 byte original opcode and 1 byte reason fields with basic fields.
*
@@ -58,6 +73,17 @@
}
/**
+ * Build <Give Physical Address> command.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildGivePhysicalAddress(int src, int dest) {
+ return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS);
+ }
+
+ /**
* Build <Give Osd Name> command.
*
* @param src source address of command
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecService.java b/services/core/java/com/android/server/hdmi/HdmiCecService.java
index 0a4c719..98dc72f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecService.java
@@ -75,13 +75,8 @@
@Override
public void onStart() {
- mNativePtr = nativeInit(this);
- if (mNativePtr != 0) {
- // TODO: Consider using a dedicated, configurable identifier for OSD name, maybe from
- // Settings. It should be ASCII only, not a very long one (limited to 15 chars).
- setOsdNameLocked(Build.MODEL);
- publishBinderService(Context.HDMI_CEC_SERVICE, new BinderService());
- }
+ // Stop publishing the service. Soon to be deprecated.
+ Log.w(TAG, "In transition to HdmiControlService. May not work.");
}
/**
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 09153b9..d775733 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -18,27 +18,26 @@
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.hdmi.IHdmiControlCallback;
-import android.hardware.hdmi.IHdmiControlService;
-import android.hardware.hdmi.IHdmiHotplugEventListener;
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.IHdmiControlService;
+import android.hardware.hdmi.IHdmiHotplugEventListener;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Slog;
-import android.util.SparseArray;
-
+import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
+import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
+import com.android.server.hdmi.HdmiCecLocalDevice.AddressAllocationCallback;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@@ -54,6 +53,37 @@
// TODO: Rename the permission to HDMI_CONTROL.
private static final String PERMISSION = "android.permission.HDMI_CEC";
+ static final int SEND_RESULT_SUCCESS = 0;
+ static final int SEND_RESULT_NAK = -1;
+ static final int SEND_RESULT_FAILURE = -2;
+
+ /**
+ * Interface to report send result.
+ */
+ interface SendMessageCallback {
+ /**
+ * Called when {@link HdmiControlService#sendCecCommand} is completed.
+ *
+ * @param error result of send request.
+ * @see {@link #SEND_RESULT_SUCCESS}
+ * @see {@link #SEND_RESULT_NAK}
+ * @see {@link #SEND_RESULT_FAILURE}
+ */
+ void onSendCompleted(int error);
+ }
+
+ /**
+ * Interface to get a list of available logical devices.
+ */
+ interface DevicePollingCallback {
+ /**
+ * Called when device polling is finished.
+ *
+ * @param ackedAddress a list of logical addresses of available devices
+ */
+ void onPollingFinished(List<Integer> ackedAddress);
+ }
+
// A thread to handle synchronous IO of CEC and MHL control service.
// Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
// and sparse call it shares a thread to handle IO operations.
@@ -102,7 +132,22 @@
mIoThread.start();
mCecController = HdmiCecController.create(this);
if (mCecController != null) {
- mCecController.initializeLocalDevices(mLocalDevices);
+ mCecController.initializeLocalDevices(mLocalDevices, new AddressAllocationCallback() {
+ private final SparseIntArray mAllocated = new SparseIntArray();
+
+ @Override
+ public void onAddressAllocated(int deviceType, int logicalAddress) {
+ mAllocated.append(deviceType, logicalAddress);
+ // TODO: get HdmiLCecLocalDevice and call onAddressAllocated here.
+
+ // Once all logical allocation is done, launch device discovery
+ // action if one of local device is TV.
+ int tvAddress = mAllocated.get(HdmiCec.DEVICE_TV, -1);
+ if (mLocalDevices.length == mAllocated.size() && tvAddress != -1) {
+ launchDeviceDiscovery(tvAddress);
+ }
+ }
+ });
} else {
Slog.i(TAG, "Device does not support HDMI-CEC.");
}
@@ -112,8 +157,7 @@
Slog.i(TAG, "Device does not support MHL-control.");
}
- // TODO: Publish the BinderService
- // publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
+ publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
}
/**
@@ -151,6 +195,16 @@
});
}
+ // See if we have an action of a given type in progress.
+ private <T extends FeatureAction> boolean hasAction(final Class<T> clazz) {
+ for (FeatureAction action : mActions) {
+ if (action.getClass().equals(clazz)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Remove the given {@link FeatureAction} object from the action queue.
*
@@ -205,10 +259,14 @@
* Transmit a CEC command to CEC bus.
*
* @param command CEC command to send out
- * @return {@code true} if succeeds to send command
+ * @param callback interface used to the result of send command
*/
- boolean sendCecCommand(HdmiCecMessage command) {
- return mCecController.sendCommand(command);
+ void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
+ mCecController.sendCommand(command, callback);
+ }
+
+ void sendCecCommand(HdmiCecMessage command) {
+ mCecController.sendCommand(command, null);
}
/**
@@ -245,24 +303,53 @@
case HdmiCec.MESSAGE_TERMINATE_ARC:
handleTerminateArc(message);
return true;
+ case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS:
+ handleReportPhysicalAddress(message);
+ return true;
// TODO: Add remaining system information query such as
// <Give Device Power Status> and <Request Active Source> handler.
default:
- Slog.w(TAG, "Unsupported cec command:" + message.toString());
- return false;
+ return dispatchMessageToAction(message);
}
}
/**
* Called when a new hotplug event is issued.
*
- * @param port hdmi port number where hot plug event issued.
+ * @param portNo hdmi port number where hot plug event issued.
* @param connected whether to be plugged in or not
*/
void onHotplug(int portNo, boolean connected) {
// TODO: Start "RequestArcInitiationAction" if ARC port.
}
+ /**
+ * Poll all remote devices. It sends <Polling Message> to all remote
+ * devices.
+ *
+ * @param callback an interface used to get a list of all remote devices' address
+ * @param retryCount the number of retry used to send polling message to remote devices
+ */
+ void pollDevices(DevicePollingCallback callback, int retryCount) {
+ mCecController.pollDevices(callback, retryCount);
+ }
+
+ private void handleReportPhysicalAddress(HdmiCecMessage message) {
+ // At first, try to consume it.
+ if (dispatchMessageToAction(message)) {
+ return;
+ }
+
+ // Ignore if [Device Discovery Action] is on going ignore message.
+ if (hasAction(DeviceDiscoveryAction.class)) {
+ Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> "
+ + "because Device Discovery Action is on-going:" + message);
+ return;
+ }
+
+ // TODO: start new device action.
+ }
+
private void handleInitiateArc(HdmiCecMessage message){
// In case where <Initiate Arc> is started by <Request ARC Initiation>
// need to clean up RequestArcInitiationAction.
@@ -341,6 +428,16 @@
}
}
+ private boolean dispatchMessageToAction(HdmiCecMessage message) {
+ for (FeatureAction action : mActions) {
+ if (action.processCommand(message)) {
+ return true;
+ }
+ }
+ Slog.w(TAG, "Unsupported cec command:" + message);
+ return false;
+ }
+
// Record class that monitors the event of the caller of being killed. Used to clean up
// the listener list and record list accordingly.
private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
@@ -359,6 +456,38 @@
}
}
+ void addCecDevice(HdmiCecDeviceInfo info) {
+ mCecController.addDeviceInfo(info);
+ }
+
+ // Launch device discovery sequence.
+ // It starts with clearing the existing device info list.
+ // Note that it assumes that logical address of all local devices is already allocated.
+ private void launchDeviceDiscovery(int sourceAddress) {
+ // At first, clear all existing device infos.
+ mCecController.clearDeviceInfoList();
+
+ // TODO: check whether TV is one of local devices.
+ DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress,
+ new DeviceDiscoveryCallback() {
+ @Override
+ public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) {
+ for (HdmiCecDeviceInfo info : deviceInfos) {
+ mCecController.addDeviceInfo(info);
+ }
+
+ // Add device info of all local devices.
+ for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
+ mCecController.addDeviceInfo(device.getDeviceInfo());
+ }
+
+ // TODO: start hot-plug detection sequence here.
+ // addAndStartAction(new HotplugDetectionAction());
+ }
+ });
+ addAndStartAction(action);
+ }
+
private void enforceAccessPermission() {
getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
}
@@ -373,36 +502,95 @@
}
@Override
- public void oneTouchPlay(IHdmiControlCallback callback) {
+ public void oneTouchPlay(final IHdmiControlCallback callback) {
enforceAccessPermission();
- // TODO: Post a message for HdmiControlService#oneTouchPlay()
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ HdmiControlService.this.oneTouchPlay(callback);
+ }
+ });
}
@Override
- public void queryDisplayStatus(IHdmiControlCallback callback) {
+ public void queryDisplayStatus(final IHdmiControlCallback callback) {
enforceAccessPermission();
- // TODO: Post a message for HdmiControlService#queryDisplayStatus()
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ HdmiControlService.this.queryDisplayStatus(callback);
+ }
+ });
}
@Override
- public void addHotplugEventListener(IHdmiHotplugEventListener listener) {
+ public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
enforceAccessPermission();
- // TODO: Post a message for HdmiControlService#addHotplugEventListener()
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ HdmiControlService.this.addHotplugEventListener(listener);
+ }
+ });
}
@Override
- public void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
+ public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
enforceAccessPermission();
- // TODO: Post a message for HdmiControlService#removeHotplugEventListener()
+ runOnServiceThread(new Runnable() {
+ @Override
+ public void run() {
+ HdmiControlService.this.removeHotplugEventListener(listener);
+ }
+ });
}
}
private void oneTouchPlay(IHdmiControlCallback callback) {
- // TODO: Create a new action
+ if (hasAction(OneTouchPlayAction.class)) {
+ Slog.w(TAG, "oneTouchPlay already in progress");
+ invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS);
+ return;
+ }
+ HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
+ if (source == null) {
+ Slog.w(TAG, "Local playback device not available");
+ invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
+ return;
+ }
+ // TODO: Consider the case of multiple TV sets. For now we always direct the command
+ // to the primary one.
+ OneTouchPlayAction action = OneTouchPlayAction.create(this,
+ source.getDeviceInfo().getLogicalAddress(),
+ source.getDeviceInfo().getPhysicalAddress(), HdmiCec.ADDR_TV, callback);
+ if (action == null) {
+ Slog.w(TAG, "Cannot initiate oneTouchPlay");
+ invokeCallback(callback, HdmiCec.RESULT_EXCEPTION);
+ return;
+ }
+ addAndStartAction(action);
}
private void queryDisplayStatus(IHdmiControlCallback callback) {
- // TODO: Create a new action
+ if (hasAction(DevicePowerStatusAction.class)) {
+ Slog.w(TAG, "queryDisplayStatus already in progress");
+ invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS);
+ return;
+ }
+ HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
+ if (source == null) {
+ Slog.w(TAG, "Local playback device not available");
+ invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
+ return;
+ }
+ DevicePowerStatusAction action = DevicePowerStatusAction.create(this,
+ source.getDeviceInfo().getLogicalAddress(), HdmiCec.ADDR_TV, callback);
+ if (action == null) {
+ Slog.w(TAG, "Cannot initiate queryDisplayStatus");
+ invokeCallback(callback, HdmiCec.RESULT_EXCEPTION);
+ return;
+ }
+ addAndStartAction(action);
}
private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
@@ -431,4 +619,12 @@
mHotplugEventListeners.remove(listener);
}
}
+
+ private void invokeCallback(IHdmiControlCallback callback, int result) {
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Invoking callback failed:" + e);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
index 0a701f9..343aff7 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
@@ -16,7 +16,7 @@
package com.android.server.hdmi;
-import android.util.Slog;
+import android.hardware.hdmi.HdmiCecMessage;
/**
* Feature action that handles ARC action initiated by TV devices.
@@ -37,17 +37,22 @@
@Override
boolean start() {
- if (sendCommand(
- HdmiCecMessageBuilder.buildRequestArcInitiation(mSourceAddress, mAvrAddress))) {
- mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
- addTimer(mState, TIMEOUT_MS);
- } else {
- Slog.w(TAG, "Failed to send <Request ARC Initiation>");
- // If failed to send <Request ARC Initiation>, start "Disabled" ARC transmission
- // action.
- disableArcTransmission();
- finish();
- }
+ HdmiCecMessage command = HdmiCecMessageBuilder.buildRequestArcInitiation(mSourceAddress,
+ mAvrAddress);
+ sendCommand(command, new HdmiControlService.SendMessageCallback() {
+ @Override
+ public void onSendCompleted(int error) {
+ if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
+ mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
+ addTimer(mState, TIMEOUT_MS);
+ } else {
+ // If failed to send <Request ARC Initiation>, start "Disabled"
+ // ARC transmission action.
+ disableArcTransmission();
+ finish();
+ }
+ }
+ });
return true;
}
}
diff --git a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
index db1b992..d4a35f8 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
@@ -16,7 +16,7 @@
package com.android.server.hdmi;
-import android.util.Slog;
+import android.hardware.hdmi.HdmiCecMessage;
/**
* Feature action to handle <Request ARC Termination>.
@@ -37,17 +37,22 @@
@Override
boolean start() {
- if (sendCommand(
- HdmiCecMessageBuilder.buildRequestArcTermination(mSourceAddress, mAvrAddress))) {
- mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
- addTimer(mState, TIMEOUT_MS);
- } else {
- Slog.w(TAG, "Failed to send <Request ARC Initiation>");
- // If failed to send <Request ARC Termination>, start "Disabled" ARC transmission
- // action.
- disableArcTransmission();
- finish();
- }
+ HdmiCecMessage command =
+ HdmiCecMessageBuilder.buildRequestArcTermination(mSourceAddress, mAvrAddress);
+ sendCommand(command, new HdmiControlService.SendMessageCallback() {
+ @Override
+ public void onSendCompleted(int error) {
+ if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
+ mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
+ addTimer(mState, TIMEOUT_MS);
+ } else {
+ // If failed to send <Request ARC Termination>, start "Disabled" ARC
+ // transmission action.
+ disableArcTransmission();
+ finish();
+ }
+ }
+ });
return true;
}
}
diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
index 6bf149b..e3525d8 100644
--- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
+++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java
@@ -64,26 +64,7 @@
@Override
boolean start() {
if (mEnabled) {
- if (sendCommand(
- HdmiCecMessageBuilder.buildReportArcInitiated(mSourceAddress, mAvrAddress))) {
- // Enable ARC status immediately after sending <Report Arc Initiated>.
- // If AVR responds with <Feature Abort>, disable ARC status again.
- // This is different from spec that says that turns ARC status to "Enabled"
- // if <Report ARC Initiated> is acknowledged and no <Feature Abort> is received.
- // But implemented this way to save the time having to wait for <Feature Abort>.
- setArcStatus(true);
- // If succeeds to send <Report ARC Initiated>, wait general timeout
- // to check whether there is no <Feature Abort> for <Report ARC Initiated>.
- mState = STATE_WAITING_TIMEOUT;
- addTimer(mState, TIMEOUT_MS);
- } else {
- // If fails to send <Report ARC Initiated>, disable ARC and
- // send <Report ARC Terminated> directly.
- Slog.w(TAG, "Failed to send <Report ARC Initiated>:[source:" + mSourceAddress
- + ", avr Address:" + mAvrAddress + "]");
- setArcStatus(false);
- finish();
- }
+ sendReportArcInitiated();
} else {
setArcStatus(false);
finish();
@@ -91,6 +72,35 @@
return true;
}
+ private void sendReportArcInitiated() {
+ HdmiCecMessage command =
+ HdmiCecMessageBuilder.buildReportArcInitiated(mSourceAddress, mAvrAddress);
+ sendCommand(command, new HdmiControlService.SendMessageCallback() {
+ @Override
+ public void onSendCompleted(int error) {
+ if (error == HdmiControlService.SEND_RESULT_SUCCESS) {
+ // Enable ARC status immediately after sending <Report Arc Initiated>.
+ // If AVR responds with <Feature Abort>, disable ARC status again.
+ // This is different from spec that says that turns ARC status to
+ // "Enabled" if <Report ARC Initiated> is acknowledged and no
+ // <Feature Abort> is received.
+ // But implemented this way to save the time having to wait for
+ // <Feature Abort>.
+ setArcStatus(true);
+ // If succeeds to send <Report ARC Initiated>, wait general timeout
+ // to check whether there is no <Feature Abort> for <Report ARC Initiated>.
+ mState = STATE_WAITING_TIMEOUT;
+ addTimer(mState, TIMEOUT_MS);
+ } else {
+ // If fails to send <Report ARC Initiated>, disable ARC and
+ // send <Report ARC Terminated> directly.
+ setArcStatus(false);
+ finish();
+ }
+ }
+ });
+ }
+
private void setArcStatus(boolean enabled) {
boolean wasEnabled = mService.setArcStatus(enabled);
Slog.i(TAG, "Change arc status [old:" + wasEnabled + " ,new:" + enabled);
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index c909a54..737ffda 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -25,6 +25,7 @@
import android.media.session.ISession;
import android.media.session.ISessionCallback;
import android.media.session.MediaController;
+import android.media.session.RemoteVolumeProvider;
import android.media.session.RouteCommand;
import android.media.session.RouteInfo;
import android.media.session.RouteOptions;
@@ -33,6 +34,7 @@
import android.media.session.MediaSessionInfo;
import android.media.session.RouteInterface;
import android.media.session.PlaybackState;
+import android.media.AudioManager;
import android.media.MediaMetadata;
import android.media.Rating;
import android.os.Bundle;
@@ -112,6 +114,14 @@
private long mLastActiveTime;
// End TransportPerformer fields
+ // Volume handling fields
+ private int mPlaybackType = MediaSession.VOLUME_TYPE_LOCAL;
+ private int mAudioStream = AudioManager.STREAM_MUSIC;
+ private int mVolumeControlType = RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE;
+ private int mMaxVolume = 0;
+ private int mCurrentVolume = 0;
+ // End volume handling fields
+
private boolean mIsActive = false;
private boolean mDestroyed = false;
@@ -248,6 +258,27 @@
}
/**
+ * Send a volume adjustment to the session owner.
+ *
+ * @param delta The amount to adjust the volume by.
+ */
+ public void adjustVolumeBy(int delta) {
+ if (mVolumeControlType == RemoteVolumeProvider.VOLUME_CONTROL_FIXED) {
+ // Nothing to do, the volume cannot be changed
+ return;
+ }
+ mSessionCb.adjustVolumeBy(delta);
+ }
+
+ public void setVolumeTo(int value) {
+ if (mVolumeControlType != RemoteVolumeProvider.VOLUME_CONTROL_ABSOLUTE) {
+ // Nothing to do. The volume can't be set directly.
+ return;
+ }
+ mSessionCb.setVolumeTo(value);
+ }
+
+ /**
* Set the connection to use for the selected route and notify the app it is
* now connected.
*
@@ -294,14 +325,16 @@
* Check if the session is currently performing playback. This will also
* return true if the session was recently paused.
*
+ * @param includeRecentlyActive True if playback that was recently paused
+ * should count, false if it shouldn't.
* @return True if the session is performing playback, false otherwise.
*/
- public boolean isPlaybackActive() {
+ public boolean isPlaybackActive(boolean includeRecentlyActive) {
int state = mPlaybackState == null ? 0 : mPlaybackState.getState();
if (isActiveState(state)) {
return true;
}
- if (state == mPlaybackState.STATE_PAUSED) {
+ if (includeRecentlyActive && state == mPlaybackState.STATE_PAUSED) {
long inactiveTime = SystemClock.uptimeMillis() - mLastActiveTime;
if (inactiveTime < ACTIVE_BUFFER) {
return true;
@@ -311,6 +344,54 @@
}
/**
+ * Get the type of playback, either local or remote.
+ *
+ * @return The current type of playback.
+ */
+ public int getPlaybackType() {
+ return mPlaybackType;
+ }
+
+ /**
+ * Get the local audio stream being used. Only valid if playback type is
+ * local.
+ *
+ * @return The audio stream the session is using.
+ */
+ public int getAudioStream() {
+ return mAudioStream;
+ }
+
+ /**
+ * Get the type of volume control. Only valid if playback type is remote.
+ *
+ * @return The volume control type being used.
+ */
+ public int getVolumeControl() {
+ return mVolumeControlType;
+ }
+
+ /**
+ * Get the max volume that can be set. Only valid if playback type is
+ * remote.
+ *
+ * @return The max volume that can be set.
+ */
+ public int getMaxVolume() {
+ return mMaxVolume;
+ }
+
+ /**
+ * Get the current volume for this session. Only valid if playback type is
+ * remote.
+ *
+ * @return The current volume of the remote playback.
+ */
+ public int getCurrentVolume() {
+ return mCurrentVolume;
+ }
+
+ /**
* @return True if this session is currently connected to a route.
*/
public boolean isConnected() {
@@ -640,6 +721,40 @@
mRequests.add(request);
}
}
+
+ @Override
+ public void setCurrentVolume(int volume) {
+ mCurrentVolume = volume;
+ }
+
+ @Override
+ public void configureVolumeHandling(int type, int arg1, int arg2) throws RemoteException {
+ switch(type) {
+ case MediaSession.VOLUME_TYPE_LOCAL:
+ mPlaybackType = type;
+ int audioStream = arg1;
+ if (isValidStream(audioStream)) {
+ mAudioStream = audioStream;
+ } else {
+ Log.e(TAG, "Cannot set stream to " + audioStream + ". Using music stream");
+ mAudioStream = AudioManager.STREAM_MUSIC;
+ }
+ break;
+ case MediaSession.VOLUME_TYPE_REMOTE:
+ mPlaybackType = type;
+ mVolumeControlType = arg1;
+ mMaxVolume = arg2;
+ break;
+ default:
+ throw new IllegalArgumentException("Volume handling type " + type
+ + " not recognized.");
+ }
+ }
+
+ private boolean isValidStream(int stream) {
+ return stream >= AudioManager.STREAM_VOICE_CALL
+ && stream <= AudioManager.STREAM_NOTIFICATION;
+ }
}
class SessionCb {
@@ -780,6 +895,22 @@
Slog.e(TAG, "Remote failure in rate.", e);
}
}
+
+ public void adjustVolumeBy(int delta) {
+ try {
+ mCb.onAdjustVolumeBy(delta);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
+ }
+ }
+
+ public void setVolumeTo(int value) {
+ try {
+ mCb.onSetVolumeTo(value);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote failure in adjustVolumeBy.", e);
+ }
+ }
}
class ControllerStub extends ISessionController.Stub {
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 9d85167..87665e1 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -26,6 +26,8 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.media.IAudioService;
import android.media.routeprovider.RouteRequest;
import android.media.session.ISession;
import android.media.session.ISessionCallback;
@@ -40,6 +42,7 @@
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.speech.RecognizerIntent;
@@ -79,6 +82,7 @@
private final PowerManager.WakeLock mMediaEventWakeLock;
private KeyguardManager mKeyguardManager;
+ private IAudioService mAudioService;
private MediaSessionRecord mPrioritySession;
private int mCurrentUserId = -1;
@@ -105,6 +109,12 @@
updateUser();
mKeyguardManager =
(KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
+ mAudioService = getAudioService();
+ }
+
+ private IAudioService getAudioService() {
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ return IAudioService.Stub.asInterface(b);
}
/**
@@ -703,6 +713,23 @@
}
@Override
+ public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
+ throws RemoteException {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ MediaSessionRecord session = mPriorityStack
+ .getDefaultVolumeSession(mCurrentUserId);
+ dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
@@ -737,6 +764,49 @@
}
}
+ private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
+ MediaSessionRecord session) {
+ int direction = 0;
+ int steps = delta;
+ if (delta > 0) {
+ direction = 1;
+ } else if (delta < 0) {
+ direction = -1;
+ steps = -delta;
+ }
+ if (DEBUG) {
+ String sessionInfo = session == null ? null : session.getSessionInfo().toString();
+ Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
+ + ", suggestedStream=" + suggestedStream);
+
+ }
+ if (session == null) {
+ for (int i = 0; i < steps; i++) {
+ try {
+ mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
+ flags, getContext().getOpPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error adjusting default volume.", e);
+ }
+ }
+ } else {
+ if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_LOCAL) {
+ for (int i = 0; i < steps; i++) {
+ try {
+ mAudioService.adjustSuggestedStreamVolume(direction,
+ session.getAudioStream(), flags,
+ getContext().getOpPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error adjusting volume for stream "
+ + session.getAudioStream(), e);
+ }
+ }
+ } else if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_REMOTE) {
+ session.adjustVolumeBy(delta);
+ }
+ }
+ }
+
private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
MediaSessionRecord session) {
if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 56236f8..803dee2 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -52,6 +52,7 @@
private MediaSessionRecord mCachedButtonReceiver;
private MediaSessionRecord mCachedDefault;
+ private MediaSessionRecord mCachedVolumeDefault;
private ArrayList<MediaSessionRecord> mCachedActiveList;
private ArrayList<MediaSessionRecord> mCachedTransportControlList;
@@ -93,6 +94,9 @@
mSessions.remove(record);
mSessions.add(0, record);
clearCache();
+ } else if (newState == PlaybackState.STATE_PAUSED) {
+ // Just clear the volume cache in this case
+ mCachedVolumeDefault = null;
}
}
@@ -177,6 +181,25 @@
return mCachedButtonReceiver;
}
+ public MediaSessionRecord getDefaultVolumeSession(int userId) {
+ if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
+ return mGlobalPrioritySession;
+ }
+ if (mCachedVolumeDefault != null) {
+ return mCachedVolumeDefault;
+ }
+ ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
+ int size = records.size();
+ for (int i = 0; i < size; i++) {
+ MediaSessionRecord record = records.get(i);
+ if (record.isPlaybackActive(false)) {
+ mCachedVolumeDefault = record;
+ return record;
+ }
+ }
+ return null;
+ }
+
public void dump(PrintWriter pw, String prefix) {
ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
UserHandle.USER_ALL);
@@ -237,7 +260,7 @@
lastLocalIndex++;
lastActiveIndex++;
lastPublishedIndex++;
- } else if (session.isPlaybackActive()) {
+ } else if (session.isPlaybackActive(true)) {
// TODO replace getRoute() == null with real local route check
if(session.getRoute() == null) {
// Active local sessions get top priority
@@ -284,6 +307,7 @@
private void clearCache() {
mCachedDefault = null;
+ mCachedVolumeDefault = null;
mCachedButtonReceiver = null;
mCachedActiveList = null;
mCachedTransportControlList = null;
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6c38a4c..e52f218 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -33,6 +33,7 @@
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -338,6 +339,41 @@
channels[0].dispose();
}
}
+
+ @Override
+ public void onVideoSizeChanged(int width, int height) throws RemoteException {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onVideoSizeChanged(" + width + ", " + height + ")");
+ }
+ if (sessionState.mSession == null || sessionState.mClient == null) {
+ return;
+ }
+ try {
+ sessionState.mClient.onVideoSizeChanged(width, height, sessionState.mSeq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onSessionEvent");
+ }
+ }
+ }
+
+ @Override
+ public void onSessionEvent(String eventType, Bundle eventArgs) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
+ }
+ if (sessionState.mSession == null || sessionState.mClient == null) {
+ return;
+ }
+ try {
+ sessionState.mClient.onSessionEvent(eventType, eventArgs,
+ sessionState.mSeq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onSessionEvent");
+ }
+ }
+ }
};
// Create a session. When failed, send a null token immediately.
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 81db8b3..a354c45 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -174,14 +174,14 @@
stackNdx = 0;
} else {
stackNdx = mTasks.size();
- final int currentUserId = mService.mCurrentUserId;
- if (task.mUserId != currentUserId) {
+ if (!mService.isCurrentProfileLocked(task.mUserId)) {
// Place the task below all current user tasks.
while (--stackNdx >= 0) {
- if (currentUserId != mTasks.get(stackNdx).mUserId) {
+ if (!mService.isCurrentProfileLocked(mTasks.get(stackNdx).mUserId)) {
break;
}
}
+ // Put it above first non-current user task.
++stackNdx;
}
}
@@ -352,7 +352,7 @@
int top = mTasks.size();
for (int taskNdx = 0; taskNdx < top; ++taskNdx) {
Task task = mTasks.get(taskNdx);
- if (task.mUserId == userId) {
+ if (mService.isCurrentProfileLocked(task.mUserId)) {
mTasks.remove(taskNdx);
mTasks.add(task);
--top;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 6fdd535..008d2fc 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -284,7 +284,7 @@
} else if (mKeyguardGoingAway && !nowAnimating) {
// Timeout!!
Slog.e(TAG, "Timeout waiting for animation to startup");
- mPolicy.startKeyguardExitAnimation(0);
+ mPolicy.startKeyguardExitAnimation(0, 0);
mKeyguardGoingAway = false;
}
if (win.isReadyForDisplay()) {
@@ -392,7 +392,9 @@
winAnimator.mAnimationIsEntrance = true;
if (startKeyguardExit) {
// Do one time only.
- mPolicy.startKeyguardExitAnimation(a.getStartOffset());
+ mPolicy.startKeyguardExitAnimation(mCurrentTime + a.getStartOffset(),
+ a.getDuration());
+ mKeyguardGoingAway = false;
startKeyguardExit = false;
}
}
diff --git a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
index 27c8876..a734026 100644
--- a/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
+++ b/services/core/jni/com_android_server_hdmi_HdmiCecController.cpp
@@ -21,12 +21,15 @@
#include "JNIHelp.h"
#include "ScopedPrimitiveArray.h"
-#include <string>
+#include <cstring>
+#include <android_os_MessageQueue.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <hardware/hdmi_cec.h>
#include <sys/param.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
namespace android {
@@ -37,7 +40,8 @@
class HdmiCecController {
public:
- HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj);
+ HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj,
+ const sp<Looper>& looper);
void init();
@@ -54,51 +58,137 @@
// Get vendor id used for vendor command.
uint32_t getVendorId();
-private:
- // Propagate the message up to Java layer.
- void propagateCecCommand(const cec_message_t& message);
- void propagateHotplugEvent(const hotplug_event_t& event);
+ jobject getCallbacksObj() const {
+ return mCallbacksObj;
+ }
+private:
static void onReceived(const hdmi_event_t* event, void* arg);
- static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName);
hdmi_cec_device_t* mDevice;
jobject mCallbacksObj;
+ sp<Looper> mLooper;
};
-HdmiCecController::HdmiCecController(hdmi_cec_device_t* device, jobject callbacksObj) :
+// RefBase wrapper for hdmi_event_t. As hdmi_event_t coming from HAL
+// may keep its own lifetime, we need to copy it in order to delegate
+// it to service thread.
+class CecEventWrapper : public LightRefBase<CecEventWrapper> {
+public:
+ CecEventWrapper(const hdmi_event_t& event) {
+ // Copy message.
+ switch (event.type) {
+ case HDMI_EVENT_CEC_MESSAGE:
+ mEvent.cec.initiator = event.cec.initiator;
+ mEvent.cec.destination = event.cec.destination;
+ mEvent.cec.length = event.cec.length;
+ std::memcpy(mEvent.cec.body, event.cec.body, event.cec.length);
+ break;
+ case HDMI_EVENT_HOT_PLUG:
+ mEvent.hotplug.connected = event.hotplug.connected;
+ mEvent.hotplug.port = event.hotplug.port;
+ break;
+ case HDMI_EVENT_TX_STATUS:
+ mEvent.tx_status.status = event.tx_status.status;
+ mEvent.tx_status.opcode = event.tx_status.opcode;
+ break;
+ default:
+ // TODO: add more type whenever new type is introduced.
+ break;
+ }
+ }
+
+ const cec_message_t& cec() const {
+ return mEvent.cec;
+ }
+
+ const hotplug_event_t& hotplug() const {
+ return mEvent.hotplug;
+ }
+
+ virtual ~CecEventWrapper() {}
+
+private:
+ hdmi_event_t mEvent;
+};
+
+// Handler class to delegate incoming message to service thread.
+class HdmiCecEventHandler : public MessageHandler {
+public:
+ HdmiCecEventHandler(HdmiCecController* controller, const sp<CecEventWrapper>& event)
+ : mController(controller),
+ mEventWrapper(event) {
+ }
+
+ virtual ~HdmiCecEventHandler() {}
+
+ void handleMessage(const Message& message) {
+ switch (message.what) {
+ case HDMI_EVENT_CEC_MESSAGE:
+ propagateCecCommand(mEventWrapper->cec());
+ break;
+ case HDMI_EVENT_HOT_PLUG:
+ propagateHotplugEvent(mEventWrapper->hotplug());
+ break;
+ case HDMI_EVENT_TX_STATUS:
+ // TODO: propagate this to controller.
+ default:
+ // TODO: add more type whenever new type is introduced.
+ break;
+ }
+ }
+
+private:
+ // Propagate the message up to Java layer.
+ void propagateCecCommand(const cec_message_t& message) {
+ jint srcAddr = message.initiator;
+ jint dstAddr = message.destination;
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jbyteArray body = env->NewByteArray(message.length);
+ const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body);
+ env->SetByteArrayRegion(body, 0, message.length, bodyPtr);
+
+ env->CallVoidMethod(mController->getCallbacksObj(),
+ gHdmiCecControllerClassInfo.handleIncomingCecCommand, srcAddr,
+ dstAddr, body);
+ env->DeleteLocalRef(body);
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ }
+
+ void propagateHotplugEvent(const hotplug_event_t& event) {
+ // Note that this method should be called in service thread.
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mController->getCallbacksObj(),
+ gHdmiCecControllerClassInfo.handleHotplug, event.connected);
+
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ }
+
+ // static
+ static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
+ if (env->ExceptionCheck()) {
+ ALOGE("An exception was thrown by callback '%s'.", methodName);
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+ }
+
+ HdmiCecController* mController;
+ sp<CecEventWrapper> mEventWrapper;
+};
+
+HdmiCecController::HdmiCecController(hdmi_cec_device_t* device,
+ jobject callbacksObj, const sp<Looper>& looper) :
mDevice(device),
- mCallbacksObj(callbacksObj) {
+ mCallbacksObj(callbacksObj),
+ mLooper(looper) {
}
void HdmiCecController::init() {
mDevice->register_event_callback(mDevice, HdmiCecController::onReceived, this);
}
-void HdmiCecController::propagateCecCommand(const cec_message_t& message) {
- jint srcAddr = message.initiator;
- jint dstAddr = message.destination;
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- jbyteArray body = env->NewByteArray(message.length);
- const jbyte* bodyPtr = reinterpret_cast<const jbyte *>(message.body);
- env->SetByteArrayRegion(body, 0, message.length, bodyPtr);
-
- env->CallVoidMethod(mCallbacksObj,
- gHdmiCecControllerClassInfo.handleIncomingCecCommand,
- srcAddr, dstAddr, body);
- env->DeleteLocalRef(body);
-
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
-void HdmiCecController::propagateHotplugEvent(const hotplug_event_t& event) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->CallVoidMethod(mCallbacksObj,
- gHdmiCecControllerClassInfo.handleHotplug, event.connected);
-
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
-}
-
int HdmiCecController::sendMessage(const cec_message_t& message) {
// TODO: propagate send_message's return value.
return mDevice->send_message(mDevice, &message);
@@ -132,15 +222,6 @@
return vendorId;
}
-// static
-void HdmiCecController::checkAndClearExceptionFromCallback(JNIEnv* env,
- const char* methodName) {
- if (env->ExceptionCheck()) {
- ALOGE("An exception was thrown by callback '%s'.", methodName);
- LOGE_EX(env);
- env->ExceptionClear();
- }
-}
// static
void HdmiCecController::onReceived(const hdmi_event_t* event, void* arg) {
@@ -149,17 +230,9 @@
return;
}
- switch (event->type) {
- case HDMI_EVENT_CEC_MESSAGE:
- controller->propagateCecCommand(event->cec);
- break;
- case HDMI_EVENT_HOT_PLUG:
- controller->propagateHotplugEvent(event->hotplug);
- break;
- default:
- ALOGE("Unsupported event type: %d", event->type);
- break;
- }
+ sp<CecEventWrapper> spEvent(new CecEventWrapper(*event));
+ sp<HdmiCecEventHandler> handler(new HdmiCecEventHandler(controller, spEvent));
+ controller->mLooper->sendMessage(handler, event->type);
}
//------------------------------------------------------------------------------
@@ -167,31 +240,31 @@
var = env->GetMethodID(clazz, methodName, methodDescriptor); \
LOG_FATAL_IF(! var, "Unable to find method " methodName);
-static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj) {
+static jlong nativeInit(JNIEnv* env, jclass clazz, jobject callbacksObj,
+ jobject messageQueueObj) {
int err;
- // If use same hardware module id between HdmiCecService and
- // HdmiControlSservice it may conflict and cause abnormal state of HAL.
- // TODO: use HDMI_CEC_HARDWARE_MODULE_ID of hdmi_cec.h for module id
- // once migration to HdmiControlService is done.
hw_module_t* module;
- err = hw_get_module("hdmi_cec_module",
+ err = hw_get_module(HDMI_CEC_HARDWARE_MODULE_ID,
const_cast<const hw_module_t **>(&module));
if (err != 0) {
ALOGE("Error acquiring hardware module: %d", err);
return 0;
}
+
hw_device_t* device;
- // TODO: use HDMI_CEC_HARDWARE_INTERFACE of hdmi_cec.h for interface name
- // once migration to HdmiControlService is done.
- err = module->methods->open(module, "hdmi_cec_module_hw_if", &device);
+ err = module->methods->open(module, HDMI_CEC_HARDWARE_INTERFACE, &device);
if (err != 0) {
ALOGE("Error opening hardware module: %d", err);
return 0;
}
+ sp<MessageQueue> messageQueue =
+ android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
+
HdmiCecController* controller = new HdmiCecController(
reinterpret_cast<hdmi_cec_device*>(device),
- env->NewGlobalRef(callbacksObj));
+ env->NewGlobalRef(callbacksObj),
+ messageQueue->getLooper());
controller->init();
GET_METHOD_ID(gHdmiCecControllerClassInfo.handleIncomingCecCommand, clazz,
@@ -255,8 +328,9 @@
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
- { "nativeInit", "(Lcom/android/server/hdmi/HdmiCecController;)J",
- (void *) nativeInit },
+ { "nativeInit",
+ "(Lcom/android/server/hdmi/HdmiCecController;Landroid/os/MessageQueue;)J",
+ (void *) nativeInit },
{ "nativeSendCecCommand", "(JII[B)I", (void *) nativeSendCecCommand },
{ "nativeAddLogicalAddress", "(JI)I", (void *) nativeAddLogicalAddress },
{ "nativeClearLogicalAddress", "(J)V", (void *) nativeClearLogicalAddress },
@@ -268,7 +342,8 @@
#define CLASS_PATH "com/android/server/hdmi/HdmiCecController"
int register_android_server_hdmi_HdmiCecController(JNIEnv* env) {
- int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods, NELEM(sMethods));
+ int res = jniRegisterNativeMethods(env, CLASS_PATH, sMethods,
+ NELEM(sMethods));
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
return 0;
}
diff --git a/telecomm/java/android/telecomm/CallService.java b/telecomm/java/android/telecomm/CallService.java
index 51f10c1..d452172 100644
--- a/telecomm/java/android/telecomm/CallService.java
+++ b/telecomm/java/android/telecomm/CallService.java
@@ -27,6 +27,8 @@
import com.android.internal.telecomm.ICallService;
import com.android.internal.telecomm.ICallServiceAdapter;
+import java.util.List;
+
/**
* Base implementation of CallService which can be used to provide calls for the system
* in-call UI. CallService is a one-way service from the framework's CallsManager to any app
@@ -59,6 +61,8 @@
private static final int MSG_ON_AUDIO_STATE_CHANGED = 11;
private static final int MSG_PLAY_DTMF_TONE = 12;
private static final int MSG_STOP_DTMF_TONE = 13;
+ private static final int MSG_ADD_TO_CONFERENCE = 14;
+ private static final int MSG_SPLIT_FROM_CONFERENCE = 15;
/**
* Default Handler used to consolidate binder method calls onto a single thread.
@@ -123,6 +127,29 @@
case MSG_STOP_DTMF_TONE:
stopDtmfTone((String) msg.obj);
break;
+ case MSG_ADD_TO_CONFERENCE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ @SuppressWarnings("unchecked")
+ List<String> callIds = (List<String>) args.arg2;
+ String conferenceCallId = (String) args.arg1;
+ addToConference(conferenceCallId, callIds);
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
+ case MSG_SPLIT_FROM_CONFERENCE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ String conferenceCallId = (String) args.arg1;
+ String callId = (String) args.arg2;
+ splitFromConference(conferenceCallId, callId);
+ } finally {
+ args.recycle();
+ }
+ break;
+ }
default:
break;
}
@@ -204,6 +231,22 @@
args.arg2 = audioState;
mMessageHandler.obtainMessage(MSG_ON_AUDIO_STATE_CHANGED, args).sendToTarget();
}
+
+ @Override
+ public void addToConference(String conferenceCallId, List<String> callsToConference) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = conferenceCallId;
+ args.arg2 = callsToConference;
+ mMessageHandler.obtainMessage(MSG_ADD_TO_CONFERENCE, args).sendToTarget();
+ }
+
+ @Override
+ public void splitFromConference(String conferenceCallId, String callId) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = conferenceCallId;
+ args.arg2 = callId;
+ mMessageHandler.obtainMessage(MSG_SPLIT_FROM_CONFERENCE, args).sendToTarget();
+ }
}
/**
@@ -359,4 +402,24 @@
* @param audioState The new {@link CallAudioState}.
*/
public abstract void onAudioStateChanged(String activeCallId, CallAudioState audioState);
+
+ /**
+ * Adds the specified calls to the specified conference call.
+ *
+ * @param conferenceCallId The unique ID of the conference call onto which the specified calls
+ * should be added.
+ * @param callIds The calls to add to the conference call.
+ * @hide
+ */
+ public abstract void addToConference(String conferenceCallId, List<String> callIds);
+
+ /**
+ * Removes the specified call from the specified conference call. This is a no-op if the call
+ * is not already part of the conference call.
+ *
+ * @param conferenceCallId The conference call.
+ * @param callId The call to remove from the conference call
+ * @hide
+ */
+ public abstract void splitFromConference(String conferenceCallId, String callId);
}
diff --git a/telecomm/java/android/telecomm/CallServiceAdapter.java b/telecomm/java/android/telecomm/CallServiceAdapter.java
index dafc310..7396808 100644
--- a/telecomm/java/android/telecomm/CallServiceAdapter.java
+++ b/telecomm/java/android/telecomm/CallServiceAdapter.java
@@ -20,6 +20,8 @@
import com.android.internal.telecomm.ICallServiceAdapter;
+import java.util.List;
+
/**
* Provides methods for ICallService implementations to interact with the system phone app.
* TODO(santoscordon): Need final public-facing comments in this file.
@@ -169,4 +171,46 @@
}
}
+ /**
+ * Indicates that the specified call can conference with any of the specified list of calls.
+ *
+ * @param callId The unique ID of the call.
+ * @param conferenceCapableCallIds The unique IDs of the calls which can be conferenced.
+ * @hide
+ */
+ public void setCanConferenceWith(String callId, List<String> conferenceCapableCallIds) {
+ try {
+ mAdapter.setCanConferenceWith(callId, conferenceCapableCallIds);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
+ * Indicates whether or not the specified call is currently conferenced into the specified
+ * conference call.
+ *
+ * @param conferenceCallId The unique ID of the conference call.
+ * @param callId The unique ID of the call being conferenced.
+ * @hide
+ */
+ public void setIsConferenced(String conferenceCallId, String callId, boolean isConferenced) {
+ try {
+ mAdapter.setIsConferenced(conferenceCallId, callId, isConferenced);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
+ * Indicates that the call no longer exists. Can be used with either a call or a conference
+ * call.
+ *
+ * @param callId The unique ID of the call.
+ * @hide
+ */
+ public void removeCall(String callId) {
+ try {
+ mAdapter.removeCall(callId);
+ } catch (RemoteException ignored) {
+ }
+ }
}
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index 8d02842..aeb1c33 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -20,6 +20,8 @@
import android.os.Bundle;
import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
import java.util.Map;
/**
@@ -248,6 +250,39 @@
findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState);
}
+ /** @hide */
+ @Override
+ public final void addToConference(String conferenceCallId, List<String> callIds) {
+ Log.d(this, "addToConference %s, %s", conferenceCallId, callIds);
+
+ List<Connection> connections = new LinkedList<>();
+ for (String id : callIds) {
+ Connection connection = findConnectionForAction(id, "addToConference");
+ if (connection == NULL_CONNECTION) {
+ Log.w(this, "Connection missing in conference request %s.", id);
+ return;
+ }
+ connections.add(connection);
+ }
+
+ // TODO(santoscordon): Find an existing conference call or create a new one. Then call
+ // conferenceWith on it.
+ }
+
+ /** @hide */
+ @Override
+ public final void splitFromConference(String conferenceCallId, String callId) {
+ Log.d(this, "splitFromConference(%s, %s)", conferenceCallId, callId);
+
+ Connection connection = findConnectionForAction(callId, "splitFromConference");
+ if (connection == NULL_CONNECTION) {
+ Log.w(this, "Connection missing in conference request %s.", callId);
+ return;
+ }
+
+ // TODO(santoscordon): Find existing conference call and invoke split(connection).
+ }
+
/**
* Find a set of Subscriptions matching a given handle (e.g. phone number).
*
@@ -342,4 +377,4 @@
Log.w(this, "%s - Cannot find Connection %s", action, callId);
return NULL_CONNECTION;
}
-}
\ No newline at end of file
+}
diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java
index e41d3f6..6838ede 100644
--- a/telecomm/java/android/telecomm/InCallAdapter.java
+++ b/telecomm/java/android/telecomm/InCallAdapter.java
@@ -196,4 +196,32 @@
} catch (RemoteException e) {
}
}
+
+ /**
+ * Instructs Telecomm to conference the specified calls together.
+ *
+ * @param callId The unique ID of the call.
+ * @param callIdToConference The unique ID of the call to conference with.
+ * @hide
+ */
+ void conferenceWith(String callId, String callIdToConference) {
+ try {
+ mAdapter.conferenceWith(callId, callIdToConference);
+ } catch (RemoteException ignored) {
+ }
+ }
+
+ /**
+ * Instructs Telecomm to split the specified call from any conference call with which it may be
+ * connected.
+ *
+ * @param callId The unique ID of the call.
+ * @hide
+ */
+ void splitFromConference(String callId) {
+ try {
+ mAdapter.splitFromConference(callId);
+ } catch (RemoteException ignored) {
+ }
+ }
}
diff --git a/telecomm/java/android/telecomm/InCallCall.java b/telecomm/java/android/telecomm/InCallCall.java
index c3b2ae7..346d207 100644
--- a/telecomm/java/android/telecomm/InCallCall.java
+++ b/telecomm/java/android/telecomm/InCallCall.java
@@ -17,13 +17,13 @@
package android.telecomm;
import android.net.Uri;
-import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.DisconnectCause;
-import java.util.Date;
-import java.util.UUID;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Information about a call that is used between InCallService and Telecomm.
@@ -38,6 +38,26 @@
private final GatewayInfo mGatewayInfo;
private final CallServiceDescriptor mCurrentCallServiceDescriptor;
private final CallServiceDescriptor mHandoffCallServiceDescriptor;
+ private final List<String> mConferenceCapableCallIds;
+ private final String mParentCallId;
+ private final List<String> mChildCallIds;
+
+ /** @hide */
+ @SuppressWarnings("unchecked")
+ public InCallCall(
+ String id,
+ CallState state,
+ int disconnectCause,
+ int capabilities,
+ long connectTimeMillis,
+ Uri handle,
+ GatewayInfo gatewayInfo,
+ CallServiceDescriptor descriptor,
+ CallServiceDescriptor handoffDescriptor) {
+ this(id, state, disconnectCause, capabilities, connectTimeMillis, handle, gatewayInfo,
+ descriptor, handoffDescriptor, Collections.EMPTY_LIST, null,
+ Collections.EMPTY_LIST);
+ }
/** @hide */
public InCallCall(
@@ -49,7 +69,10 @@
Uri handle,
GatewayInfo gatewayInfo,
CallServiceDescriptor descriptor,
- CallServiceDescriptor handoffDescriptor) {
+ CallServiceDescriptor handoffDescriptor,
+ List<String> conferenceCapableCallIds,
+ String parentCallId,
+ List<String> childCallIds) {
mId = id;
mState = state;
mDisconnectCause = disconnectCause;
@@ -59,6 +82,9 @@
mGatewayInfo = gatewayInfo;
mCurrentCallServiceDescriptor = descriptor;
mHandoffCallServiceDescriptor = handoffDescriptor;
+ mConferenceCapableCallIds = conferenceCapableCallIds;
+ mParentCallId = parentCallId;
+ mChildCallIds = childCallIds;
}
/** The unique ID of the call. */
@@ -112,6 +138,31 @@
return mHandoffCallServiceDescriptor;
}
+ /**
+ * The calls with which this call can conference.
+ * @hide
+ */
+ public List<String> getConferenceCapableCallIds() {
+ return mConferenceCapableCallIds;
+ }
+
+ /**
+ * The conference call to which this call is conferenced. Null if not conferenced.
+ * @hide
+ */
+ public String getParentCallId() {
+ return mParentCallId;
+ }
+
+ /**
+ * The child call-IDs if this call is a conference call. Returns an empty list if this is not
+ * a conference call or if the conference call contains no children.
+ * @hide
+ */
+ public List<String> getChildCallIds() {
+ return mChildCallIds;
+ }
+
/** Responsible for creating InCallCall objects for deserialized Parcels. */
public static final Parcelable.Creator<InCallCall> CREATOR =
new Parcelable.Creator<InCallCall> () {
@@ -127,8 +178,14 @@
GatewayInfo gatewayInfo = source.readParcelable(classLoader);
CallServiceDescriptor descriptor = source.readParcelable(classLoader);
CallServiceDescriptor handoffDescriptor = source.readParcelable(classLoader);
+ List<String> conferenceCapableCallIds = new ArrayList<>();
+ source.readList(conferenceCapableCallIds, classLoader);
+ String parentCallId = source.readString();
+ List<String> childCallIds = new ArrayList<>();
+ source.readList(childCallIds, classLoader);
return new InCallCall(id, state, disconnectCause, capabilities, connectTimeMillis,
- handle, gatewayInfo, descriptor, handoffDescriptor);
+ handle, gatewayInfo, descriptor, handoffDescriptor, conferenceCapableCallIds,
+ parentCallId, childCallIds);
}
@Override
@@ -155,5 +212,8 @@
destination.writeParcelable(mGatewayInfo, 0);
destination.writeParcelable(mCurrentCallServiceDescriptor, 0);
destination.writeParcelable(mHandoffCallServiceDescriptor, 0);
+ destination.writeList(mConferenceCapableCallIds);
+ destination.writeString(mParentCallId);
+ destination.writeList(mChildCallIds);
}
}
diff --git a/telecomm/java/com/android/internal/telecomm/ICallService.aidl b/telecomm/java/com/android/internal/telecomm/ICallService.aidl
index cc0641c..771a3ae 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallService.aidl
@@ -55,4 +55,8 @@
void playDtmfTone(String callId, char digit);
void stopDtmfTone(String callId);
+
+ void addToConference(String conferenceCallId, in List<String> callIds);
+
+ void splitFromConference(String conferenceCallId, String callId);
}
diff --git a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
index 6d36494..a92b176 100644
--- a/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/ICallServiceAdapter.aidl
@@ -45,4 +45,10 @@
void setOnHold(String callId);
void setRequestingRingback(String callId, boolean ringing);
+
+ void setCanConferenceWith(String callId, in List<String> conferenceCapableCallIds);
+
+ void setIsConferenced(String conferenceCallId, String callId, boolean isConferenced);
+
+ void removeCall(String callId);
}
diff --git a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
index 512e898..6a27217 100644
--- a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
@@ -47,4 +47,8 @@
void postDialContinue(String callId);
void handoffCall(String callId);
+
+ void conferenceWith(String callId, String callIdToConference);
+
+ void splitFromConference(String callId);
}
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index c162bf28..a54936b 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -608,4 +608,9 @@
public File[] getExternalCacheDirs() {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public File[] getExternalMediaDirs() {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java
index 5b0aa66..a81e063 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/CirclePropActivity.java
@@ -118,19 +118,25 @@
mRadius, mToggle ? 250.0f : 150.0f));
mRunningAnimations.add(new RenderNodeAnimator(
- mPaint, RenderNodeAnimator.PAINT_ALPHA,
- mToggle ? 64.0f : 255.0f));
-
- mRunningAnimations.add(new RenderNodeAnimator(
mPaint, RenderNodeAnimator.PAINT_STROKE_WIDTH,
mToggle ? 5.0f : 60.0f));
- TimeInterpolator interp = new OvershootInterpolator(3.0f);
+ mRunningAnimations.add(new RenderNodeAnimator(
+ mPaint, RenderNodeAnimator.PAINT_ALPHA, 64.0f));
+
+ // Will be "chained" to run after the above
+ mRunningAnimations.add(new RenderNodeAnimator(
+ mPaint, RenderNodeAnimator.PAINT_ALPHA, 255.0f));
+
for (int i = 0; i < mRunningAnimations.size(); i++) {
RenderNodeAnimator anim = mRunningAnimations.get(i);
- anim.setInterpolator(interp);
anim.setDuration(1000);
anim.setTarget(this);
+ if (i == (mRunningAnimations.size() - 1)) {
+ // "chain" test
+ anim.setStartValue(64.0f);
+ anim.setStartDelay(anim.getDuration());
+ }
anim.start();
}
diff --git a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
index c1fa74f..d6f8118 100644
--- a/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
+++ b/tests/OneMedia/src/com/android/onemedia/PlayerSession.java
@@ -82,7 +82,7 @@
Log.d(TAG, "Creating session for package " + mContext.getBasePackageName());
mSession = man.createSession("OneMedia");
mSession.addCallback(mCallback);
- mSession.addTransportControlsCallback(new TransportListener());
+ mSession.addTransportControlsCallback(new TransportCallback());
mSession.setPlaybackState(mPlaybackState);
mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
mSession.setRouteOptions(mRouteOptions);
@@ -255,7 +255,7 @@
}
}
- private class TransportListener extends MediaSession.TransportControlsCallback {
+ private class TransportCallback extends MediaSession.TransportControlsCallback {
@Override
public void onPlay() {
mRenderer.onPlay();
diff --git a/tests/VoiceInteraction/res/layout/test_interaction.xml b/tests/VoiceInteraction/res/layout/test_interaction.xml
index 2abf65194..4c0c67a 100644
--- a/tests/VoiceInteraction/res/layout/test_interaction.xml
+++ b/tests/VoiceInteraction/res/layout/test_interaction.xml
@@ -18,6 +18,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
+ android:padding="8dp"
>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
@@ -29,9 +30,16 @@
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
- android:layout_marginTop="10dp"
- android:textSize="12sp"
+ android:layout_marginTop="16dp"
+ android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#ffffffff"
/>
+ <Button android:id="@+id/abort"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="@string/abortVoice"
+ />
+
</LinearLayout>
diff --git a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml
index 563fa44..142d781 100644
--- a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml
+++ b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml
@@ -14,26 +14,49 @@
limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
- android:background="#ffffffff"
- android:fitsSystemWindows="true"
- >
+ android:fitsSystemWindows="true">
- <TextView android:id="@+id/text"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="32dp"
- />
+ <FrameLayout android:layout_width="fill_parent"
+ android:layout_height="match_parent"
+ android:padding="8dp">
- <Button android:id="@+id/start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/start"
- />
+ <LinearLayout android:id="@+id/content"
+ android:layout_width="fill_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="#ffffffff"
+ android:elevation="8dp"
+ >
-</LinearLayout>
+ <TextView android:id="@+id/text"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="16dp"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ />
+ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button android:id="@+id/start"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/start"
+ />
+ <Button android:id="@+id/confirm"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/confirm"
+ />
+ <Button android:id="@+id/abort"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/abort"
+ />
+ </LinearLayout>
+ </LinearLayout>
+ </FrameLayout>
+</FrameLayout>
diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml
index 12edb31..70baa52 100644
--- a/tests/VoiceInteraction/res/values/strings.xml
+++ b/tests/VoiceInteraction/res/values/strings.xml
@@ -16,7 +16,10 @@
<resources>
- <string name="start">Start!</string>
+ <string name="start">Start</string>
+ <string name="confirm">Confirm</string>
+ <string name="abort">Abort</string>
+ <string name="abortVoice">Abort Voice</string>
</resources>
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
index a3af284..c24a088 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
@@ -33,9 +33,17 @@
View mContentView;
TextView mText;
Button mStartButton;
+ Button mConfirmButton;
+ Button mAbortButton;
+ static final int STATE_IDLE = 0;
+ static final int STATE_LAUNCHING = 1;
+ static final int STATE_CONFIRM = 2;
+ static final int STATE_COMMAND = 3;
+ static final int STATE_ABORT_VOICE = 4;
+
+ int mState = STATE_IDLE;
Request mPendingRequest;
- boolean mPendingConfirm;
MainInteractionSession(Context context) {
super(context);
@@ -54,21 +62,39 @@
mText = (TextView)mContentView.findViewById(R.id.text);
mStartButton = (Button)mContentView.findViewById(R.id.start);
mStartButton.setOnClickListener(this);
+ mConfirmButton = (Button)mContentView.findViewById(R.id.confirm);
+ mConfirmButton.setOnClickListener(this);
+ mAbortButton = (Button)mContentView.findViewById(R.id.abort);
+ mAbortButton.setOnClickListener(this);
+ updateState();
return mContentView;
}
+ void updateState() {
+ mStartButton.setEnabled(mState == STATE_IDLE);
+ mConfirmButton.setEnabled(mState == STATE_CONFIRM || mState == STATE_COMMAND);
+ mAbortButton.setEnabled(mState == STATE_ABORT_VOICE);
+ }
+
public void onClick(View v) {
- if (mPendingRequest == null) {
- mStartButton.setEnabled(false);
+ if (v == mStartButton) {
+ mState = STATE_LAUNCHING;
+ updateState();
startVoiceActivity(mStartIntent);
- } else {
- if (mPendingConfirm) {
+ } else if (v == mConfirmButton) {
+ if (mState == STATE_CONFIRM) {
mPendingRequest.sendConfirmResult(true, null);
} else {
mPendingRequest.sendCommandResult(true, null);
}
mPendingRequest = null;
- mStartButton.setText("Start");
+ mState = STATE_IDLE;
+ updateState();
+ } else if (v == mAbortButton) {
+ mPendingRequest.sendAbortVoiceResult(null);
+ mPendingRequest = null;
+ mState = STATE_IDLE;
+ updateState();
}
}
@@ -78,23 +104,32 @@
}
@Override
- public void onConfirm(Caller caller, Request request, String prompt, Bundle extras) {
+ public void onConfirm(Caller caller, Request request, CharSequence prompt, Bundle extras) {
Log.i(TAG, "onConfirm: prompt=" + prompt + " extras=" + extras);
mText.setText(prompt);
- mStartButton.setEnabled(true);
mStartButton.setText("Confirm");
mPendingRequest = request;
- mPendingConfirm = true;
+ mState = STATE_CONFIRM;
+ updateState();
+ }
+
+ @Override
+ public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) {
+ Log.i(TAG, "onAbortVoice: message=" + message + " extras=" + extras);
+ mText.setText(message);
+ mPendingRequest = request;
+ mState = STATE_ABORT_VOICE;
+ updateState();
}
@Override
public void onCommand(Caller caller, Request request, String command, Bundle extras) {
Log.i(TAG, "onCommand: command=" + command + " extras=" + extras);
mText.setText("Command: " + command);
- mStartButton.setEnabled(true);
mStartButton.setText("Finish Command");
mPendingRequest = request;
- mPendingConfirm = false;
+ mState = STATE_COMMAND;
+ updateState();
}
@Override
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
index a61e0da..3ae6a36 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
@@ -21,12 +21,15 @@
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
+import android.view.View;
import android.view.ViewGroup;
+import android.widget.Button;
-public class TestInteractionActivity extends Activity {
+public class TestInteractionActivity extends Activity implements View.OnClickListener {
static final String TAG = "TestInteractionActivity";
VoiceInteractor mInteractor;
+ Button mAbortButton;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -39,6 +42,8 @@
}
setContentView(R.layout.test_interaction);
+ mAbortButton = (Button)findViewById(R.id.abort);
+ mAbortButton.setOnClickListener(this);
// Framework should take care of these.
getWindow().setGravity(Gravity.TOP);
@@ -69,6 +74,26 @@
}
@Override
+ public void onClick(View v) {
+ if (v == mAbortButton) {
+ VoiceInteractor.AbortVoiceRequest req = new VoiceInteractor.AbortVoiceRequest(
+ "Dammit, we suck :(", null) {
+ @Override
+ public void onCancel() {
+ Log.i(TAG, "Canceled!");
+ }
+
+ @Override
+ public void onAbortResult(Bundle result) {
+ Log.i(TAG, "Abort result: result=" + result);
+ getActivity().finish();
+ }
+ };
+ mInteractor.submitRequest(req);
+ }
+ }
+
+ @Override
public void onDestroy() {
super.onDestroy();
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index d31239b..5c51c63 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1466,4 +1466,10 @@
// pass
return new File[0];
}
+
+ @Override
+ public File[] getExternalMediaDirs() {
+ // pass
+ return new File[0];
+ }
}
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 58b0d61..f6d7f55 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -90,6 +90,19 @@
*/
public final static int UNSPECIFIED = -1;
+ /** information element from beacon
+ * @hide
+ */
+ public static class InformationElement {
+ public int id;
+ public byte[] bytes;
+ }
+
+ /** information elements found in the beacon
+ * @hide
+ */
+ public InformationElement informationElements[];
+
/** {@hide} */
public ScanResult(WifiSsid wifiSsid, String BSSID, String caps, int level, int frequency,
long tsf) {
@@ -199,6 +212,16 @@
} else {
dest.writeInt(0);
}
+ if (informationElements != null) {
+ dest.writeInt(informationElements.length);
+ for (int i = 0; i < informationElements.length; i++) {
+ dest.writeInt(informationElements[i].id);
+ dest.writeInt(informationElements[i].bytes.length);
+ dest.writeByteArray(informationElements[i].bytes);
+ }
+ } else {
+ dest.writeInt(0);
+ }
}
/** Implement the Parcelable interface {@hide} */
@@ -223,6 +246,17 @@
if (in.readInt() == 1) {
sr.passpoint = WifiPasspointInfo.CREATOR.createFromParcel(in);
}
+ int n = in.readInt();
+ if (n != 0) {
+ sr.informationElements = new InformationElement[n];
+ for (int i = 0; i < n; i++) {
+ sr.informationElements[i] = new InformationElement();
+ sr.informationElements[i].id = in.readInt();
+ int len = in.readInt();
+ sr.informationElements[i].bytes = new byte[len];
+ in.readByteArray(sr.informationElements[i].bytes);
+ }
+ }
return sr;
}
@@ -230,5 +264,4 @@
return new ScanResult[size];
}
};
-
}
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 9ea7027..3b65ca8 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -40,6 +40,7 @@
* Get an instance of this class by calling
* {@link android.content.Context#getSystemService(String) Context.getSystemService(Context
* .WIFI_SCANNING_SERVICE)}.
+ * @hide
*/
public class WifiScanner {
@@ -72,16 +73,14 @@
public static final int REASON_INVALID_LISTENER = -2;
/** Invalid request */
public static final int REASON_INVALID_REQUEST = -3;
- /** Request conflicts with other scans that may be going on */
- public static final int REASON_CONFLICTING_REQUEST = -4;
/**
* Generic action callback invocation interface
* @hide
*/
public static interface ActionListener {
- public void onSuccess(Object result);
- public void onFailure(int reason, Object exception);
+ public void onSuccess();
+ public void onFailure(int reason, String description);
}
/**
@@ -193,58 +192,6 @@
}
- /** information element from beacon */
- public static class InformationElement {
- public int id;
- public byte[] bytes;
- }
-
- /** scan result with information elements from beacons */
- public static class FullScanResult implements Parcelable {
- public ScanResult result;
- public InformationElement informationElements[];
-
- /** Implement the Parcelable interface {@hide} */
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface {@hide} */
- public void writeToParcel(Parcel dest, int flags) {
- result.writeToParcel(dest, flags);
- dest.writeInt(informationElements.length);
- for (int i = 0; i < informationElements.length; i++) {
- dest.writeInt(informationElements[i].id);
- dest.writeInt(informationElements[i].bytes.length);
- dest.writeByteArray(informationElements[i].bytes);
- }
- }
-
- /** Implement the Parcelable interface {@hide} */
- public static final Creator<FullScanResult> CREATOR =
- new Creator<FullScanResult>() {
- public FullScanResult createFromParcel(Parcel in) {
- FullScanResult result = new FullScanResult();
- result.result = ScanResult.CREATOR.createFromParcel(in);
- int n = in.readInt();
- result.informationElements = new InformationElement[n];
- for (int i = 0; i < n; i++) {
- result.informationElements[i] = new InformationElement();
- result.informationElements[i].id = in.readInt();
- int len = in.readInt();
- result.informationElements[i].bytes = new byte[len];
- in.readByteArray(result.informationElements[i].bytes);
- }
-
- return result;
- }
-
- public FullScanResult[] newArray(int size) {
- return new FullScanResult[size];
- }
- };
- }
-
/** @hide */
public static class ParcelableScanResults implements Parcelable {
public ScanResult mResults[];
@@ -305,7 +252,7 @@
/**
* reports full scan result for each access point found in scan
*/
- public void onFullResult(FullScanResult fullScanResult);
+ public void onFullResult(ScanResult fullScanResult);
}
/** @hide */
@@ -336,13 +283,12 @@
}
/**
* retrieves currently available scan results
- * @param flush {@code true} means flush all results
- * @param listener specifies which scan to cancel; must be same object as passed in {@link
- * #startBackgroundScan}
*/
- public void retrieveScanResults(boolean flush, ScanListener listener) {
+ public ScanResult[] getScanResults() {
validateChannel();
- sAsyncChannel.sendMessage(CMD_GET_SCAN_RESULTS, 0, getListenerKey(listener));
+ Message reply = sAsyncChannel.sendMessageSynchronously(CMD_GET_SCAN_RESULTS, 0);
+ ScanResult[] results = (ScanResult[]) reply.obj;
+ return results;
}
/** specifies information about an access point of interest */
@@ -490,7 +436,7 @@
}
/** interface to receive hotlist events on; use this on {@link #setHotlist} */
- public static interface HotlistListener extends ActionListener {
+ public static interface HotspotListener extends ActionListener {
/** indicates that access points were found by on going scans
* @param results list of scan results, one for each access point visible currently
*/
@@ -550,10 +496,10 @@
* @param hotspots access points of interest
* @param apLostThreshold number of scans needed to indicate that AP is lost
* @param listener object provided to report events on; this object must be unique and must
- * also be provided on {@link #resetHotlist}
+ * also be provided on {@link #stopTrackingHotspots}
*/
- public void setHotlist(HotspotInfo[] hotspots,
- int apLostThreshold, HotlistListener listener) {
+ public void startTrackingHotspots(HotspotInfo[] hotspots,
+ int apLostThreshold, HotspotListener listener) {
validateChannel();
HotlistSettings settings = new HotlistSettings();
settings.hotspotInfos = hotspots;
@@ -562,9 +508,9 @@
/**
* remove tracking of interesting access points
- * @param listener same object provided in {@link #setHotlist}
+ * @param listener same object provided in {@link #startTrackingHotspots}
*/
- public void resetHotlist(HotlistListener listener) {
+ public void stopTrackingHotspots(HotspotListener listener) {
validateChannel();
sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener));
}
@@ -769,10 +715,10 @@
switch (msg.what) {
/* ActionListeners grouped together */
case CMD_OP_SUCCEEDED :
- ((ActionListener) listener).onSuccess(msg.obj);
+ ((ActionListener) listener).onSuccess();
break;
case CMD_OP_FAILED :
- ((ActionListener) listener).onFailure(msg.arg1, msg.obj);
+ ((ActionListener) listener).onFailure(msg.arg1, (String)msg.obj);
removeListener(msg.arg2);
break;
case CMD_SCAN_RESULT :
@@ -780,14 +726,14 @@
((ParcelableScanResults) msg.obj).getResults());
return;
case CMD_FULL_SCAN_RESULT :
- FullScanResult result = (FullScanResult) msg.obj;
+ ScanResult result = (ScanResult) msg.obj;
((ScanListener) listener).onFullResult(result);
return;
case CMD_PERIOD_CHANGED:
((ScanListener) listener).onPeriodChanged(msg.arg1);
return;
case CMD_AP_FOUND:
- ((HotlistListener) listener).onFound(
+ ((HotspotListener) listener).onFound(
((ParcelableScanResults) msg.obj).getResults());
return;
case CMD_WIFI_CHANGE_DETECTED: