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