1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.assist; 18 19 import android.annotation.NonNull; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.pm.ActivityInfo; 23 import android.content.pm.PackageManager; 24 import android.content.res.Configuration; 25 import android.content.res.Resources; 26 import android.graphics.PixelFormat; 27 import android.os.Binder; 28 import android.os.Bundle; 29 import android.util.Log; 30 import android.view.Gravity; 31 import android.view.LayoutInflater; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.view.WindowManager; 35 import android.widget.ImageView; 36 37 import com.android.settingslib.applications.InterestingConfigChanges; 38 import com.android.systemui.R; 39 import com.android.systemui.statusbar.policy.ConfigurationController; 40 41 /** 42 * AssistOrbController controls the showing and hiding of the assistant orb. 43 */ 44 public class AssistOrbController { 45 private static final String ASSIST_ICON_METADATA_NAME = 46 "com.android.systemui.action_assist_icon"; 47 private static final String TAG = "AssistOrbController"; 48 private static final boolean VERBOSE = false; 49 50 private final InterestingConfigChanges mInterestingConfigChanges; 51 private AssistOrbContainer mView; 52 private final Context mContext; 53 private final WindowManager mWindowManager; 54 55 private Runnable mHideRunnable = new Runnable() { 56 @Override 57 public void run() { 58 mView.removeCallbacks(this); 59 mView.show(false /* show */, true /* animate */, () -> { 60 mWindowManager.removeView(mView); 61 }); 62 } 63 }; 64 65 private ConfigurationController.ConfigurationListener mConfigurationListener = 66 new ConfigurationController.ConfigurationListener() { 67 @Override 68 public void onConfigChanged(Configuration newConfig) { 69 if (!mInterestingConfigChanges.applyNewConfig(mContext.getResources())) { 70 return; 71 } 72 boolean visible = false; 73 if (mView != null) { 74 visible = mView.isShowing(); 75 if (mView.isAttachedToWindow()) { 76 mWindowManager.removeView(mView); 77 } 78 } 79 80 if (visible) { 81 showOrb(false); 82 } 83 } 84 }; 85 AssistOrbController(ConfigurationController configurationController, Context context)86 AssistOrbController(ConfigurationController configurationController, Context context) { 87 mContext = context; 88 mWindowManager = mContext.getSystemService(WindowManager.class); 89 mInterestingConfigChanges = new InterestingConfigChanges(ActivityInfo.CONFIG_ORIENTATION 90 | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE 91 | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS); 92 93 configurationController.addCallback(mConfigurationListener); 94 mConfigurationListener.onConfigChanged(context.getResources().getConfiguration()); 95 } 96 postHide()97 public void postHide() { 98 mView.post(mHideRunnable); 99 } 100 postHideDelayed(long delayMs)101 public void postHideDelayed(long delayMs) { 102 mView.postDelayed(mHideRunnable, delayMs); 103 } 104 showOrb(boolean animated)105 private void showOrb(boolean animated) { 106 if (mView == null) { 107 mView = (AssistOrbContainer) LayoutInflater.from(mContext).inflate( 108 R.layout.assist_orb, null); 109 mView.setVisibility(View.GONE); 110 mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 111 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE 112 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 113 } 114 if (!mView.isAttachedToWindow()) { 115 WindowManager.LayoutParams params = getLayoutParams(); 116 mWindowManager.addView(mView, params); 117 } 118 mView.show(true, animated, null); 119 } 120 getLayoutParams()121 private WindowManager.LayoutParams getLayoutParams() { 122 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 123 ViewGroup.LayoutParams.MATCH_PARENT, 124 mContext.getResources().getDimensionPixelSize(R.dimen.assist_orb_scrim_height), 125 WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING, 126 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 127 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 128 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, 129 PixelFormat.TRANSLUCENT); 130 lp.token = new Binder(); 131 lp.gravity = Gravity.BOTTOM | Gravity.START; 132 lp.setTitle("AssistPreviewPanel"); 133 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED 134 | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; 135 return lp; 136 } 137 showOrb(@onNull ComponentName assistComponent, boolean isService)138 public void showOrb(@NonNull ComponentName assistComponent, boolean isService) { 139 showOrb(true); 140 maybeSwapSearchIcon(assistComponent, isService); 141 } 142 maybeSwapSearchIcon(@onNull ComponentName assistComponent, boolean isService)143 private void maybeSwapSearchIcon(@NonNull ComponentName assistComponent, boolean isService) { 144 replaceDrawable(mView.getOrb().getLogo(), assistComponent, ASSIST_ICON_METADATA_NAME, 145 isService); 146 } 147 replaceDrawable(ImageView v, ComponentName component, String name, boolean isService)148 public void replaceDrawable(ImageView v, ComponentName component, String name, 149 boolean isService) { 150 if (component != null) { 151 try { 152 PackageManager packageManager = mContext.getPackageManager(); 153 // Look for the search icon specified in the activity meta-data 154 Bundle metaData = isService 155 ? packageManager.getServiceInfo( 156 component, PackageManager.GET_META_DATA).metaData 157 : packageManager.getActivityInfo( 158 component, PackageManager.GET_META_DATA).metaData; 159 if (metaData != null) { 160 int iconResId = metaData.getInt(name); 161 if (iconResId != 0) { 162 Resources res = packageManager.getResourcesForApplication( 163 component.getPackageName()); 164 v.setImageDrawable(res.getDrawable(iconResId)); 165 return; 166 } 167 } 168 } catch (PackageManager.NameNotFoundException e) { 169 if (VERBOSE) { 170 Log.v(TAG, "Assistant component " 171 + component.flattenToShortString() + " not found"); 172 } 173 } catch (Resources.NotFoundException nfe) { 174 Log.w(TAG, "Failed to swap drawable from " 175 + component.flattenToShortString(), nfe); 176 } 177 } 178 v.setImageDrawable(null); 179 } 180 } 181