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