1package com.android.systemui.assist;
2
3import android.app.ActivityManager;
4import android.app.ActivityOptions;
5import android.app.SearchManager;
6import android.content.ActivityNotFoundException;
7import android.content.ComponentName;
8import android.content.Context;
9import android.content.Intent;
10import android.content.pm.PackageManager;
11import android.content.res.Resources;
12import android.database.ContentObserver;
13import android.graphics.PixelFormat;
14import android.media.AudioAttributes;
15import android.os.AsyncTask;
16import android.os.Bundle;
17import android.os.Handler;
18import android.os.RemoteException;
19import android.os.UserHandle;
20import android.provider.Settings;
21import android.service.voice.VoiceInteractionSession;
22import android.util.Log;
23import android.view.Gravity;
24import android.view.HapticFeedbackConstants;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.ViewGroup;
28import android.view.WindowManager;
29import android.widget.ImageView;
30
31import com.android.internal.app.AssistUtils;
32import com.android.internal.app.IVoiceInteractionSessionShowCallback;
33import com.android.systemui.R;
34import com.android.systemui.statusbar.BaseStatusBar;
35import com.android.systemui.statusbar.CommandQueue;
36import com.android.systemui.statusbar.phone.PhoneStatusBar;
37
38import java.io.FileDescriptor;
39import java.io.PrintWriter;
40
41/**
42 * Class to manage everything related to assist in SystemUI.
43 */
44public class AssistManager {
45
46    private static final String TAG = "AssistManager";
47    private static final String ASSIST_ICON_METADATA_NAME =
48            "com.android.systemui.action_assist_icon";
49
50    private static final long TIMEOUT_SERVICE = 2500;
51    private static final long TIMEOUT_ACTIVITY = 1000;
52
53    private final Context mContext;
54    private final WindowManager mWindowManager;
55    private final AssistDisclosure mAssistDisclosure;
56
57    private AssistOrbContainer mView;
58    private final BaseStatusBar mBar;
59    private final AssistUtils mAssistUtils;
60
61    private ComponentName mAssistComponent;
62
63    private IVoiceInteractionSessionShowCallback mShowCallback =
64            new IVoiceInteractionSessionShowCallback.Stub() {
65
66        @Override
67        public void onFailed() throws RemoteException {
68            mView.post(mHideRunnable);
69        }
70
71        @Override
72        public void onShown() throws RemoteException {
73            mView.post(mHideRunnable);
74        }
75    };
76
77    private Runnable mHideRunnable = new Runnable() {
78        @Override
79        public void run() {
80            mView.removeCallbacks(this);
81            mView.show(false /* show */, true /* animate */);
82        }
83    };
84
85    private final ContentObserver mAssistSettingsObserver = new ContentObserver(new Handler()) {
86        @Override
87        public void onChange(boolean selfChange) {
88            updateAssistInfo();
89        }
90    };
91
92    public AssistManager(BaseStatusBar bar, Context context) {
93        mContext = context;
94        mBar = bar;
95        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
96        mAssistUtils = new AssistUtils(context);
97
98        mContext.getContentResolver().registerContentObserver(
99                Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), false,
100                mAssistSettingsObserver);
101        mAssistSettingsObserver.onChange(false);
102        mAssistDisclosure = new AssistDisclosure(context, new Handler());
103    }
104
105    public void onConfigurationChanged() {
106        boolean visible = false;
107        if (mView != null) {
108            visible = mView.isShowing();
109            mWindowManager.removeView(mView);
110        }
111
112        mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate(
113                R.layout.assist_orb, null);
114        mView.setVisibility(View.GONE);
115        mView.setSystemUiVisibility(
116                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
117                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
118        WindowManager.LayoutParams lp = getLayoutParams();
119        mWindowManager.addView(mView, lp);
120        if (visible) {
121            mView.show(true /* show */, false /* animate */);
122        }
123    }
124
125    public void startAssist(Bundle args) {
126        updateAssistInfo();
127        if (mAssistComponent == null) {
128            return;
129        }
130
131        final boolean isService = isAssistantService();
132        if (!isService || !isVoiceSessionRunning()) {
133            showOrb();
134            mView.postDelayed(mHideRunnable, isService
135                    ? TIMEOUT_SERVICE
136                    : TIMEOUT_ACTIVITY);
137        }
138        startAssistInternal(args);
139    }
140
141    public void hideAssist() {
142        mAssistUtils.hideCurrentSession();
143    }
144
145    private WindowManager.LayoutParams getLayoutParams() {
146        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
147                ViewGroup.LayoutParams.MATCH_PARENT,
148                mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height),
149                WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING,
150                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
151                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
152                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
153                PixelFormat.TRANSLUCENT);
154        if (ActivityManager.isHighEndGfx()) {
155            lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
156        }
157        lp.gravity = Gravity.BOTTOM | Gravity.START;
158        lp.setTitle("AssistPreviewPanel");
159        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED
160                | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
161        return lp;
162    }
163
164    private void showOrb() {
165        maybeSwapSearchIcon();
166        mView.show(true /* show */, true /* animate */);
167    }
168
169    private void startAssistInternal(Bundle args) {
170        if (mAssistComponent != null) {
171            if (isAssistantService()) {
172                startVoiceInteractor(args);
173            } else {
174                startAssistActivity(args);
175            }
176        }
177    }
178
179    private void startAssistActivity(Bundle args) {
180        if (!mBar.isDeviceProvisioned()) {
181            return;
182        }
183
184        // Close Recent Apps if needed
185        mBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL |
186                CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL);
187
188        boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(),
189                Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0;
190
191        final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
192                .getAssistIntent(structureEnabled);
193        if (intent == null) {
194            return;
195        }
196        if (mAssistComponent != null) {
197            intent.setComponent(mAssistComponent);
198        }
199        intent.putExtras(args);
200
201        if (structureEnabled) {
202            showDisclosure();
203        }
204
205        try {
206            final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext,
207                    R.anim.search_launch_enter, R.anim.search_launch_exit);
208            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
209            AsyncTask.execute(new Runnable() {
210                @Override
211                public void run() {
212                    mContext.startActivityAsUser(intent, opts.toBundle(),
213                            new UserHandle(UserHandle.USER_CURRENT));
214                }
215            });
216        } catch (ActivityNotFoundException e) {
217            Log.w(TAG, "Activity not found for " + intent.getAction());
218        }
219    }
220
221    private void startVoiceInteractor(Bundle args) {
222        mAssistUtils.showSessionForActiveService(args,
223                VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE, mShowCallback, null);
224    }
225
226    public void launchVoiceAssistFromKeyguard() {
227        mAssistUtils.launchVoiceAssistFromKeyguard();
228    }
229
230    public boolean canVoiceAssistBeLaunchedFromKeyguard() {
231        return mAssistUtils.activeServiceSupportsLaunchFromKeyguard();
232    }
233
234    public ComponentName getVoiceInteractorComponentName() {
235        return mAssistUtils.getActiveServiceComponentName();
236    }
237
238    private boolean isVoiceSessionRunning() {
239        return mAssistUtils.isSessionRunning();
240    }
241
242    public void destroy() {
243        mWindowManager.removeViewImmediate(mView);
244    }
245
246    private void maybeSwapSearchIcon() {
247        if (mAssistComponent != null) {
248            replaceDrawable(mView.getOrb().getLogo(), mAssistComponent, ASSIST_ICON_METADATA_NAME,
249                    isAssistantService());
250        } else {
251            mView.getOrb().getLogo().setImageDrawable(null);
252        }
253    }
254
255    public void replaceDrawable(ImageView v, ComponentName component, String name,
256            boolean isService) {
257        if (component != null) {
258            try {
259                PackageManager packageManager = mContext.getPackageManager();
260                // Look for the search icon specified in the activity meta-data
261                Bundle metaData = isService
262                        ? packageManager.getServiceInfo(
263                                component, PackageManager.GET_META_DATA).metaData
264                        : packageManager.getActivityInfo(
265                                component, PackageManager.GET_META_DATA).metaData;
266                if (metaData != null) {
267                    int iconResId = metaData.getInt(name);
268                    if (iconResId != 0) {
269                        Resources res = packageManager.getResourcesForApplication(
270                                component.getPackageName());
271                        v.setImageDrawable(res.getDrawable(iconResId));
272                        return;
273                    }
274                }
275            } catch (PackageManager.NameNotFoundException e) {
276                Log.w(TAG, "Failed to swap drawable; "
277                        + component.flattenToShortString() + " not found", e);
278            } catch (Resources.NotFoundException nfe) {
279                Log.w(TAG, "Failed to swap drawable from "
280                        + component.flattenToShortString(), nfe);
281            }
282        }
283        v.setImageDrawable(null);
284    }
285
286    private boolean isAssistantService() {
287        return mAssistComponent == null ?
288                false : mAssistComponent.equals(getVoiceInteractorComponentName());
289    }
290
291    private void updateAssistInfo() {
292        mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.USER_CURRENT);
293    }
294
295    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
296        pw.println("AssistManager state:");
297        pw.print("  mAssistComponent="); pw.println(mAssistComponent);
298    }
299
300    public void showDisclosure() {
301        mAssistDisclosure.postShow();
302    }
303
304    public void onUserSwitched(int newUserId) {
305        updateAssistInfo();
306    }
307
308    public void onLockscreenShown() {
309        mAssistUtils.onLockscreenShown();
310    }
311}
312