The voice assist may now be launched above the lockscreen

A possibility was introduced to launch voice assist over
the lockscreen using the left keyguard affordance.

Change-Id: Ic4618d24256b65441a50d77d0ef59b0ec99b6ead
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
new file mode 100644
index 0000000..d1f8963
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -0,0 +1,309 @@
+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 related to assist in SystemUI.
+ */
+public class AssistManager {
+
+    private static final String TAG = "AssistManager";
+    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 AssistManager(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);
+        }
+    }
+
+    public void launchVoiceAssistFromKeyguard() {
+        try {
+            mVoiceInteractionManagerService.launchVoiceAssistFromKeyguard();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to call launchVoiceAssistFromKeyguard", e);
+        }
+    }
+
+    private boolean getVoiceInteractorSupportsAssistGesture() {
+        try {
+            return mVoiceInteractionManagerService.activeServiceSupportsAssist();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to call activeServiceSupportsAssistGesture", e);
+            return false;
+        }
+    }
+
+    public boolean canVoiceAssistBeLaunchedFromKeyguard() {
+        try {
+            return mVoiceInteractionManagerService.activeServiceSupportsLaunchFromKeyguard();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to call activeServiceSupportsLaunchFromKeyguard", e);
+            return false;
+        }
+    }
+
+    public 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;
+    }
+}