1 /* 2 * Copyright (C) 2020 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.toast; 18 19 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; 20 import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; 21 22 import android.animation.Animator; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.UserIdInt; 26 import android.app.Application; 27 import android.content.Context; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.PackageManager; 30 import android.content.pm.PackageManager.NameNotFoundException; 31 import android.content.res.Configuration; 32 import android.content.res.Resources; 33 import android.graphics.drawable.Drawable; 34 import android.os.Build; 35 import android.os.UserHandle; 36 import android.util.IconDrawableFactory; 37 import android.util.Log; 38 import android.view.LayoutInflater; 39 import android.view.View; 40 import android.widget.ImageView; 41 import android.widget.TextView; 42 43 import com.android.internal.R; 44 import com.android.systemui.plugins.ToastPlugin; 45 46 /** 47 * SystemUI TextToast that can be customized by ToastPlugins. Should never instantiate this class 48 * directly. Instead, use {@link ToastFactory#createToast}. 49 */ 50 public class SystemUIToast implements ToastPlugin.Toast { 51 static final String TAG = "SystemUIToast"; 52 final Context mContext; 53 final CharSequence mText; 54 final ToastPlugin.Toast mPluginToast; 55 56 private final String mPackageName; 57 @UserIdInt private final int mUserId; 58 private final LayoutInflater mLayoutInflater; 59 60 final int mDefaultX = 0; 61 final int mDefaultHorizontalMargin = 0; 62 final int mDefaultVerticalMargin = 0; 63 64 private int mDefaultY; 65 private int mDefaultGravity; 66 67 @NonNull private final View mToastView; 68 @Nullable private final Animator mInAnimator; 69 @Nullable private final Animator mOutAnimator; 70 SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text, String packageName, int userId, int orientation)71 SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text, 72 String packageName, int userId, int orientation) { 73 this(layoutInflater, context, text, null, packageName, userId, 74 orientation); 75 } 76 SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text, ToastPlugin.Toast pluginToast, String packageName, @UserIdInt int userId, int orientation)77 SystemUIToast(LayoutInflater layoutInflater, Context context, CharSequence text, 78 ToastPlugin.Toast pluginToast, String packageName, @UserIdInt int userId, 79 int orientation) { 80 mLayoutInflater = layoutInflater; 81 mContext = context; 82 mText = text; 83 mPluginToast = pluginToast; 84 mPackageName = packageName; 85 mUserId = userId; 86 mToastView = inflateToastView(); 87 mInAnimator = createInAnimator(); 88 mOutAnimator = createOutAnimator(); 89 90 onOrientationChange(orientation); 91 } 92 93 @Override 94 @NonNull getGravity()95 public Integer getGravity() { 96 if (isPluginToast() && mPluginToast.getGravity() != null) { 97 return mPluginToast.getGravity(); 98 } 99 return mDefaultGravity; 100 } 101 102 @Override 103 @NonNull getXOffset()104 public Integer getXOffset() { 105 if (isPluginToast() && mPluginToast.getXOffset() != null) { 106 return mPluginToast.getXOffset(); 107 } 108 return mDefaultX; 109 } 110 111 @Override 112 @NonNull getYOffset()113 public Integer getYOffset() { 114 if (isPluginToast() && mPluginToast.getYOffset() != null) { 115 return mPluginToast.getYOffset(); 116 } 117 return mDefaultY; 118 } 119 120 @Override 121 @NonNull getHorizontalMargin()122 public Integer getHorizontalMargin() { 123 if (isPluginToast() && mPluginToast.getHorizontalMargin() != null) { 124 return mPluginToast.getHorizontalMargin(); 125 } 126 return mDefaultHorizontalMargin; 127 } 128 129 @Override 130 @NonNull getVerticalMargin()131 public Integer getVerticalMargin() { 132 if (isPluginToast() && mPluginToast.getVerticalMargin() != null) { 133 return mPluginToast.getVerticalMargin(); 134 } 135 return mDefaultVerticalMargin; 136 } 137 138 @Override 139 @NonNull getView()140 public View getView() { 141 return mToastView; 142 } 143 144 @Override 145 @Nullable getInAnimation()146 public Animator getInAnimation() { 147 return mInAnimator; 148 } 149 150 @Override 151 @Nullable getOutAnimation()152 public Animator getOutAnimation() { 153 return mOutAnimator; 154 } 155 156 /** 157 * Whether this toast has a custom animation. 158 */ hasCustomAnimation()159 public boolean hasCustomAnimation() { 160 return getInAnimation() != null || getOutAnimation() != null; 161 } 162 isPluginToast()163 private boolean isPluginToast() { 164 return mPluginToast != null; 165 } 166 inflateToastView()167 private View inflateToastView() { 168 if (isPluginToast() && mPluginToast.getView() != null) { 169 return mPluginToast.getView(); 170 } 171 172 final View toastView = mLayoutInflater.inflate( 173 com.android.systemui.res.R.layout.text_toast, null); 174 final TextView textView = toastView.findViewById(com.android.systemui.res.R.id.text); 175 final ImageView iconView = toastView.findViewById(com.android.systemui.res.R.id.icon); 176 textView.setText(mText); 177 178 ApplicationInfo appInfo = null; 179 try { 180 appInfo = mContext.getPackageManager() 181 .getApplicationInfoAsUser(mPackageName, 0, mUserId); 182 } catch (PackageManager.NameNotFoundException e) { 183 Log.e(TAG, "Package name not found package=" + mPackageName 184 + " user=" + mUserId); 185 } 186 187 if (appInfo != null && appInfo.targetSdkVersion < Build.VERSION_CODES.S) { 188 // no two-line limit 189 textView.setMaxLines(Integer.MAX_VALUE); 190 191 // no app icon 192 toastView.findViewById(com.android.systemui.res.R.id.icon).setVisibility(View.GONE); 193 } else { 194 Drawable icon = getBadgedIcon(mContext, mPackageName, mUserId); 195 if (icon == null) { 196 iconView.setVisibility(View.GONE); 197 } else { 198 iconView.setImageDrawable(icon); 199 if (appInfo == null) { 200 Log.d(TAG, "No appInfo for pkg=" + mPackageName + " usr=" + mUserId); 201 } else if (appInfo.labelRes != 0) { 202 try { 203 Resources res = mContext.getPackageManager().getResourcesForApplication( 204 appInfo, 205 new Configuration(mContext.getResources().getConfiguration())); 206 iconView.setContentDescription(res.getString(appInfo.labelRes)); 207 } catch (PackageManager.NameNotFoundException e) { 208 Log.d(TAG, "Cannot find application resources for icon label."); 209 } 210 } 211 } 212 } 213 return toastView; 214 } 215 216 /** 217 * Called on orientation changes to update parameters associated with the toast placement. 218 */ onOrientationChange(int orientation)219 public void onOrientationChange(int orientation) { 220 if (mPluginToast != null) { 221 mPluginToast.onOrientationChange(orientation); 222 } 223 224 mDefaultY = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset); 225 mDefaultGravity = 226 mContext.getResources().getInteger(R.integer.config_toastDefaultGravity); 227 } 228 createInAnimator()229 private Animator createInAnimator() { 230 if (isPluginToast() && mPluginToast.getInAnimation() != null) { 231 return mPluginToast.getInAnimation(); 232 } 233 234 return ToastDefaultAnimation.Companion.toastIn(getView()); 235 } 236 createOutAnimator()237 private Animator createOutAnimator() { 238 if (isPluginToast() && mPluginToast.getOutAnimation() != null) { 239 return mPluginToast.getOutAnimation(); 240 } 241 return ToastDefaultAnimation.Companion.toastOut(getView()); 242 } 243 244 /** 245 * Get badged app icon if necessary, similar as used in the Settings UI. 246 * @return The icon to use 247 */ getBadgedIcon(@onNull Context context, String packageName, int userId)248 public static Drawable getBadgedIcon(@NonNull Context context, String packageName, 249 int userId) { 250 if (!(context.getApplicationContext() instanceof Application)) { 251 return null; 252 } 253 254 try { 255 final PackageManager packageManager = context.getPackageManager(); 256 final ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser( 257 packageName, 258 PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA), 259 userId); 260 if (appInfo == null || !showApplicationIcon(appInfo, packageManager)) { 261 return null; 262 } 263 264 IconDrawableFactory iconFactory = IconDrawableFactory.newInstance(context); 265 return iconFactory.getBadgedIcon(appInfo, UserHandle.getUserId(appInfo.uid)); 266 } catch (NameNotFoundException e) { 267 Log.e(TAG, "Couldn't find application info for packageName=" + packageName 268 + " userId=" + userId); 269 return null; 270 } 271 } 272 showApplicationIcon(ApplicationInfo appInfo, PackageManager packageManager)273 private static boolean showApplicationIcon(ApplicationInfo appInfo, 274 PackageManager packageManager) { 275 if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP | FLAG_SYSTEM)) { 276 return packageManager.getLaunchIntentForPackage(appInfo.packageName) != null; 277 } 278 return true; 279 } 280 hasFlag(int flags, int flag)281 private static boolean hasFlag(int flags, int flag) { 282 return (flags & flag) != 0; 283 } 284 } 285