diff options
66 files changed, 1707 insertions, 1250 deletions
@@ -267,6 +267,7 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/app/IBatteryStats.aidl \ core/java/com/android/internal/app/IProcessStats.aidl \ core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \ + core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl \ core/java/com/android/internal/app/IVoiceInteractor.aidl \ core/java/com/android/internal/app/IVoiceInteractorCallback.aidl \ core/java/com/android/internal/app/IVoiceInteractorRequest.aidl \ diff --git a/api/current.txt b/api/current.txt index b99cb4f..8174b0e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1200,6 +1200,7 @@ package android { field public static final int summaryColumn = 16843426; // 0x10102a2 field public static final int summaryOff = 16843248; // 0x10101f0 field public static final int summaryOn = 16843247; // 0x10101ef + field public static final int supportsAssistGesture = 16844012; // 0x10104ec field public static final int supportsRtl = 16843695; // 0x10103af field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb field public static final int supportsUploading = 16843419; // 0x101029b @@ -28196,6 +28197,7 @@ package android.service.voice { method public void showSession(android.os.Bundle, int); field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService"; field public static final java.lang.String SERVICE_META_DATA = "android.voice_interaction"; + field public static final int START_SOURCE_ASSIST_GESTURE = 4; // 0x4 field public static final int START_WITH_ASSIST = 1; // 0x1 field public static final int START_WITH_SCREENSHOT = 2; // 0x2 } diff --git a/api/system-current.txt b/api/system-current.txt index 584b18f..ce821eb 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1277,6 +1277,7 @@ package android { field public static final int summaryColumn = 16843426; // 0x10102a2 field public static final int summaryOff = 16843248; // 0x10101f0 field public static final int summaryOn = 16843247; // 0x10101ef + field public static final int supportsAssistGesture = 16844012; // 0x10104ec field public static final int supportsRtl = 16843695; // 0x10103af field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb field public static final int supportsUploading = 16843419; // 0x101029b @@ -30271,6 +30272,7 @@ package android.service.voice { method public void showSession(android.os.Bundle, int); field public static final java.lang.String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService"; field public static final java.lang.String SERVICE_META_DATA = "android.voice_interaction"; + field public static final int START_SOURCE_ASSIST_GESTURE = 4; // 0x4 field public static final int START_WITH_ASSIST = 1; // 0x1 field public static final int START_WITH_SCREENSHOT = 2; // 0x2 } diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl index 4f4b2d5..7c90261 100644 --- a/core/java/android/service/voice/IVoiceInteractionSession.aidl +++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl @@ -20,11 +20,13 @@ import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; + /** * @hide */ oneway interface IVoiceInteractionSession { - void show(in Bundle sessionArgs, int flags); + void show(in Bundle sessionArgs, int flags, IVoiceInteractionSessionShowCallback showCallback); void hide(); void handleAssist(in Bundle assistData); void handleScreenshot(in Bitmap screenshot); diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 419b92b..fee0c75 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -82,6 +82,12 @@ public class VoiceInteractionService extends Service { */ public static final int START_WITH_SCREENSHOT = 1<<1; + /** + * Flag for use with {@link #showSession}: indicate that the session has been started from the + * system assist gesture. + */ + public static final int START_SOURCE_ASSIST_GESTURE = 1<<2; + IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() { @Override public void ready() { mHandler.sendEmptyMessage(MSG_READY); diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java index ebc7507..4bc97c9 100644 --- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java +++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java @@ -43,6 +43,7 @@ public class VoiceInteractionServiceInfo { private String mSessionService; private String mRecognitionService; private String mSettingsActivity; + private boolean mSupportsAssistGesture; public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp) throws PackageManager.NameNotFoundException { @@ -94,6 +95,9 @@ public class VoiceInteractionServiceInfo { com.android.internal.R.styleable.VoiceInteractionService_recognitionService); mSettingsActivity = array.getString( com.android.internal.R.styleable.VoiceInteractionService_settingsActivity); + mSupportsAssistGesture = array.getBoolean( + com.android.internal.R.styleable.VoiceInteractionService_supportsAssistGesture, + false); array.recycle(); if (mSessionService == null) { mParseError = "No sessionService specified"; @@ -103,11 +107,6 @@ public class VoiceInteractionServiceInfo { mParseError = "No recognitionService specified"; return; } - /* Not yet time - if (mRecognitionService == null) { - mParseError = "No recogitionService specified"; - return; - } */ } catch (XmlPullParserException e) { mParseError = "Error parsing voice interation service meta-data: " + e; Log.w(TAG, "error parsing voice interaction service meta-data", e); @@ -145,4 +144,8 @@ public class VoiceInteractionServiceInfo { public String getSettingsActivity() { return mSettingsActivity; } + + public boolean getSupportsAssistGesture() { + return mSupportsAssistGesture; + } } diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java index 49ab797..4c31f80 100644 --- a/core/java/android/service/voice/VoiceInteractionSession.java +++ b/core/java/android/service/voice/VoiceInteractionSession.java @@ -44,6 +44,7 @@ import android.view.ViewTreeObserver; import android.view.WindowManager; import android.widget.FrameLayout; import com.android.internal.app.IVoiceInteractionManagerService; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.IVoiceInteractorCallback; import com.android.internal.app.IVoiceInteractorRequest; @@ -164,9 +165,10 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() { @Override - public void show(Bundle sessionArgs, int flags) { - mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_SHOW, - flags, sessionArgs)); + public void show(Bundle sessionArgs, int flags, + IVoiceInteractionSessionShowCallback showCallback) { + mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW, + flags, sessionArgs, showCallback)); } @Override @@ -425,9 +427,12 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { onHandleScreenshot((Bitmap) msg.obj); break; case MSG_SHOW: - if (DEBUG) Log.d(TAG, "doShow: args=" + msg.obj - + " flags=" + msg.arg1); - doShow((Bundle) msg.obj, msg.arg1); + args = (SomeArgs)msg.obj; + if (DEBUG) Log.d(TAG, "doShow: args=" + args.arg1 + + " flags=" + msg.arg1 + + " showCallback=" + args.arg2); + doShow((Bundle) args.arg1, msg.arg1, + (IVoiceInteractionSessionShowCallback) args.arg2); break; case MSG_HIDE: if (DEBUG) Log.d(TAG, "doHide"); @@ -536,7 +541,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { onCreate(args, startFlags); } - void doShow(Bundle args, int flags) { + void doShow(Bundle args, int flags, final IVoiceInteractionSessionShowCallback showCallback) { if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded + " mWindowVisible=" + mWindowVisible); @@ -561,6 +566,22 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mWindowVisible = true; mWindow.show(); } + if (showCallback != null) { + mRootView.invalidate(); + mRootView.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + mRootView.getViewTreeObserver().removeOnPreDrawListener(this); + try { + showCallback.onShown(); + } catch (RemoteException e) { + Log.w(TAG, "Error calling onShown", e); + } + return true; + } + }); + } } finally { mWindowWasVisible = true; mInShowWindow = false; @@ -595,7 +616,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mRootView = mInflater.inflate( com.android.internal.R.layout.voice_interaction_session, null); mRootView.setSystemUiVisibility( - View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); mWindow.setContentView(mRootView); mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer); @@ -729,7 +751,9 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback { mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme, mCallbacks, this, mDispatcherState, WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.BOTTOM, true); - mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); + mWindow.getWindow().addFlags( + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); initViews(); mWindow.getWindow().setLayout(MATCH_PARENT, MATCH_PARENT); mWindow.setToken(mToken); diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 66dae7b..54d78f3 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -223,6 +223,7 @@ public interface WindowManager extends ViewManager { @ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY, to = "TYPE_MAGNIFICATION_OVERLAY"), @ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION"), @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION, to = "TYPE_VOICE_INTERACTION"), + @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION_STARTING, to = "TYPE_VOICE_INTERACTION_STARTING"), }) public int type; @@ -549,6 +550,12 @@ public interface WindowManager extends ViewManager { public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32; /** + * Window type: Starting window for voice interaction layer. + * @hide + */ + public static final int TYPE_VOICE_INTERACTION_STARTING = FIRST_SYSTEM_WINDOW+33; + + /** * End of types of system windows. */ public static final int LAST_SYSTEM_WINDOW = 2999; diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 6450d52..d149c5b 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -16,9 +16,11 @@ package com.android.internal.app; +import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.SoundTrigger; @@ -80,4 +82,28 @@ interface IVoiceInteractionManagerService { */ int stopRecognition(in IVoiceInteractionService service, int keyphraseId, in IRecognitionStatusCallback callback); + + /** + * @return the component name for the currently active voice interaction service + */ + ComponentName getActiveServiceComponentName(); + + /** + * Shows the session for the currently active service. Used to start a new session from system + * affordances. + * + * @param showCallback callback to be notified when the session was shown + */ + void showSessionForActiveService(IVoiceInteractionSessionShowCallback showCallback); + + /** + * Indicates whether there is a voice session running (but not necessarily showing). + */ + boolean isSessionRunning(); + + /** + * Indicates whether the currently active voice interaction service is capable of handling the + * assist gesture. + */ + boolean activeServiceSupportsAssistGesture(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java b/core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl index 272c321..15fa89b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPanel.java +++ b/core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.systemui.statusbar; +package com.android.internal.app; -public interface StatusBarPanel { - public boolean isInContentArea(int x, int y); +oneway interface IVoiceInteractionSessionShowCallback { + void onFailed(); + void onShown(); } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b0771dd..f427f2b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3009,6 +3009,14 @@ android:description="@string/permdesc_bindCarrierMessagingService" android:protectionLevel="signature|system" /> + <!-- Allows an application to interact with the currently active + {@link android.service.voice.VoiceInteractionService}. + @hide --> + <permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" + android:protectionLevel="signature" + android:description="@string/permdesc_accessVoiceInteractionService" + android:label="@string/permlab_accessVoiceInteractionService" /> + <!-- The system process is explicitly the only one allowed to launch the confirmation UI for full backup/restore --> <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 674000b..a13282c 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -7216,6 +7216,9 @@ the component here for their recognition service. --> <attr name="recognitionService" format="string" /> <attr name="settingsActivity" /> + <!-- Flag indicating whether this voice interaction service is capable of handling the + assist gesture. --> + <attr name="supportsAssistGesture" format="boolean" /> </declare-styleable> <!-- Use <code>voice-enrollment-application</code> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index f59a4d8..f1707d2 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2658,4 +2658,5 @@ <public type="attr" name="autoVerify" /> <public type="attr" name="breakStrategy" /> + <public type="attr" name="supportsAssistGesture" /> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 6cd3139..4d90932 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2462,6 +2462,11 @@ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_bindCarrierMessagingService">Allows the holder to bind to the top-level interface of a carrier messaging service. Should never be needed for normal apps.</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_accessVoiceInteractionService">interact with voice interaction service</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_accessVoiceInteractionService">Allows the holder to interact with the currently active voice interaction service. Should never be needed for normal apps.</string> + <!-- Policy administration --> <!-- Title of policy access to limiting the user's password choices --> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 67c11b8..012c84c 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -120,6 +120,9 @@ <!-- Screen Capturing --> <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" /> + <!-- Assist --> + <uses-permission android:name="android.permission.ACCESS_VOICE_INTERACTION_SERVICE" /> + <application android:name=".SystemUIApplication" android:persistent="true" diff --git a/packages/SystemUI/res/drawable/assist_orb_navbar_scrim.xml b/packages/SystemUI/res/drawable/assist_orb_navbar_scrim.xml new file mode 100644 index 0000000..52ed76d --- /dev/null +++ b/packages/SystemUI/res/drawable/assist_orb_navbar_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="#33000000" + android:endColor="#00000000" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/search_panel_scrim.xml b/packages/SystemUI/res/drawable/assist_orb_scrim.xml index bbb2617..bbb2617 100644 --- a/packages/SystemUI/res/drawable/search_panel_scrim.xml +++ b/packages/SystemUI/res/drawable/assist_orb_scrim.xml diff --git a/packages/SystemUI/res/layout/assist_orb.xml b/packages/SystemUI/res/layout/assist_orb.xml new file mode 100644 index 0000000..ab0a0a5 --- /dev/null +++ b/packages/SystemUI/res/layout/assist_orb.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* apps/common/assets/default/default/skins/StatusBar.xml +** +** Copyright 2012, 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. +*/ +--> + +<!-- Extends FrameLayout --> +<com.android.systemui.assist.AssistOrbContainer + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.android.systemui.statusbar.AlphaOptimizedView + android:layout_width="match_parent" + android:layout_height="@dimen/assist_orb_scrim_height" + android:layout_gravity="bottom" + android:id="@+id/assist_orb_scrim" + android:background="@drawable/assist_orb_scrim"/> + + <com.android.systemui.assist.AssistOrbView + android:id="@+id/assist_orb" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/search_logo"/> + </com.android.systemui.assist.AssistOrbView> + + <com.android.systemui.statusbar.AlphaOptimizedView + android:id="@+id/assist_orb_navbar_scrim" + android:layout_height="@dimen/assist_orb_navbar_scrim_height" + android:layout_width="match_parent" + android:layout_gravity="bottom" + android:elevation="50dp" + android:outlineProvider="none" + android:background="@drawable/assist_orb_navbar_scrim"/> + +</com.android.systemui.assist.AssistOrbContainer>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_search_panel.xml b/packages/SystemUI/res/layout/status_bar_search_panel.xml deleted file mode 100644 index e0520ef..0000000 --- a/packages/SystemUI/res/layout/status_bar_search_panel.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* apps/common/assets/default/default/skins/StatusBar.xml -** -** Copyright 2012, 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. -*/ ---> - -<!-- Extends FrameLayout --> -<com.android.systemui.SearchPanelView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/search_panel_container" - android:layout_height="match_parent" - android:layout_width="match_parent"> - - <com.android.systemui.statusbar.AlphaOptimizedView - style="@style/SearchPanelScrim" - android:id="@+id/search_panel_scrim" - android:background="@drawable/search_panel_scrim" /> - - <com.android.systemui.SearchPanelCircleView - style="@style/SearchPanelCircle" - android:id="@+id/search_panel_circle"> - - <ImageView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:id="@+id/search_logo" /> - </com.android.systemui.SearchPanelCircleView> - -</com.android.systemui.SearchPanelView> diff --git a/packages/SystemUI/res/values-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml index e58fbb1..8919198 100644 --- a/packages/SystemUI/res/values-land/styles.xml +++ b/packages/SystemUI/res/values-land/styles.xml @@ -18,10 +18,4 @@ <style name="BrightnessDialogContainer" parent="@style/BaseBrightnessDialogContainer"> <item name="android:layout_width">360dp</item> </style> - - <style name="SearchPanelScrim"> - <item name="android:layout_width">@dimen/search_panel_scrim_height</item> - <item name="android:layout_height">match_parent</item> - <item name="android:layout_gravity">right</item> - </style> </resources> diff --git a/packages/SystemUI/res/values-sw600dp/styles.xml b/packages/SystemUI/res/values-sw600dp/styles.xml index 156fa65..4d7d6b5 100644 --- a/packages/SystemUI/res/values-sw600dp/styles.xml +++ b/packages/SystemUI/res/values-sw600dp/styles.xml @@ -19,12 +19,6 @@ <item name="android:layout_width">480dp</item> </style> - <style name="SearchPanelScrim"> - <item name="android:layout_width">match_parent</item> - <item name="android:layout_height">@dimen/search_panel_scrim_height</item> - <item name="android:layout_gravity">bottom</item> - </style> - <style name="UserDetailView"> <item name="numColumns">4</item> </style> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index b319344..ded7c4e 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -108,8 +108,7 @@ <color name="notification_guts_text_color">#b2FFFFFF</color> <color name="notification_guts_btn_color">#FFFFFFFF</color> - <color name="search_panel_circle_color">#ffffff</color> - <color name="search_panel_ripple_color">#ffbbbbbb</color> + <color name="assist_orb_color">#ffffff</color> <color name="keyguard_user_switcher_background_gradient_color">#77000000</color> <color name="doze_small_icon_background_color">#ff434343</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index c24cd64..c9e1fee 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -471,22 +471,23 @@ <dimen name="go_to_full_shade_appearing_translation">200dp</dimen> <!-- The diameter of the search panel circle. --> - <dimen name="search_panel_circle_size">88dp</dimen> + <dimen name="assist_orb_size">144dp</dimen> - <!-- The margin to the edge of the screen from where the circle starts to appear --> - <dimen name="search_panel_circle_base_margin">80dp</dimen> + <!-- The margin to the edge of the screen from where the orb starts to appear --> + <dimen name="assist_orb_base_margin">22dp</dimen> - <!-- The amount the circle translates when appearing --> - <dimen name="search_panel_circle_travel_distance">80dp</dimen> + <!-- The amount the orb translates when appearing --> + <dimen name="assist_orb_travel_distance">26dp</dimen> - <!-- The elevation of the search panel circle --> - <dimen name="search_panel_circle_elevation">12dp</dimen> + <!-- The elevation of the orb --> + <dimen name="assist_orb_elevation">12dp</dimen> - <!-- The height of the scrim behind the search panel circle. --> - <dimen name="search_panel_scrim_height">250dp</dimen> + <!-- The height of the scrim behind the orb. --> + <dimen name="assist_orb_scrim_height">250dp</dimen> - <!-- How far the user needs to drag up to invoke search. --> - <dimen name="search_panel_threshold">100dp</dimen> + <!-- The height of the scrim behind the search panel circle. Should be navigation_bar_height + + 8dp. --> + <dimen name="assist_orb_navbar_scrim_height">56dp</dimen> <!-- The width/height of the phone/camera/unlock icon view on keyguard. --> <dimen name="keyguard_affordance_height">56dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 83dceae..107a8ec 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -246,12 +246,6 @@ <item name="android:layout_height">match_parent</item> </style> - <style name="SearchPanelScrim"> - <item name="android:layout_width">match_parent</item> - <item name="android:layout_height">@dimen/search_panel_scrim_height</item> - <item name="android:layout_gravity">bottom</item> - </style> - <style name="UserDetailView"> <item name="numColumns">3</item> </style> diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java deleted file mode 100644 index f33e2b8..0000000 --- a/packages/SystemUI/src/com/android/systemui/SearchPanelCircleView.java +++ /dev/null @@ -1,592 +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 - */ - -package com.android.systemui; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.PropertyValuesHolder; -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Outline; -import android.graphics.Paint; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewOutlineProvider; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; -import android.view.animation.LinearInterpolator; -import android.widget.FrameLayout; -import android.widget.ImageView; -import com.android.systemui.statusbar.phone.PhoneStatusBar; - -import java.util.ArrayList; - -public class SearchPanelCircleView extends FrameLayout { - - private final int mCircleMinSize; - private final int mBaseMargin; - private final int mStaticOffset; - private final Paint mBackgroundPaint = new Paint(); - private final Paint mRipplePaint = new Paint(); - private final Rect mCircleRect = new Rect(); - private final Rect mStaticRect = new Rect(); - private final Interpolator mFastOutSlowInInterpolator; - private final Interpolator mAppearInterpolator; - private final Interpolator mDisappearInterpolator; - - private boolean mClipToOutline; - private final int mMaxElevation; - private boolean mAnimatingOut; - private float mOutlineAlpha; - private float mOffset; - private float mCircleSize; - private boolean mHorizontal; - private boolean mCircleHidden; - private ImageView mLogo; - private boolean mDraggedFarEnough; - private boolean mOffsetAnimatingIn; - private float mCircleAnimationEndValue; - private ArrayList<Ripple> mRipples = new ArrayList<Ripple>(); - - private ValueAnimator mOffsetAnimator; - private ValueAnimator mCircleAnimator; - private ValueAnimator mFadeOutAnimator; - private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener - = new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - applyCircleSize((float) animation.getAnimatedValue()); - updateElevation(); - } - }; - private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mCircleAnimator = null; - } - }; - private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener - = new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - setOffset((float) animation.getAnimatedValue()); - } - }; - - - public SearchPanelCircleView(Context context) { - this(context, null); - } - - public SearchPanelCircleView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public SearchPanelCircleView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - if (mCircleSize > 0.0f) { - outline.setOval(mCircleRect); - } else { - outline.setEmpty(); - } - outline.setAlpha(mOutlineAlpha); - } - }); - setWillNotDraw(false); - mCircleMinSize = context.getResources().getDimensionPixelSize( - R.dimen.search_panel_circle_size); - mBaseMargin = context.getResources().getDimensionPixelSize( - R.dimen.search_panel_circle_base_margin); - mStaticOffset = context.getResources().getDimensionPixelSize( - R.dimen.search_panel_circle_travel_distance); - mMaxElevation = context.getResources().getDimensionPixelSize( - R.dimen.search_panel_circle_elevation); - mAppearInterpolator = AnimationUtils.loadInterpolator(mContext, - android.R.interpolator.linear_out_slow_in); - mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext, - android.R.interpolator.fast_out_slow_in); - mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext, - android.R.interpolator.fast_out_linear_in); - mBackgroundPaint.setAntiAlias(true); - mBackgroundPaint.setColor(context.getColor(R.color.search_panel_circle_color)); - mRipplePaint.setColor(context.getColor(R.color.search_panel_ripple_color)); - mRipplePaint.setAntiAlias(true); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - drawBackground(canvas); - drawRipples(canvas); - } - - private void drawRipples(Canvas canvas) { - for (int i = 0; i < mRipples.size(); i++) { - Ripple ripple = mRipples.get(i); - ripple.draw(canvas); - } - } - - private void drawBackground(Canvas canvas) { - canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2, - mBackgroundPaint); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mLogo = (ImageView) findViewById(R.id.search_logo); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight()); - if (changed) { - updateCircleRect(mStaticRect, mStaticOffset, true); - } - } - - public void setCircleSize(float circleSize) { - setCircleSize(circleSize, false, null, 0, null); - } - - public void setCircleSize(float circleSize, boolean animated, final Runnable endRunnable, - int startDelay, Interpolator interpolator) { - boolean isAnimating = mCircleAnimator != null; - boolean animationPending = isAnimating && !mCircleAnimator.isRunning(); - boolean animatingOut = isAnimating && mCircleAnimationEndValue == 0; - if (animated || animationPending || animatingOut) { - if (isAnimating) { - if (circleSize == mCircleAnimationEndValue) { - return; - } - mCircleAnimator.cancel(); - } - mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize); - mCircleAnimator.addUpdateListener(mCircleUpdateListener); - mCircleAnimator.addListener(mClearAnimatorListener); - mCircleAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (endRunnable != null) { - endRunnable.run(); - } - } - }); - Interpolator desiredInterpolator = interpolator != null ? interpolator - : circleSize == 0 ? mDisappearInterpolator : mAppearInterpolator; - mCircleAnimator.setInterpolator(desiredInterpolator); - mCircleAnimator.setDuration(300); - mCircleAnimator.setStartDelay(startDelay); - mCircleAnimator.start(); - mCircleAnimationEndValue = circleSize; - } else { - if (isAnimating) { - float diff = circleSize - mCircleAnimationEndValue; - PropertyValuesHolder[] values = mCircleAnimator.getValues(); - values[0].setFloatValues(diff, circleSize); - mCircleAnimator.setCurrentPlayTime(mCircleAnimator.getCurrentPlayTime()); - mCircleAnimationEndValue = circleSize; - } else { - applyCircleSize(circleSize); - updateElevation(); - } - } - } - - private void applyCircleSize(float circleSize) { - mCircleSize = circleSize; - updateLayout(); - } - - private void updateElevation() { - float t = (mStaticOffset - mOffset) / (float) mStaticOffset; - t = 1.0f - Math.max(t, 0.0f); - float offset = t * mMaxElevation; - setElevation(offset); - } - - /** - * Sets the offset to the edge of the screen. By default this not not animated. - * - * @param offset The offset to apply. - */ - public void setOffset(float offset) { - setOffset(offset, false, 0, null, null); - } - - /** - * Sets the offset to the edge of the screen. - * - * @param offset The offset to apply. - * @param animate Whether an animation should be performed. - * @param startDelay The desired start delay if animated. - * @param interpolator The desired interpolator if animated. If null, - * a default interpolator will be taken designed for appearing or - * disappearing. - * @param endRunnable The end runnable which should be executed when the animation is finished. - */ - private void setOffset(float offset, boolean animate, int startDelay, - Interpolator interpolator, final Runnable endRunnable) { - if (!animate) { - mOffset = offset; - updateLayout(); - if (endRunnable != null) { - endRunnable.run(); - } - } else { - if (mOffsetAnimator != null) { - mOffsetAnimator.removeAllListeners(); - mOffsetAnimator.cancel(); - } - mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset); - mOffsetAnimator.addUpdateListener(mOffsetUpdateListener); - mOffsetAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mOffsetAnimator = null; - if (endRunnable != null) { - endRunnable.run(); - } - } - }); - Interpolator desiredInterpolator = interpolator != null ? - interpolator : offset == 0 ? mDisappearInterpolator : mAppearInterpolator; - mOffsetAnimator.setInterpolator(desiredInterpolator); - mOffsetAnimator.setStartDelay(startDelay); - mOffsetAnimator.setDuration(300); - mOffsetAnimator.start(); - mOffsetAnimatingIn = offset != 0; - } - } - - private void updateLayout() { - updateCircleRect(); - updateLogo(); - invalidateOutline(); - invalidate(); - updateClipping(); - } - - private void updateClipping() { - boolean clip = mCircleSize < mCircleMinSize || !mRipples.isEmpty(); - if (clip != mClipToOutline) { - setClipToOutline(clip); - mClipToOutline = clip; - } - } - - private void updateLogo() { - boolean exitAnimationRunning = mFadeOutAnimator != null; - Rect rect = exitAnimationRunning ? mCircleRect : mStaticRect; - float translationX = (rect.left + rect.right) / 2.0f - mLogo.getWidth() / 2.0f; - float translationY = (rect.top + rect.bottom) / 2.0f - mLogo.getHeight() / 2.0f; - float t = (mStaticOffset - mOffset) / (float) mStaticOffset; - if (!exitAnimationRunning) { - if (mHorizontal) { - translationX += t * mStaticOffset * 0.3f; - } else { - translationY += t * mStaticOffset * 0.3f; - } - float alpha = 1.0f-t; - alpha = Math.max((alpha - 0.5f) * 2.0f, 0); - mLogo.setAlpha(alpha); - } else { - translationY += (mOffset - mStaticOffset) / 2; - } - mLogo.setTranslationX(translationX); - mLogo.setTranslationY(translationY); - } - - private void updateCircleRect() { - updateCircleRect(mCircleRect, mOffset, false); - } - - private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) { - int left, top; - float circleSize = useStaticSize ? mCircleMinSize : mCircleSize; - if (mHorizontal) { - left = (int) (getWidth() - circleSize / 2 - mBaseMargin - offset); - top = (int) ((getHeight() - circleSize) / 2); - } else { - left = (int) (getWidth() - circleSize) / 2; - top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset); - } - rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize)); - } - - public void setHorizontal(boolean horizontal) { - mHorizontal = horizontal; - updateCircleRect(mStaticRect, mStaticOffset, true); - updateLayout(); - } - - public void setDragDistance(float distance) { - if (!mAnimatingOut && (!mCircleHidden || mDraggedFarEnough)) { - float circleSize = mCircleMinSize + rubberband(distance); - setCircleSize(circleSize); - } - - } - - private float rubberband(float diff) { - return (float) Math.pow(Math.abs(diff), 0.6f); - } - - public void startAbortAnimation(Runnable endRunnable) { - if (mAnimatingOut) { - if (endRunnable != null) { - endRunnable.run(); - } - return; - } - setCircleSize(0, true, null, 0, null); - setOffset(0, true, 0, null, endRunnable); - mCircleHidden = true; - } - - public void startEnterAnimation() { - if (mAnimatingOut) { - return; - } - applyCircleSize(0); - setOffset(0); - setCircleSize(mCircleMinSize, true, null, 50, null); - setOffset(mStaticOffset, true, 50, null, null); - mCircleHidden = false; - } - - - public void startExitAnimation(final Runnable endRunnable) { - if (!mHorizontal) { - float offset = getHeight() / 2.0f; - setOffset(offset - mBaseMargin, true, 50, mFastOutSlowInInterpolator, null); - float xMax = getWidth() / 2; - float yMax = getHeight() / 2; - float maxRadius = (float) Math.ceil(Math.hypot(xMax, yMax) * 2); - setCircleSize(maxRadius, true, null, 50, mFastOutSlowInInterpolator); - performExitFadeOutAnimation(50, 300, endRunnable); - } else { - - // when in landscape, we don't wan't the animation as it interferes with the general - // rotation animation to the homescreen. - endRunnable.run(); - } - } - - private void performExitFadeOutAnimation(int startDelay, int duration, - final Runnable endRunnable) { - mFadeOutAnimator = ValueAnimator.ofFloat(mBackgroundPaint.getAlpha() / 255.0f, 0.0f); - - // Linear since we are animating multiple values - mFadeOutAnimator.setInterpolator(new LinearInterpolator()); - mFadeOutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float animatedFraction = animation.getAnimatedFraction(); - float logoValue = animatedFraction > 0.5f ? 1.0f : animatedFraction / 0.5f; - logoValue = PhoneStatusBar.ALPHA_OUT.getInterpolation(1.0f - logoValue); - float backgroundValue = animatedFraction < 0.2f ? 0.0f : - PhoneStatusBar.ALPHA_OUT.getInterpolation((animatedFraction - 0.2f) / 0.8f); - backgroundValue = 1.0f - backgroundValue; - mBackgroundPaint.setAlpha((int) (backgroundValue * 255)); - mOutlineAlpha = backgroundValue; - mLogo.setAlpha(logoValue); - invalidateOutline(); - invalidate(); - } - }); - mFadeOutAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (endRunnable != null) { - endRunnable.run(); - } - mLogo.setAlpha(1.0f); - mBackgroundPaint.setAlpha(255); - mOutlineAlpha = 1.0f; - mFadeOutAnimator = null; - } - }); - mFadeOutAnimator.setStartDelay(startDelay); - mFadeOutAnimator.setDuration(duration); - mFadeOutAnimator.start(); - } - - public void setDraggedFarEnough(boolean farEnough) { - if (farEnough != mDraggedFarEnough) { - if (farEnough) { - if (mCircleHidden) { - startEnterAnimation(); - } - if (mOffsetAnimator == null) { - addRipple(); - } else { - postDelayed(new Runnable() { - @Override - public void run() { - addRipple(); - } - }, 100); - } - } else { - startAbortAnimation(null); - } - mDraggedFarEnough = farEnough; - } - - } - - private void addRipple() { - if (mRipples.size() > 1) { - // we only want 2 ripples at the time - return; - } - float xInterpolation, yInterpolation; - if (mHorizontal) { - xInterpolation = 0.75f; - yInterpolation = 0.5f; - } else { - xInterpolation = 0.5f; - yInterpolation = 0.75f; - } - float circleCenterX = mStaticRect.left * (1.0f - xInterpolation) - + mStaticRect.right * xInterpolation; - float circleCenterY = mStaticRect.top * (1.0f - yInterpolation) - + mStaticRect.bottom * yInterpolation; - float radius = Math.max(mCircleSize, mCircleMinSize * 1.25f) * 0.75f; - Ripple ripple = new Ripple(circleCenterX, circleCenterY, radius); - ripple.start(); - } - - public void reset() { - mDraggedFarEnough = false; - mAnimatingOut = false; - mCircleHidden = true; - mClipToOutline = false; - if (mFadeOutAnimator != null) { - mFadeOutAnimator.cancel(); - } - mBackgroundPaint.setAlpha(255); - mOutlineAlpha = 1.0f; - } - - /** - * Check if an animation is currently running - * - * @param enterAnimation Is the animating queried the enter animation. - */ - public boolean isAnimationRunning(boolean enterAnimation) { - return mOffsetAnimator != null && (enterAnimation == mOffsetAnimatingIn); - } - - public void performOnAnimationFinished(final Runnable runnable) { - if (mOffsetAnimator != null) { - mOffsetAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (runnable != null) { - runnable.run(); - } - } - }); - } else { - if (runnable != null) { - runnable.run(); - } - } - } - - public void setAnimatingOut(boolean animatingOut) { - mAnimatingOut = animatingOut; - } - - /** - * @return Whether the circle is currently launching to the search activity or aborting the - * interaction - */ - public boolean isAnimatingOut() { - return mAnimatingOut; - } - - @Override - public boolean hasOverlappingRendering() { - // not really true but it's ok during an animation, as it's never permanent - return false; - } - - private class Ripple { - float x; - float y; - float radius; - float endRadius; - float alpha; - - Ripple(float x, float y, float endRadius) { - this.x = x; - this.y = y; - this.endRadius = endRadius; - } - - void start() { - ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f); - - // Linear since we are animating multiple values - animator.setInterpolator(new LinearInterpolator()); - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - alpha = 1.0f - animation.getAnimatedFraction(); - alpha = mDisappearInterpolator.getInterpolation(alpha); - radius = mAppearInterpolator.getInterpolation(animation.getAnimatedFraction()); - radius *= endRadius; - invalidate(); - } - }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mRipples.remove(Ripple.this); - updateClipping(); - } - - public void onAnimationStart(Animator animation) { - mRipples.add(Ripple.this); - updateClipping(); - } - }); - animator.setDuration(400); - animator.start(); - } - - public void draw(Canvas canvas) { - mRipplePaint.setAlpha((int) (alpha * 255)); - canvas.drawCircle(x, y, radius, mRipplePaint); - } - } - -} diff --git a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java b/packages/SystemUI/src/com/android/systemui/SearchPanelView.java deleted file mode 100644 index 445b499..0000000 --- a/packages/SystemUI/src/com/android/systemui/SearchPanelView.java +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui; - -import android.app.ActivityOptions; -import android.app.SearchManager; -import android.content.ActivityNotFoundException; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.media.AudioAttributes; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.UserHandle; -import android.os.Vibrator; -import android.provider.Settings; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import com.android.systemui.statusbar.BaseStatusBar; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.StatusBarPanel; -import com.android.systemui.statusbar.phone.PhoneStatusBar; - -public class SearchPanelView extends FrameLayout implements StatusBarPanel { - - private static final String TAG = "SearchPanelView"; - private static final String ASSIST_ICON_METADATA_NAME = - "com.android.systemui.action_assist_icon"; - - private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) - .build(); - - private final Context mContext; - private BaseStatusBar mBar; - - private SearchPanelCircleView mCircle; - private ImageView mLogo; - private View mScrim; - - private int mThreshold; - private boolean mHorizontal; - - private boolean mLaunching; - private boolean mDragging; - private boolean mDraggedFarEnough; - private float mStartTouch; - private float mStartDrag; - private boolean mLaunchPending; - - public SearchPanelView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SearchPanelView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mContext = context; - mThreshold = context.getResources().getDimensionPixelSize(R.dimen.search_panel_threshold); - } - - private void startAssistActivity() { - if (!mBar.isDeviceProvisioned()) return; - - // Close Recent Apps if needed - mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL); - - final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) - .getAssistIntent(mContext, true, UserHandle.USER_CURRENT); - if (intent == null) return; - - try { - final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, - R.anim.search_launch_enter, R.anim.search_launch_exit); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - AsyncTask.execute(new Runnable() { - @Override - public void run() { - mContext.startActivityAsUser(intent, opts.toBundle(), - new UserHandle(UserHandle.USER_CURRENT)); - } - }); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "Activity not found for " + intent.getAction()); - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mCircle = (SearchPanelCircleView) findViewById(R.id.search_panel_circle); - mLogo = (ImageView) findViewById(R.id.search_logo); - mScrim = findViewById(R.id.search_panel_scrim); - } - - private void maybeSwapSearchIcon() { - Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) - .getAssistIntent(mContext, false, UserHandle.USER_CURRENT); - if (intent != null) { - ComponentName component = intent.getComponent(); - replaceDrawable(mLogo, component, ASSIST_ICON_METADATA_NAME); - } else { - mLogo.setImageDrawable(null); - } - } - - public void replaceDrawable(ImageView v, ComponentName component, String name) { - if (component != null) { - try { - PackageManager packageManager = mContext.getPackageManager(); - // Look for the search icon specified in the activity meta-data - Bundle metaData = packageManager.getActivityInfo( - component, PackageManager.GET_META_DATA).metaData; - if (metaData != null) { - int iconResId = metaData.getInt(name); - if (iconResId != 0) { - Resources res = packageManager.getResourcesForActivity(component); - v.setImageDrawable(res.getDrawable(iconResId)); - return; - } - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Failed to swap drawable; " - + component.flattenToShortString() + " not found", e); - } catch (Resources.NotFoundException nfe) { - Log.w(TAG, "Failed to swap drawable from " - + component.flattenToShortString(), nfe); - } - } - v.setImageDrawable(null); - } - - @Override - public boolean isInContentArea(int x, int y) { - return true; - } - - private void vibrate() { - Context context = getContext(); - if (Settings.System.getIntForUser(context.getContentResolver(), - Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0) { - Resources res = context.getResources(); - Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration), - VIBRATION_ATTRIBUTES); - } - } - - public void show(final boolean show, boolean animate) { - if (show) { - maybeSwapSearchIcon(); - if (getVisibility() != View.VISIBLE) { - setVisibility(View.VISIBLE); - vibrate(); - if (animate) { - startEnterAnimation(); - } else { - mScrim.setAlpha(1f); - } - } - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - } else { - if (animate) { - startAbortAnimation(); - } else { - setVisibility(View.INVISIBLE); - } - } - } - - private void startEnterAnimation() { - mCircle.startEnterAnimation(); - mScrim.setAlpha(0f); - mScrim.animate() - .alpha(1f) - .setDuration(300) - .setStartDelay(50) - .setInterpolator(PhoneStatusBar.ALPHA_IN) - .start(); - - } - - private void startAbortAnimation() { - mCircle.startAbortAnimation(new Runnable() { - @Override - public void run() { - mCircle.setAnimatingOut(false); - setVisibility(View.INVISIBLE); - } - }); - mCircle.setAnimatingOut(true); - mScrim.animate() - .alpha(0f) - .setDuration(300) - .setStartDelay(0) - .setInterpolator(PhoneStatusBar.ALPHA_OUT); - } - - public void hide(boolean animate) { - if (mBar != null) { - // This will indirectly cause show(false, ...) to get called - mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); - } else { - if (animate) { - startAbortAnimation(); - } else { - setVisibility(View.INVISIBLE); - } - } - } - - @Override - public boolean dispatchHoverEvent(MotionEvent event) { - // Ignore hover events outside of this panel bounds since such events - // generate spurious accessibility events with the panel content when - // tapping outside of it, thus confusing the user. - final int x = (int) event.getX(); - final int y = (int) event.getY(); - if (x >= 0 && x < getWidth() && y >= 0 && y < getHeight()) { - return super.dispatchHoverEvent(event); - } - return true; - } - - /** - * Whether the panel is showing, or, if it's animating, whether it will be - * when the animation is done. - */ - public boolean isShowing() { - return getVisibility() == View.VISIBLE && !mCircle.isAnimatingOut(); - } - - public void setBar(BaseStatusBar bar) { - mBar = bar; - } - - public boolean isAssistantAvailable() { - return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) - .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (mLaunching || mLaunchPending) { - return false; - } - int action = event.getActionMasked(); - switch (action) { - case MotionEvent.ACTION_DOWN: - mStartTouch = mHorizontal ? event.getX() : event.getY(); - mDragging = false; - mDraggedFarEnough = false; - mCircle.reset(); - break; - case MotionEvent.ACTION_MOVE: - float currentTouch = mHorizontal ? event.getX() : event.getY(); - if (getVisibility() == View.VISIBLE && !mDragging && - (!mCircle.isAnimationRunning(true /* enterAnimation */) - || Math.abs(mStartTouch - currentTouch) > mThreshold)) { - mStartDrag = currentTouch; - mDragging = true; - } - if (mDragging) { - float offset = Math.max(mStartDrag - currentTouch, 0.0f); - mCircle.setDragDistance(offset); - mDraggedFarEnough = Math.abs(mStartTouch - currentTouch) > mThreshold; - mCircle.setDraggedFarEnough(mDraggedFarEnough); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - if (mDraggedFarEnough) { - if (mCircle.isAnimationRunning(true /* enterAnimation */)) { - mLaunchPending = true; - mCircle.setAnimatingOut(true); - mCircle.performOnAnimationFinished(new Runnable() { - @Override - public void run() { - startExitAnimation(); - } - }); - } else { - startExitAnimation(); - } - } else { - startAbortAnimation(); - } - break; - } - return true; - } - - private void startExitAnimation() { - mLaunchPending = false; - if (mLaunching || getVisibility() != View.VISIBLE) { - return; - } - mLaunching = true; - startAssistActivity(); - vibrate(); - mCircle.setAnimatingOut(true); - mCircle.startExitAnimation(new Runnable() { - @Override - public void run() { - mLaunching = false; - mCircle.setAnimatingOut(false); - setVisibility(View.INVISIBLE); - } - }); - mScrim.animate() - .alpha(0f) - .setDuration(300) - .setStartDelay(0) - .setInterpolator(PhoneStatusBar.ALPHA_OUT); - } - - public void setHorizontal(boolean horizontal) { - mHorizontal = horizontal; - mCircle.setHorizontal(horizontal); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistGestureManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistGestureManager.java new file mode 100644 index 0000000..36be355 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistGestureManager.java @@ -0,0 +1,292 @@ +package com.android.systemui.assist; + +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.SearchManager; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.media.AudioAttributes; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.os.Vibrator; +import android.provider.Settings; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ImageView; + +import com.android.internal.app.IVoiceInteractionManagerService; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; +import com.android.systemui.R; +import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.phone.PhoneStatusBar; + +/** + * Class to manage everything around the assist gesture. + */ +public class AssistGestureManager { + + private static final String TAG = "AssistGestureManager"; + private static final String ASSIST_ICON_METADATA_NAME = + "com.android.systemui.action_assist_icon"; + + private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) + .build(); + + private static final long TIMEOUT_SERVICE = 2500; + private static final long TIMEOUT_ACTIVITY = 1000; + + private final Context mContext; + private final WindowManager mWindowManager; + private AssistOrbContainer mView; + private final PhoneStatusBar mBar; + private final IVoiceInteractionManagerService mVoiceInteractionManagerService; + + private IVoiceInteractionSessionShowCallback mShowCallback = + new IVoiceInteractionSessionShowCallback.Stub() { + + @Override + public void onFailed() throws RemoteException { + mView.post(mHideRunnable); + } + + @Override + public void onShown() throws RemoteException { + mView.post(mHideRunnable); + } + }; + + private Runnable mHideRunnable = new Runnable() { + @Override + public void run() { + mView.removeCallbacks(this); + mView.show(false /* show */, true /* animate */); + } + }; + + public AssistGestureManager(PhoneStatusBar bar, Context context) { + mContext = context; + mBar = bar; + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + mVoiceInteractionManagerService = IVoiceInteractionManagerService.Stub.asInterface( + ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); + } + + public void onConfigurationChanged() { + boolean visible = false; + if (mView != null) { + visible = mView.isShowing(); + mWindowManager.removeView(mView); + } + + mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate( + R.layout.assist_orb, null); + mView.setVisibility(View.GONE); + mView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + WindowManager.LayoutParams lp = getLayoutParams(); + mWindowManager.addView(mView, lp); + mBar.getNavigationBarView().setDelegateView(mView); + if (visible) { + mView.show(true /* show */, false /* animate */); + } + } + + public void onGestureInvoked(boolean vibrate) { + boolean isVoiceInteractorActive = getVoiceInteractorSupportsAssistGesture(); + if (!isVoiceInteractorActive && !isAssistantIntentAvailable()) { + return; + } + if (vibrate) { + vibrate(); + } + if (!isVoiceInteractorActive || !isVoiceSessionRunning()) { + showOrb(); + mView.postDelayed(mHideRunnable, isVoiceInteractorActive + ? TIMEOUT_SERVICE + : TIMEOUT_ACTIVITY); + } + startAssist(); + } + + private WindowManager.LayoutParams getLayoutParams() { + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height), + WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + if (ActivityManager.isHighEndGfx()) { + lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + } + lp.gravity = Gravity.BOTTOM | Gravity.START; + lp.setTitle("AssistPreviewPanel"); + lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; + return lp; + } + + private void showOrb() { + maybeSwapSearchIcon(); + mView.show(true /* show */, true /* animate */); + } + + private void startAssist() { + if (getVoiceInteractorSupportsAssistGesture()) { + startVoiceInteractor(); + } else { + startAssistActivity(); + } + } + + private void startAssistActivity() { + if (!mBar.isDeviceProvisioned()) { + return; + } + + // Close Recent Apps if needed + mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL | + CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL); + + final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, true, UserHandle.USER_CURRENT); + if (intent == null) { + return; + } + + try { + final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, + R.anim.search_launch_enter, R.anim.search_launch_exit); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + AsyncTask.execute(new Runnable() { + @Override + public void run() { + mContext.startActivityAsUser(intent, opts.toBundle(), + new UserHandle(UserHandle.USER_CURRENT)); + } + }); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Activity not found for " + intent.getAction()); + } + } + + private void startVoiceInteractor() { + try { + mVoiceInteractionManagerService.showSessionForActiveService(mShowCallback); + } catch (RemoteException e) { + Log.w(TAG, "Failed to call showSessionForActiveService", e); + } + } + + private boolean getVoiceInteractorSupportsAssistGesture() { + try { + return mVoiceInteractionManagerService.activeServiceSupportsAssistGesture(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to call activeServiceSupportsAssistGesture", e); + return false; + } + } + + private ComponentName getVoiceInteractorComponentName() { + try { + return mVoiceInteractionManagerService.getActiveServiceComponentName(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to call getActiveServiceComponentName", e); + return null; + } + } + + private boolean isVoiceSessionRunning() { + try { + return mVoiceInteractionManagerService.isSessionRunning(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to call isSessionRunning", e); + return false; + } + } + + public void destroy() { + mWindowManager.removeViewImmediate(mView); + } + + private void maybeSwapSearchIcon() { + Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, false, UserHandle.USER_CURRENT); + ComponentName component = null; + boolean isService = false; + if (getVoiceInteractorSupportsAssistGesture()) { + component = getVoiceInteractorComponentName(); + isService = true; + } else if (intent != null) { + component = intent.getComponent(); + } + if (component != null) { + replaceDrawable(mView.getOrb().getLogo(), component, ASSIST_ICON_METADATA_NAME, + isService); + } else { + mView.getOrb().getLogo().setImageDrawable(null); + } + } + + public void replaceDrawable(ImageView v, ComponentName component, String name, + boolean isService) { + if (component != null) { + try { + PackageManager packageManager = mContext.getPackageManager(); + // Look for the search icon specified in the activity meta-data + Bundle metaData = isService + ? packageManager.getServiceInfo( + component, PackageManager.GET_META_DATA).metaData + : packageManager.getActivityInfo( + component, PackageManager.GET_META_DATA).metaData; + if (metaData != null) { + int iconResId = metaData.getInt(name); + if (iconResId != 0) { + Resources res = packageManager.getResourcesForApplication( + component.getPackageName()); + v.setImageDrawable(res.getDrawable(iconResId)); + return; + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Failed to swap drawable; " + + component.flattenToShortString() + " not found", e); + } catch (Resources.NotFoundException nfe) { + Log.w(TAG, "Failed to swap drawable from " + + component.flattenToShortString(), nfe); + } + } + v.setImageDrawable(null); + } + + private void vibrate() { + if (Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.HAPTIC_FEEDBACK_ENABLED, 1, UserHandle.USER_CURRENT) != 0) { + Resources res = mContext.getResources(); + Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); + vibrator.vibrate(res.getInteger(R.integer.config_search_panel_view_vibration_duration), + VIBRATION_ATTRIBUTES); + } + } + + public boolean isAssistantIntentAvailable() { + return ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) + .getAssistIntent(mContext, false, UserHandle.USER_CURRENT) != null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java new file mode 100644 index 0000000..67017db --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbContainer.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.assist; + +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.widget.FrameLayout; + +import com.android.systemui.R; + +public class AssistOrbContainer extends FrameLayout { + + private static final long EXIT_START_DELAY = 150; + + private final Interpolator mLinearOutSlowInInterpolator; + private final Interpolator mFastOutLinearInInterpolator; + + private View mScrim; + private View mNavbarScrim; + private AssistOrbView mOrb; + + private boolean mAnimatingOut; + + public AssistOrbContainer(Context context) { + this(context, null); + } + + public AssistOrbContainer(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public AssistOrbContainer(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, + android.R.interpolator.linear_out_slow_in); + mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context, + android.R.interpolator.fast_out_slow_in); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mScrim = findViewById(R.id.assist_orb_scrim); + mNavbarScrim = findViewById(R.id.assist_orb_navbar_scrim); + mOrb = (AssistOrbView) findViewById(R.id.assist_orb); + } + + public void show(final boolean show, boolean animate) { + if (show) { + if (getVisibility() != View.VISIBLE) { + setVisibility(View.VISIBLE); + if (animate) { + startEnterAnimation(); + } else { + reset(); + } + } + } else { + if (animate) { + startExitAnimation(new Runnable() { + @Override + public void run() { + mAnimatingOut = false; + setVisibility(View.GONE); + } + }); + } else { + setVisibility(View.GONE); + } + } + } + + private void reset() { + mAnimatingOut = false; + mOrb.reset(); + mScrim.setAlpha(1f); + mNavbarScrim.setAlpha(1f); + } + + private void startEnterAnimation() { + if (mAnimatingOut) { + return; + } + mOrb.startEnterAnimation(); + mScrim.setAlpha(0f); + mNavbarScrim.setAlpha(0f); + post(new Runnable() { + @Override + public void run() { + mScrim.animate() + .alpha(1f) + .setDuration(300) + .setStartDelay(0) + .setInterpolator(mLinearOutSlowInInterpolator); + mNavbarScrim.animate() + .alpha(1f) + .setDuration(300) + .setStartDelay(0) + .setInterpolator(mLinearOutSlowInInterpolator); + } + }); + } + + private void startExitAnimation(final Runnable endRunnable) { + if (mAnimatingOut) { + if (endRunnable != null) { + endRunnable.run(); + } + return; + } + mAnimatingOut = true; + mOrb.startExitAnimation(EXIT_START_DELAY); + mScrim.animate() + .alpha(0f) + .setDuration(250) + .setStartDelay(EXIT_START_DELAY) + .setInterpolator(mFastOutLinearInInterpolator); + mNavbarScrim.animate() + .alpha(0f) + .setDuration(250) + .setStartDelay(EXIT_START_DELAY) + .setInterpolator(mFastOutLinearInInterpolator) + .withEndAction(endRunnable); + } + + /** + * Whether the panel is showing, or, if it's animating, whether it will be + * when the animation is done. + */ + public boolean isShowing() { + return getVisibility() == View.VISIBLE && !mAnimatingOut; + } + + public AssistOrbView getOrb() { + return mOrb; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java new file mode 100644 index 0000000..a3372a8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbView.java @@ -0,0 +1,285 @@ +/* + * 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.assist; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import android.view.animation.OvershootInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.android.systemui.R; + +public class AssistOrbView extends FrameLayout { + + private final int mCircleMinSize; + private final int mBaseMargin; + private final int mStaticOffset; + private final Paint mBackgroundPaint = new Paint(); + private final Rect mCircleRect = new Rect(); + private final Rect mStaticRect = new Rect(); + private final Interpolator mAppearInterpolator; + private final Interpolator mDisappearInterpolator; + private final Interpolator mOvershootInterpolator = new OvershootInterpolator(); + + private boolean mClipToOutline; + private final int mMaxElevation; + private float mOutlineAlpha; + private float mOffset; + private float mCircleSize; + private ImageView mLogo; + private float mCircleAnimationEndValue; + + private ValueAnimator mOffsetAnimator; + private ValueAnimator mCircleAnimator; + + private ValueAnimator.AnimatorUpdateListener mCircleUpdateListener + = new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + applyCircleSize((float) animation.getAnimatedValue()); + updateElevation(); + } + }; + private AnimatorListenerAdapter mClearAnimatorListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCircleAnimator = null; + } + }; + private ValueAnimator.AnimatorUpdateListener mOffsetUpdateListener + = new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mOffset = (float) animation.getAnimatedValue(); + updateLayout(); + } + }; + + + public AssistOrbView(Context context) { + this(context, null); + } + + public AssistOrbView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public AssistOrbView(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + if (mCircleSize > 0.0f) { + outline.setOval(mCircleRect); + } else { + outline.setEmpty(); + } + outline.setAlpha(mOutlineAlpha); + } + }); + setWillNotDraw(false); + mCircleMinSize = context.getResources().getDimensionPixelSize( + R.dimen.assist_orb_size); + mBaseMargin = context.getResources().getDimensionPixelSize( + R.dimen.assist_orb_base_margin); + mStaticOffset = context.getResources().getDimensionPixelSize( + R.dimen.assist_orb_travel_distance); + mMaxElevation = context.getResources().getDimensionPixelSize( + R.dimen.assist_orb_elevation); + mAppearInterpolator = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.linear_out_slow_in); + mDisappearInterpolator = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.fast_out_linear_in); + mBackgroundPaint.setAntiAlias(true); + mBackgroundPaint.setColor(getResources().getColor(R.color.assist_orb_color)); + } + + public ImageView getLogo() { + return mLogo; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + drawBackground(canvas); + } + + private void drawBackground(Canvas canvas) { + canvas.drawCircle(mCircleRect.centerX(), mCircleRect.centerY(), mCircleSize / 2, + mBackgroundPaint); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mLogo = (ImageView) findViewById(R.id.search_logo); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + mLogo.layout(0, 0, mLogo.getMeasuredWidth(), mLogo.getMeasuredHeight()); + if (changed) { + updateCircleRect(mStaticRect, mStaticOffset, true); + } + } + + public void animateCircleSize(float circleSize, long duration, + long startDelay, Interpolator interpolator) { + if (circleSize == mCircleAnimationEndValue) { + return; + } + if (mCircleAnimator != null) { + mCircleAnimator.cancel(); + } + mCircleAnimator = ValueAnimator.ofFloat(mCircleSize, circleSize); + mCircleAnimator.addUpdateListener(mCircleUpdateListener); + mCircleAnimator.addListener(mClearAnimatorListener); + mCircleAnimator.setInterpolator(interpolator); + mCircleAnimator.setDuration(duration); + mCircleAnimator.setStartDelay(startDelay); + mCircleAnimator.start(); + mCircleAnimationEndValue = circleSize; + } + + private void applyCircleSize(float circleSize) { + mCircleSize = circleSize; + updateLayout(); + } + + private void updateElevation() { + float t = (mStaticOffset - mOffset) / (float) mStaticOffset; + t = 1.0f - Math.max(t, 0.0f); + float offset = t * mMaxElevation; + setElevation(offset); + } + + /** + * Animates the offset to the edge of the screen. + * + * @param offset The offset to apply. + * @param startDelay The desired start delay if animated. + * + * @param interpolator The desired interpolator if animated. If null, + * a default interpolator will be taken designed for appearing or + * disappearing. + */ + private void animateOffset(float offset, long duration, long startDelay, + Interpolator interpolator) { + if (mOffsetAnimator != null) { + mOffsetAnimator.removeAllListeners(); + mOffsetAnimator.cancel(); + } + mOffsetAnimator = ValueAnimator.ofFloat(mOffset, offset); + mOffsetAnimator.addUpdateListener(mOffsetUpdateListener); + mOffsetAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mOffsetAnimator = null; + } + }); + mOffsetAnimator.setInterpolator(interpolator); + mOffsetAnimator.setStartDelay(startDelay); + mOffsetAnimator.setDuration(duration); + mOffsetAnimator.start(); + } + + private void updateLayout() { + updateCircleRect(); + updateLogo(); + invalidateOutline(); + invalidate(); + updateClipping(); + } + + private void updateClipping() { + boolean clip = mCircleSize < mCircleMinSize; + if (clip != mClipToOutline) { + setClipToOutline(clip); + mClipToOutline = clip; + } + } + + private void updateLogo() { + float translationX = (mCircleRect.left + mCircleRect.right) / 2.0f - mLogo.getWidth() / 2.0f; + float translationY = (mCircleRect.top + mCircleRect.bottom) / 2.0f + - mLogo.getHeight() / 2.0f - mCircleMinSize / 7f; + float t = (mStaticOffset - mOffset) / (float) mStaticOffset; + translationY += t * mStaticOffset * 0.1f; + float alpha = 1.0f-t; + alpha = Math.max((alpha - 0.5f) * 2.0f, 0); + mLogo.setImageAlpha((int) (alpha * 255)); + mLogo.setTranslationX(translationX); + mLogo.setTranslationY(translationY); + } + + private void updateCircleRect() { + updateCircleRect(mCircleRect, mOffset, false); + } + + private void updateCircleRect(Rect rect, float offset, boolean useStaticSize) { + int left, top; + float circleSize = useStaticSize ? mCircleMinSize : mCircleSize; + left = (int) (getWidth() - circleSize) / 2; + top = (int) (getHeight() - circleSize / 2 - mBaseMargin - offset); + rect.set(left, top, (int) (left + circleSize), (int) (top + circleSize)); + } + + public void startExitAnimation(long delay) { + animateCircleSize(0, 200, delay, mDisappearInterpolator); + animateOffset(0, 200, delay, mDisappearInterpolator); + } + + public void startEnterAnimation() { + applyCircleSize(0); + post(new Runnable() { + @Override + public void run() { + animateCircleSize(mCircleMinSize, 300, 0 /* delay */, mOvershootInterpolator); + animateOffset(mStaticOffset, 400, 0 /* delay */, mAppearInterpolator); + } + }); + } + + public void reset() { + mClipToOutline = false; + mBackgroundPaint.setAlpha(255); + mOutlineAlpha = 1.0f; + } + + @Override + public boolean hasOverlappingRendering() { + // not really true but it's ok during an animation, as it's never permanent + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 55bdcac..f75dd73 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -71,7 +71,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; import android.view.ViewParent; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -79,7 +78,6 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.AnimationUtils; import android.widget.DateTimeView; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.RemoteViews; import android.widget.TextView; @@ -90,7 +88,6 @@ import com.android.internal.util.NotificationColorUtil; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; import com.android.systemui.RecentsComponent; -import com.android.systemui.SearchPanelView; import com.android.systemui.SwipeHelper; import com.android.systemui.SystemUI; import com.android.systemui.recents.Recents; @@ -132,7 +129,6 @@ public abstract class BaseStatusBar extends SystemUI implements protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; protected static final int MSG_SHOW_NEXT_AFFILIATED_TASK = 1024; protected static final int MSG_SHOW_PREV_AFFILIATED_TASK = 1025; - protected static final int MSG_CLOSE_SEARCH_PANEL = 1027; protected static final int MSG_SHOW_HEADS_UP = 1028; protected static final int MSG_HIDE_HEADS_UP = 1029; protected static final int MSG_ESCALATE_HEADS_UP = 1030; @@ -164,9 +160,6 @@ public abstract class BaseStatusBar extends SystemUI implements protected HeadsUpNotificationView mHeadsUpNotificationView; protected int mHeadsUpNotificationDecay; - // Search panel - protected SearchPanelView mSearchPanelView; - protected int mCurrentUserId = 0; final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>(); @@ -1043,50 +1036,6 @@ public abstract class BaseStatusBar extends SystemUI implements mHandler.sendEmptyMessage(msg); } - @Override - public void showSearchPanel() { - if (mSearchPanelView != null && mSearchPanelView.isAssistantAvailable()) { - mSearchPanelView.show(true, true); - } - } - - @Override - public void hideSearchPanel() { - int msg = MSG_CLOSE_SEARCH_PANEL; - mHandler.removeMessages(msg); - mHandler.sendEmptyMessage(msg); - } - - protected abstract WindowManager.LayoutParams getSearchLayoutParams( - LayoutParams layoutParams); - - protected void updateSearchPanel() { - // Search Panel - boolean visible = false; - if (mSearchPanelView != null) { - visible = mSearchPanelView.isShowing(); - mWindowManager.removeView(mSearchPanelView); - } - - // Provide SearchPanel with a temporary parent to allow layout params to work. - LinearLayout tmpRoot = new LinearLayout(mContext); - mSearchPanelView = (SearchPanelView) LayoutInflater.from(mContext).inflate( - R.layout.status_bar_search_panel, tmpRoot, false); - mSearchPanelView.setOnTouchListener( - new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); - mSearchPanelView.setVisibility(View.GONE); - boolean vertical = mNavigationBarView != null && mNavigationBarView.isVertical(); - mSearchPanelView.setHorizontal(vertical); - - WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); - - mWindowManager.addView(mSearchPanelView, lp); - mSearchPanelView.setBar(this); - if (visible) { - mSearchPanelView.show(true, false); - } - } - protected H createHandler() { return new H(); } @@ -1263,35 +1212,7 @@ public abstract class BaseStatusBar extends SystemUI implements case MSG_SHOW_PREV_AFFILIATED_TASK: showRecentsPreviousAffiliatedTask(); break; - case MSG_CLOSE_SEARCH_PANEL: - if (DEBUG) Log.d(TAG, "closing search panel"); - if (mSearchPanelView != null && mSearchPanelView.isShowing()) { - mSearchPanelView.show(false, true); - } - break; - } - } - } - - public class TouchOutsideListener implements View.OnTouchListener { - private int mMsg; - private StatusBarPanel mPanel; - - public TouchOutsideListener(int msg, StatusBarPanel panel) { - mMsg = msg; - mPanel = panel; - } - - public boolean onTouch(View v, MotionEvent ev) { - final int action = ev.getAction(); - if (action == MotionEvent.ACTION_OUTSIDE - || (action == MotionEvent.ACTION_DOWN - && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { - mHandler.removeMessages(mMsg); - mHandler.sendEmptyMessage(mMsg); - return true; } - return false; } } @@ -1935,7 +1856,7 @@ public abstract class BaseStatusBar extends SystemUI implements protected abstract void setAreThereNotifications(); protected abstract void updateNotifications(); - protected abstract boolean shouldDisableNavbarGestures(); + public abstract boolean shouldDisableNavbarGestures(); public abstract void addNotification(StatusBarNotification notification, RankingMap ranking, Entry oldEntry); @@ -2241,9 +2162,6 @@ public abstract class BaseStatusBar extends SystemUI implements } public void destroy() { - if (mSearchPanelView != null) { - mWindowManager.removeViewImmediate(mSearchPanelView); - } mContext.unregisterReceiver(mBroadcastReceiver); try { mNotificationListener.unregisterAsSystemService(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 8f88e73..7aa9a90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -96,8 +96,6 @@ public class CommandQueue extends IStatusBar.Stub { public void toggleRecentApps(); public void preloadRecentApps(); public void cancelPreloadRecentApps(); - public void showSearchPanel(); - public void hideSearchPanel(); public void setWindowState(int window, int state); public void buzzBeepBlinked(); public void notificationLightOff(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java index 7ae6764..9e2207e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java @@ -22,11 +22,12 @@ import android.graphics.RectF; import android.view.MotionEvent; import android.view.View; import com.android.systemui.R; +import com.android.systemui.statusbar.phone.PhoneStatusBar; public class DelegateViewHelper { private View mDelegateView; private View mSourceView; - private BaseStatusBar mBar; + private PhoneStatusBar mBar; private int[] mTempPoint = new int[2]; private float[] mDownPoint = new float[2]; private float mTriggerThreshhold; @@ -45,7 +46,7 @@ public class DelegateViewHelper { mDelegateView = view; } - public void setBar(BaseStatusBar phoneStatusBar) { + public void setBar(PhoneStatusBar phoneStatusBar) { mBar = phoneStatusBar; } @@ -79,7 +80,7 @@ public class DelegateViewHelper { float y = k < historySize ? event.getHistoricalY(k) : event.getY(); final float distance = mSwapXY ? (mDownPoint[0] - x) : (mDownPoint[1] - y); if (distance > mTriggerThreshhold) { - mBar.showSearchPanel(); + mBar.invokeAssistGesture(false /* vibrate */); mPanelShowing = true; break; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 12ff399..c62ad66 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -50,6 +50,8 @@ import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.DelegateViewHelper; import com.android.systemui.statusbar.policy.DeadZone; +import com.android.systemui.statusbar.policy.KeyButtonView; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -195,7 +197,7 @@ public class NavigationBarView extends LinearLayout { mDelegateHelper.setDelegateView(view); } - public void setBar(BaseStatusBar phoneStatusBar) { + public void setBar(PhoneStatusBar phoneStatusBar) { mTaskSwitchHelper.setBar(phoneStatusBar); mDelegateHelper.setBar(phoneStatusBar); } @@ -261,8 +263,8 @@ public class NavigationBarView extends LinearLayout { return mCurrentView.findViewById(R.id.back); } - public View getHomeButton() { - return mCurrentView.findViewById(R.id.home); + public KeyButtonView getHomeButton() { + return (KeyButtonView) mCurrentView.findViewById(R.id.home); } public View getImeSwitchButton() { 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 f046c63..ad78f6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -120,6 +120,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.qs.QSPanel; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.statusbar.ActivatableNotificationView; +import com.android.systemui.assist.AssistGestureManager; import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; @@ -320,6 +321,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, private int mNavigationIconHints = 0; private HandlerThread mHandlerThread; + private AssistGestureManager mAssistGestureManager; + // ensure quick settings is disabled until the current user makes it through the setup wizard private boolean mUserSetup = false; private ContentObserver mUserSetupObserver = new ContentObserver(new Handler()) { @@ -638,8 +641,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, new NavigationBarView.OnVerticalChangedListener() { @Override public void onVerticalChanged(boolean isVertical) { - if (mSearchPanelView != null) { - mSearchPanelView.setHorizontal(isVertical); + if (mAssistGestureManager != null) { + mAssistGestureManager.onConfigurationChanged(); } mNotificationPanel.setQsScrimEnabled(!isVertical); } @@ -830,6 +833,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mBroadcastReceiver.onReceive(mContext, new Intent(pm.isScreenOn() ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF)); + mAssistGestureManager = new AssistGestureManager(this, context); + // receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); @@ -949,60 +954,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, return mStatusBarWindow; } - @Override - protected WindowManager.LayoutParams getSearchLayoutParams(LayoutParams layoutParams) { - boolean opaque = false; - WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - LayoutParams.MATCH_PARENT, - LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - (opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT)); - if (ActivityManager.isHighEndGfx()) { - lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; - } - lp.gravity = Gravity.BOTTOM | Gravity.START; - lp.setTitle("SearchPanel"); - lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED - | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; - return lp; - } - - @Override - protected void updateSearchPanel() { - super.updateSearchPanel(); - if (mNavigationBarView != null) { - mNavigationBarView.setDelegateView(mSearchPanelView); - } - } - - @Override - public void showSearchPanel() { - super.showSearchPanel(); - mHandler.removeCallbacks(mShowSearchPanel); - - // we want to freeze the sysui state wherever it is - mSearchPanelView.setSystemUiVisibility(mSystemUiVisibility); - - if (mNavigationBarView != null) { - WindowManager.LayoutParams lp = - (android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams(); - lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; - mWindowManager.updateViewLayout(mNavigationBarView, lp); - } - } - - @Override - public void hideSearchPanel() { - super.hideSearchPanel(); - if (mNavigationBarView != null) { - WindowManager.LayoutParams lp = - (android.view.WindowManager.LayoutParams) mNavigationBarView.getLayoutParams(); - lp.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; - mWindowManager.updateViewLayout(mNavigationBarView, lp); - } + public void invokeAssistGesture(boolean vibrate) { + mHandler.removeCallbacks(mInvokeAssist); + mAssistGestureManager.onGestureInvoked(vibrate); } public int getStatusBarHeight() { @@ -1032,30 +986,33 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, }; private int mShowSearchHoldoff = 0; - private Runnable mShowSearchPanel = new Runnable() { + private Runnable mInvokeAssist = new Runnable() { public void run() { - showSearchPanel(); + invokeAssistGesture(true /* vibrate */); awakenDreams(); + if (mNavigationBarView != null) { + mNavigationBarView.getHomeButton().abortCurrentGesture(); + } } }; View.OnTouchListener mHomeActionListener = new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { - switch(event.getAction()) { + switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - if (!shouldDisableNavbarGestures()) { - mHandler.removeCallbacks(mShowSearchPanel); - mHandler.postDelayed(mShowSearchPanel, mShowSearchHoldoff); - } - break; + if (!shouldDisableNavbarGestures()) { + mHandler.removeCallbacks(mInvokeAssist); + mHandler.postDelayed(mInvokeAssist, mShowSearchHoldoff); + } + break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mHandler.removeCallbacks(mShowSearchPanel); - awakenDreams(); - break; - } - return false; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mHandler.removeCallbacks(mInvokeAssist); + awakenDreams(); + break; + } + return false; } }; @@ -1079,7 +1036,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNavigationBarView.getBackButton().setLongClickable(true); mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener); mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener); - updateSearchPanel(); + mAssistGestureManager.onConfigurationChanged(); } // For small-screen devices (read: phones) that lack hardware navigation buttons @@ -2056,11 +2013,6 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - if ((flags & CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL) == 0) { - mHandler.removeMessages(MSG_CLOSE_SEARCH_PANEL); - mHandler.sendEmptyMessage(MSG_CLOSE_SEARCH_PANEL); - } - if (mStatusBarWindow != null) { // release focus immediately to kick off focus change transition mStatusBarWindowManager.setStatusBarFocusable(false); @@ -2983,7 +2935,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, }; @Override - protected boolean shouldDisableNavbarGestures() { + public boolean shouldDisableNavbarGestures() { return !isDeviceProvisioned() || mExpandedVisible || (mDisabled & StatusBarManager.DISABLE_SEARCH) != 0; @@ -3052,6 +3004,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mHandlerThread = null; } mContext.unregisterReceiver(mBroadcastReceiver); + mAssistGestureManager.destroy(); } private boolean mDemoModeAllowed; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index a18daed..6bc51fa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -48,6 +48,7 @@ public class KeyButtonView extends ImageView { private int mTouchSlop; private boolean mSupportsLongpress = true; private AudioManager mAudioManager; + private boolean mGestureAborted; private final Runnable mCheckLongPress = new Runnable() { public void run() { @@ -126,10 +127,15 @@ public class KeyButtonView extends ImageView { public boolean onTouchEvent(MotionEvent ev) { final int action = ev.getAction(); int x, y; + if (action == MotionEvent.ACTION_DOWN) { + mGestureAborted = false; + } + if (mGestureAborted) { + return false; + } switch (action) { case MotionEvent.ACTION_DOWN: - //Log.d("KeyButtonView", "press"); mDownTime = SystemClock.uptimeMillis(); setPressed(true); if (mCode != 0) { @@ -203,6 +209,11 @@ public class KeyButtonView extends ImageView { InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } + + public void abortCurrentGesture() { + setPressed(false); + mGestureAborted = true; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index d1e1b71..c272e48 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -106,12 +106,6 @@ public class TvStatusBar extends BaseStatusBar { } @Override - protected WindowManager.LayoutParams getSearchLayoutParams( - LayoutParams layoutParams) { - return null; - } - - @Override protected void setAreThereNotifications() { } @@ -120,7 +114,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - protected boolean shouldDisableNavbarGestures() { + public boolean shouldDisableNavbarGestures() { return true; } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 958caea..ed14569 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1922,6 +1922,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { case TYPE_PHONE: return 3; case TYPE_SEARCH_BAR: + case TYPE_VOICE_INTERACTION_STARTING: return 4; case TYPE_VOICE_INTERACTION: // voice interaction layer is almost immediately above apps. @@ -2270,16 +2271,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar); break; case TYPE_NAVIGATION_BAR_PANEL: - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.STATUS_BAR_SERVICE, - "PhoneWindowManager"); - break; case TYPE_STATUS_BAR_PANEL: - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.STATUS_BAR_SERVICE, - "PhoneWindowManager"); - break; case TYPE_STATUS_BAR_SUB_PANEL: + case TYPE_VOICE_INTERACTION_STARTING: mContext.enforceCallingOrSelfPermission( android.Manifest.permission.STATUS_BAR_SERVICE, "PhoneWindowManager"); @@ -3657,7 +3651,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop + mOverscanScreenHeight; } - } else if (attrs.type == TYPE_INPUT_METHOD || attrs.type == TYPE_VOICE_INTERACTION) { + } else if (attrs.type == TYPE_INPUT_METHOD) { pf.left = df.left = of.left = cf.left = vf.left = mDockLeft; pf.top = df.top = of.top = cf.top = vf.top = mDockTop; pf.right = df.right = of.right = cf.right = vf.right = mDockRight; @@ -3668,6 +3662,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { // IM dock windows always go to the bottom of the screen. attrs.gravity = Gravity.BOTTOM; mDockLayer = win.getSurfaceLayer(); + } else if (attrs.type == TYPE_VOICE_INTERACTION) { + pf.left = df.left = of.left = cf.left = vf.left = mUnrestrictedScreenLeft; + pf.top = df.top = of.top = mUnrestrictedScreenTop; + pf.right = df.right = of.right = cf.right = vf.right = mUnrestrictedScreenLeft + + mUnrestrictedScreenWidth; + pf.bottom = df.bottom = of.bottom = cf.bottom = mUnrestrictedScreenTop + + mUnrestrictedScreenHeight; + cf.bottom = vf.bottom = mStableBottom; + cf.top = vf.top = mStableTop; } else if (win == mStatusBar && (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) { pf.left = df.left = of.left = mUnrestrictedScreenLeft; pf.top = df.top = of.top = mUnrestrictedScreenTop; @@ -3907,6 +3910,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0 && (attrs.type == TYPE_STATUS_BAR || attrs.type == TYPE_TOAST + || attrs.type == TYPE_VOICE_INTERACTION_STARTING || (attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) { // Asking for layout as if the nav bar is hidden, lets the diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index f032ccf..8a28d51 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -50,6 +50,7 @@ import android.text.TextUtils; import android.util.Slog; import com.android.internal.app.IVoiceInteractionManagerService; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; @@ -396,7 +397,8 @@ public class VoiceInteractionManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - mImpl.showSessionLocked(callingPid, callingUid, args, flags); + mImpl.showSessionLocked(callingPid, callingUid, args, flags, + null /* showCallback */); } finally { Binder.restoreCallingIdentity(caller); } @@ -434,7 +436,8 @@ public class VoiceInteractionManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); final long caller = Binder.clearCallingIdentity(); try { - return mImpl.showSessionLocked(callingPid, callingUid, sessionArgs, flags); + return mImpl.showSessionLocked(callingPid, callingUid, sessionArgs, flags, + null /* showCallback */); } finally { Binder.restoreCallingIdentity(caller); } @@ -518,13 +521,7 @@ public class VoiceInteractionManagerService extends SystemService { @Override public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) { - synchronized (this) { - if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Caller does not hold the permission " - + Manifest.permission.MANAGE_VOICE_KEYPHRASES); - } - } + enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES); if (bcp47Locale == null) { throw new IllegalArgumentException("Illegal argument(s) in getKeyphraseSoundModel"); @@ -541,15 +538,9 @@ public class VoiceInteractionManagerService extends SystemService { @Override public int updateKeyphraseSoundModel(KeyphraseSoundModel model) { - synchronized (this) { - if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Caller does not hold the permission " - + Manifest.permission.MANAGE_VOICE_KEYPHRASES); - } - if (model == null) { - throw new IllegalArgumentException("Model must not be null"); - } + enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES); + if (model == null) { + throw new IllegalArgumentException("Model must not be null"); } final long caller = Binder.clearCallingIdentity(); @@ -572,13 +563,7 @@ public class VoiceInteractionManagerService extends SystemService { @Override public int deleteKeyphraseSoundModel(int keyphraseId, String bcp47Locale) { - synchronized (this) { - if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Caller does not hold the permission " - + Manifest.permission.MANAGE_VOICE_KEYPHRASES); - } - } + enforceCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES); if (bcp47Locale == null) { throw new IllegalArgumentException( @@ -707,6 +692,54 @@ public class VoiceInteractionManagerService extends SystemService { } @Override + public ComponentName getActiveServiceComponentName() { + enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); + synchronized (this) { + return mImpl != null ? mImpl.mComponent : null; + } + } + + @Override + public void showSessionForActiveService(IVoiceInteractionSessionShowCallback showCallback) { + enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); + synchronized (this) { + if (mImpl == null) { + Slog.w(TAG, "showSessionForActiveService without running voice interaction" + + "service"); + return; + } + final int callingPid = Binder.getCallingPid(); + final int callingUid = Binder.getCallingUid(); + final long caller = Binder.clearCallingIdentity(); + try { + mImpl.showSessionLocked(callingPid, callingUid, new Bundle() /* sessionArgs */, + VoiceInteractionService.START_SOURCE_ASSIST_GESTURE + | VoiceInteractionService.START_WITH_ASSIST + | VoiceInteractionService.START_WITH_SCREENSHOT, + showCallback); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + } + + @Override + public boolean isSessionRunning() { + enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); + synchronized (this) { + return mImpl != null && mImpl.mActiveSession != null; + } + } + + @Override + public boolean activeServiceSupportsAssistGesture() { + enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); + synchronized (this) { + return mImpl != null && mImpl.mInfo.getSupportsAssistGesture(); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -726,6 +759,12 @@ public class VoiceInteractionManagerService extends SystemService { mSoundTriggerHelper.dump(fd, pw, args); } + private void enforceCallingPermission(String permission) { + if (mContext.checkCallingPermission(permission) != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Caller does not hold the permission " + permission); + } + } + class SettingsObserver extends ContentObserver { SettingsObserver(Handler handler) { super(handler); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 5a91b88..1aa0d0b 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -39,6 +39,7 @@ import android.service.voice.VoiceInteractionServiceInfo; import android.util.Slog; import android.view.IWindowManager; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import java.io.FileDescriptor; @@ -134,12 +135,13 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mContext.registerReceiver(mBroadcastReceiver, filter, null, handler); } - public boolean showSessionLocked(int callingPid, int callingUid, Bundle args, int flags) { + public boolean showSessionLocked(int callingPid, int callingUid, Bundle args, int flags, + IVoiceInteractionSessionShowCallback showCallback) { if (mActiveSession == null) { mActiveSession = new VoiceInteractionSessionConnection(mLock, mSessionComponentName, mUser, mContext, this, callingPid, callingUid); } - return mActiveSession.showLocked(args, flags); + return mActiveSession.showLocked(args, flags, showCallback); } public boolean hideSessionLocked(int callingPid, int callingUid) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java index 7a379c2..73c7363 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -41,10 +41,12 @@ import android.util.Slog; import android.view.IWindowManager; import android.view.WindowManager; import com.android.internal.app.IAssistScreenshotReceiver; +import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.IResultReceiver; import java.io.PrintWriter; +import java.util.ArrayList; final class VoiceInteractionSessionConnection implements ServiceConnection { final static String TAG = "VoiceInteractionServiceManager"; @@ -74,6 +76,26 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { Bundle mAssistData; boolean mHaveScreenshot; Bitmap mScreenshot; + ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>(); + + IVoiceInteractionSessionShowCallback mShowCallback = + new IVoiceInteractionSessionShowCallback.Stub() { + @Override + public void onFailed() throws RemoteException { + synchronized (mLock) { + notifyPendingShowCallbacksFailedLocked(); + } + } + + @Override + public void onShown() throws RemoteException { + synchronized (mLock) { + // TODO: Figure out whether this is good enough or whether we need to hook into + // Window manager to actually wait for the window to be drawn. + notifyPendingShowCallbacksShownLocked(); + } + } + }; public interface Callback { public void sessionConnectionGone(VoiceInteractionSessionConnection connection); @@ -151,7 +173,8 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { } } - public boolean showLocked(Bundle args, int flags) { + public boolean showLocked(Bundle args, int flags, + IVoiceInteractionSessionShowCallback showCallback) { if (mBound) { if (!mFullyBound) { mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection, @@ -182,15 +205,23 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { } if (mSession != null) { try { - mSession.show(mShowArgs, mShowFlags); + mSession.show(mShowArgs, mShowFlags, showCallback); mShowArgs = null; mShowFlags = 0; } catch (RemoteException e) { } deliverSessionDataLocked(); + } else if (showCallback != null) { + mPendingShowCallbacks.add(showCallback); } return true; } + if (showCallback != null) { + try { + showCallback.onFailed(); + } catch (RemoteException e) { + } + } return false; } @@ -320,7 +351,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { mInteractor = interactor; if (mShown) { try { - session.show(mShowArgs, mShowFlags); + session.show(mShowArgs, mShowFlags, mShowCallback); mShowArgs = null; mShowFlags = 0; } catch (RemoteException e) { @@ -330,6 +361,26 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { return true; } + private void notifyPendingShowCallbacksShownLocked() { + for (int i = 0; i < mPendingShowCallbacks.size(); i++) { + try { + mPendingShowCallbacks.get(i).onShown(); + } catch (RemoteException e) { + } + } + mPendingShowCallbacks.clear(); + } + + private void notifyPendingShowCallbacksFailedLocked() { + for (int i = 0; i < mPendingShowCallbacks.size(); i++) { + try { + mPendingShowCallbacks.get(i).onFailed(); + } catch (RemoteException e) { + } + } + mPendingShowCallbacks.clear(); + } + @Override public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mLock) { diff --git a/tests/Assist/Android.mk b/tests/Assist/Android.mk new file mode 100644 index 0000000..f31c4dd --- /dev/null +++ b/tests/Assist/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := Assist + +include $(BUILD_PACKAGE) diff --git a/tests/Assist/AndroidManifest.xml b/tests/Assist/AndroidManifest.xml new file mode 100644 index 0000000..4eceed9 --- /dev/null +++ b/tests/Assist/AndroidManifest.xml @@ -0,0 +1,39 @@ +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.assist"> + + <application> + <service android:name="AssistInteractionService" + android:label="Test Assist Interaction Service" + android:permission="android.permission.BIND_VOICE_INTERACTION" + android:process=":interactor"> + <meta-data android:name="android.voice_interaction" + android:resource="@xml/interaction_service" /> + <intent-filter> + <action android:name="android.service.voice.VoiceInteractionService" /> + </intent-filter> + <meta-data + android:name="com.android.systemui.action_assist_icon" + android:resource="@drawable/assistant" /> + </service> + <service android:name="AssistInteractionSessionService" + android:permission="android.permission.BIND_VOICE_INTERACTION" + android:process=":session"> + </service> + </application> +</manifest> diff --git a/tests/Assist/res/drawable/assistant.xml b/tests/Assist/res/drawable/assistant.xml new file mode 100644 index 0000000..2a89dda --- /dev/null +++ b/tests/Assist/res/drawable/assistant.xml @@ -0,0 +1,27 @@ +<!-- +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" + android:width="48.0dp" + android:height="48.0dp" + android:viewportWidth="48.0" + android:viewportHeight="48.0"> + <path + android:pathData="M0 0h48v48H0z" + android:fillColor="#00000000"/> + <path + android:fillColor="#FF000000" + android:pathData="M38.0,4.0L10.0,4.0C7.79,4.0 6.0,5.79 6.0,8.0l0.0,28.0c0.0,2.21 1.79,4.0 4.0,4.0l8.0,0.0l6.0,6.0 6.0,-6.0l8.0,0.0c2.21,0.0 4.0,-1.79 4.0,-4.0L36.0,8.0c0.0,-2.21 -1.79,-4.0 -4.0,-4.0zM27.75,25.75L24.0,34.0l-3.75,-8.25L12.0,22.0l8.25,-3.75L24.0,10.0l3.75,8.25L36.0,22.0l-8.25,3.75z"/> +</vector> diff --git a/tests/Assist/res/drawable/navbar_scrim.xml b/tests/Assist/res/drawable/navbar_scrim.xml new file mode 100644 index 0000000..52ed76d --- /dev/null +++ b/tests/Assist/res/drawable/navbar_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="#33000000" + android:endColor="#00000000" /> +</shape>
\ No newline at end of file diff --git a/tests/Assist/res/drawable/round_rect.xml b/tests/Assist/res/drawable/round_rect.xml new file mode 100644 index 0000000..55937a0 --- /dev/null +++ b/tests/Assist/res/drawable/round_rect.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 + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="#e0e0e0"> + <item> + <shape> + <solid android:color="#ffffff" /> + <corners android:radius="2dp" /> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/tests/Assist/res/layout/assist.xml b/tests/Assist/res/layout/assist.xml new file mode 100644 index 0000000..8c4be2d --- /dev/null +++ b/tests/Assist/res/layout/assist.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <com.android.test.assist.ScrimView + android:id="@+id/scrim" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#60000000"/> + + <View + android:id="@+id/background" + android:layout_width="match_parent" + android:layout_height="350dp" + android:layout_gravity="bottom" + android:background="#e0e0e0"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="350dp" + android:orientation="vertical" + android:layout_gravity="bottom"> + + <FrameLayout + android:id="@+id/card1" + android:layout_width="match_parent" + android:layout_height="150dp" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:layout_marginTop="16dp" + android:elevation="3dp" + android:background="@drawable/round_rect"> + </FrameLayout> + + <View + android:id="@+id/card2" + android:layout_width="match_parent" + android:layout_height="200dp" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:layout_marginTop="16dp" + android:elevation="3dp" + android:background="@drawable/round_rect"/> + + </LinearLayout> + + <com.android.test.assist.ScrimView + android:id="@+id/navbar_scrim" + android:layout_width="match_parent" + android:layout_height="150dp" + android:layout_gravity="bottom" + android:background="@drawable/navbar_scrim"/> +</FrameLayout>
\ No newline at end of file diff --git a/tests/Assist/res/values/strings.xml b/tests/Assist/res/values/strings.xml new file mode 100644 index 0000000..5331457 --- /dev/null +++ b/tests/Assist/res/values/strings.xml @@ -0,0 +1,28 @@ +<?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. +--> + +<resources> + + <string name="start">Start</string> + <string name="confirm">Confirm</string> + <string name="abort">Abort</string> + <string name="complete">Complete</string> + <string name="abortVoice">Abort Voice</string> + <string name="completeVoice">Complete Voice</string> + <string name="pickVoice">Pick Voice</string> + <string name="cancelVoice">Cancel</string> + +</resources> diff --git a/tests/Assist/res/xml/interaction_service.xml b/tests/Assist/res/xml/interaction_service.xml new file mode 100644 index 0000000..2fd50aa --- /dev/null +++ b/tests/Assist/res/xml/interaction_service.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android" + android:sessionService="com.android.test.assist.AssistInteractionSessionService" + android:recognitionService="com.android.test.assist.AssistRecognitionService" + android:supportsAssistGesture="true"/> diff --git a/tests/Assist/res/xml/recognition_service.xml b/tests/Assist/res/xml/recognition_service.xml new file mode 100644 index 0000000..5b52c3c --- /dev/null +++ b/tests/Assist/res/xml/recognition_service.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2015 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<recognition-service/> diff --git a/tests/Assist/src/com/android/test/assist/AssistInteractionService.java b/tests/Assist/src/com/android/test/assist/AssistInteractionService.java new file mode 100644 index 0000000..e6a6713 --- /dev/null +++ b/tests/Assist/src/com/android/test/assist/AssistInteractionService.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.test.assist; + +import android.service.voice.VoiceInteractionService; + +public class AssistInteractionService extends VoiceInteractionService { + +} diff --git a/tests/Assist/src/com/android/test/assist/AssistInteractionSession.java b/tests/Assist/src/com/android/test/assist/AssistInteractionSession.java new file mode 100644 index 0000000..0b522c0 --- /dev/null +++ b/tests/Assist/src/com/android/test/assist/AssistInteractionSession.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.test.assist; + +import android.animation.Animator; +import android.animation.RevealAnimator; +import android.animation.ValueAnimator; +import android.app.VoiceInteractor; +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.os.Handler; +import android.service.voice.VoiceInteractionService; +import android.service.voice.VoiceInteractionSession; +import android.util.Log; +import android.view.View; +import android.view.ViewAnimationUtils; +import android.view.ViewTreeObserver; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; + +/** + * Sample session to show test assist transition. + */ +public class AssistInteractionSession extends VoiceInteractionSession { + + private View mScrim; + private View mBackground; + private View mNavbarScrim; + private View mCard1; + private View mCard2; + + private float mDensity; + + public AssistInteractionSession(Context context) { + super(context); + } + + public AssistInteractionSession(Context context, Handler handler) { + super(context, handler); + } + + @Override + public void onConfirm(Caller caller, + Request request, CharSequence prompt, Bundle extras) { + + } + + @Override + public void onPickOption(Caller caller, + Request request, CharSequence prompt, + VoiceInteractor.PickOptionRequest.Option[] options, Bundle extras) { + + } + + @Override + public void onCommand(Caller caller, + Request request, String command, Bundle extras) { + + } + + @Override + public void onCreate(Bundle args) { + super.onCreate(args); + + // Simulate slowness of Assist app + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override + public void onCancel(Request request) { + + } + + @Override + public View onCreateContentView() { + View v = getLayoutInflater().inflate(R.layout.assist, null); + mScrim = v.findViewById(R.id.scrim); + mBackground = v.findViewById(R.id.background); + mDensity = mScrim.getResources().getDisplayMetrics().density; + mCard1 = v.findViewById(R.id.card1); + mCard2 = v.findViewById(R.id.card2); + mNavbarScrim = v.findViewById(R.id.navbar_scrim); + return v; + } + + @Override + public void onShow(Bundle args, int showFlags) { + super.onShow(args, showFlags); + if ((showFlags & VoiceInteractionService.START_SOURCE_ASSIST_GESTURE) != 0) { + mBackground.getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + mBackground.getViewTreeObserver().removeOnPreDrawListener(this); + playAssistAnimation(); + return true; + } + }); + } + } + + private void playAssistAnimation() { + Interpolator linearOutSlowIn = AnimationUtils.loadInterpolator(mBackground.getContext(), + android.R.interpolator.linear_out_slow_in); + Interpolator fastOutSlowIn = AnimationUtils.loadInterpolator(mBackground.getContext(), + android.R.interpolator.fast_out_slow_in); + mScrim.setAlpha(0f); + mScrim.animate() + .alpha(1f) + .setStartDelay(100) + .setDuration(500); + mBackground.setTranslationY(50 * mDensity); + mBackground.animate() + .translationY(0) + .setDuration(300) + .setInterpolator(linearOutSlowIn); + int centerX = mBackground.getWidth()/2; + int centerY = (int) (mBackground.getHeight()/5*3.8f); + int radius = (int) Math.sqrt(centerX*centerX + centerY*centerY) + 1; + Animator animator = ViewAnimationUtils.createCircularReveal(mBackground, centerX, centerY, + 0, radius); + animator.setDuration(300); + animator.setInterpolator(fastOutSlowIn); + animator.start(); + + ValueAnimator colorAnim = ValueAnimator.ofArgb(Color.WHITE, 0xffe0e0e0); + colorAnim.setDuration(300); + colorAnim.setInterpolator(fastOutSlowIn); + colorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mBackground.setBackgroundColor((Integer) animation.getAnimatedValue()); + } + }); + colorAnim.start(); + + + mCard1.setY(mBackground.getHeight()); + mCard2.setTranslationY(mCard1.getTranslationY()); + mCard1.animate() + .translationY(0) + .setDuration(500) + .setInterpolator(linearOutSlowIn) + .setStartDelay(100); + mCard2.animate() + .translationY(0) + .setInterpolator(linearOutSlowIn) + .setStartDelay(150) + .setDuration(500); + + mNavbarScrim.setAlpha(0f); + mNavbarScrim.animate() + .alpha(1f) + .setDuration(500) + .setStartDelay(100); + } + + @Override + public void onHide() { + super.onHide(); + } +} diff --git a/tests/Assist/src/com/android/test/assist/AssistInteractionSessionService.java b/tests/Assist/src/com/android/test/assist/AssistInteractionSessionService.java new file mode 100644 index 0000000..3c483d6 --- /dev/null +++ b/tests/Assist/src/com/android/test/assist/AssistInteractionSessionService.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.test.assist; + +import android.os.Bundle; +import android.service.voice.VoiceInteractionSession; +import android.service.voice.VoiceInteractionSessionService; + +public class AssistInteractionSessionService extends VoiceInteractionSessionService { + @Override + public VoiceInteractionSession onNewSession(Bundle args) { + return new AssistInteractionSession(this); + } +} diff --git a/tests/Assist/src/com/android/test/assist/AssistRecognitionService.java b/tests/Assist/src/com/android/test/assist/AssistRecognitionService.java new file mode 100644 index 0000000..6e5faa1 --- /dev/null +++ b/tests/Assist/src/com/android/test/assist/AssistRecognitionService.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.test.assist; + +import android.content.Intent; +import android.speech.RecognitionService; + +/** + * Stub recognition service needed to be a complete voice interactor. + */ +public class AssistRecognitionService extends RecognitionService { + + @Override + protected void onStartListening(Intent recognizerIntent, Callback listener) { + } + + @Override + protected void onCancel(Callback listener) { + } + + @Override + protected void onStopListening(Callback listener) { + } +} diff --git a/tests/Assist/src/com/android/test/assist/ScrimView.java b/tests/Assist/src/com/android/test/assist/ScrimView.java new file mode 100644 index 0000000..1b7387b --- /dev/null +++ b/tests/Assist/src/com/android/test/assist/ScrimView.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.test.assist; + +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +public class ScrimView extends View { + + public ScrimView(Context context) { + super(context); + } + + public ScrimView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public ScrimView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } +} diff --git a/tests/Assistant/Android.mk b/tests/LegacyAssistant/Android.mk index bf8cc29..0ad48d1 100644 --- a/tests/Assistant/Android.mk +++ b/tests/LegacyAssistant/Android.mk @@ -3,7 +3,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-subdir-java-files) -LOCAL_PACKAGE_NAME := Assistant +LOCAL_PACKAGE_NAME := LegacyAssistant LOCAL_MODULE_TAGS := tests LOCAL_CERTIFICATE := platform diff --git a/tests/Assistant/AndroidManifest.xml b/tests/LegacyAssistant/AndroidManifest.xml index b5d4d51..7ae5103 100644 --- a/tests/Assistant/AndroidManifest.xml +++ b/tests/LegacyAssistant/AndroidManifest.xml @@ -15,7 +15,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.google.android.test.assistant"> + package="com.google.android.test.legacyassistant"> <application android:label="@string/activity_title"> diff --git a/tests/Assistant/res/drawable-hdpi/ic_action_assist_activated.png b/tests/LegacyAssistant/res/drawable-hdpi/ic_action_assist_activated.png Binary files differindex cea8ac4..cea8ac4 100644 --- a/tests/Assistant/res/drawable-hdpi/ic_action_assist_activated.png +++ b/tests/LegacyAssistant/res/drawable-hdpi/ic_action_assist_activated.png diff --git a/tests/Assistant/res/drawable-hdpi/ic_action_assist_normal.png b/tests/LegacyAssistant/res/drawable-hdpi/ic_action_assist_normal.png Binary files differindex bb7702d..bb7702d 100644 --- a/tests/Assistant/res/drawable-hdpi/ic_action_assist_normal.png +++ b/tests/LegacyAssistant/res/drawable-hdpi/ic_action_assist_normal.png diff --git a/tests/Assistant/res/drawable-mdpi/ic_action_assist_activated.png b/tests/LegacyAssistant/res/drawable-mdpi/ic_action_assist_activated.png Binary files differindex 5841d82..5841d82 100644 --- a/tests/Assistant/res/drawable-mdpi/ic_action_assist_activated.png +++ b/tests/LegacyAssistant/res/drawable-mdpi/ic_action_assist_activated.png diff --git a/tests/Assistant/res/drawable-mdpi/ic_action_assist_normal.png b/tests/LegacyAssistant/res/drawable-mdpi/ic_action_assist_normal.png Binary files differindex 3851f03..3851f03 100644 --- a/tests/Assistant/res/drawable-mdpi/ic_action_assist_normal.png +++ b/tests/LegacyAssistant/res/drawable-mdpi/ic_action_assist_normal.png diff --git a/tests/Assistant/res/drawable-xhdpi/ic_action_assist_activated.png b/tests/LegacyAssistant/res/drawable-xhdpi/ic_action_assist_activated.png Binary files differindex 778db19..778db19 100644 --- a/tests/Assistant/res/drawable-xhdpi/ic_action_assist_activated.png +++ b/tests/LegacyAssistant/res/drawable-xhdpi/ic_action_assist_activated.png diff --git a/tests/Assistant/res/drawable-xhdpi/ic_action_assist_normal.png b/tests/LegacyAssistant/res/drawable-xhdpi/ic_action_assist_normal.png Binary files differindex ad49125..ad49125 100644 --- a/tests/Assistant/res/drawable-xhdpi/ic_action_assist_normal.png +++ b/tests/LegacyAssistant/res/drawable-xhdpi/ic_action_assist_normal.png diff --git a/tests/Assistant/res/drawable/ic_action_assist.xml b/tests/LegacyAssistant/res/drawable/ic_action_assist.xml index 05c4bf5..05c4bf5 100644 --- a/tests/Assistant/res/drawable/ic_action_assist.xml +++ b/tests/LegacyAssistant/res/drawable/ic_action_assist.xml diff --git a/tests/Assistant/res/layout/assist_intent_activity.xml b/tests/LegacyAssistant/res/layout/assist_intent_activity.xml index 49785bc..49785bc 100644 --- a/tests/Assistant/res/layout/assist_intent_activity.xml +++ b/tests/LegacyAssistant/res/layout/assist_intent_activity.xml diff --git a/tests/Assistant/res/values/strings.xml b/tests/LegacyAssistant/res/values/strings.xml index a59c1ef..a59c1ef 100644 --- a/tests/Assistant/res/values/strings.xml +++ b/tests/LegacyAssistant/res/values/strings.xml diff --git a/tests/Assistant/src/com/google/android/test/assistant/AssistActivity.java b/tests/LegacyAssistant/src/com/google/android/test/legacyassistant/AssistActivity.java index 51894a1..b3dbb15 100644 --- a/tests/Assistant/src/com/google/android/test/assistant/AssistActivity.java +++ b/tests/LegacyAssistant/src/com/google/android/test/legacyassistant/AssistActivity.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.google.android.test.assistant; +package com.google.android.test.legacyassistant; import android.app.Activity; import android.os.Bundle; -import com.google.android.test.assistant.R; +import com.google.android.test.legacyassistant.R; public class AssistActivity extends Activity { |