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.labelRes != 0) { 204 try { 205 Resources res = mContext.getPackageManager().getResourcesForApplication( 206 appInfo, 207 new Configuration(mContext.getResources().getConfiguration())); 208 iconView.setContentDescription(res.getString(appInfo.labelRes)); 209 } catch (PackageManager.NameNotFoundException e) { 210 Log.d(TAG, "Cannot find application resources for icon label."); 211 } 212 } 213 } 214 } 215 return toastView; 216 } 217 218 /** 219 * Called on orientation changes to update parameters associated with the toast placement. 220 */ onOrientationChange(int orientation)221 public void onOrientationChange(int orientation) { 222 if (mPluginToast != null) { 223 mPluginToast.onOrientationChange(orientation); 224 } 225 226 mDefaultY = mContext.getResources().getDimensionPixelSize(R.dimen.toast_y_offset); 227 mDefaultGravity = 228 mContext.getResources().getInteger(R.integer.config_toastDefaultGravity); 229 } 230 createInAnimator()231 private Animator createInAnimator() { 232 if (isPluginToast() && mPluginToast.getInAnimation() != null) { 233 return mPluginToast.getInAnimation(); 234 } 235 236 return ToastDefaultAnimation.Companion.toastIn(getView()); 237 } 238 createOutAnimator()239 private Animator createOutAnimator() { 240 if (isPluginToast() && mPluginToast.getOutAnimation() != null) { 241 return mPluginToast.getOutAnimation(); 242 } 243 return ToastDefaultAnimation.Companion.toastOut(getView()); 244 } 245 246 /** 247 * Get badged app icon if necessary, similar as used in the Settings UI. 248 * @return The icon to use 249 */ getBadgedIcon(@onNull Context context, String packageName, int userId)250 public static Drawable getBadgedIcon(@NonNull Context context, String packageName, 251 int userId) { 252 if (!(context.getApplicationContext() instanceof Application)) { 253 return null; 254 } 255 256 final Context userContext; 257 try { 258 userContext = context.createPackageContextAsUser("android", 259 0, new UserHandle(userId)); 260 } catch (NameNotFoundException e) { 261 Log.e(TAG, "Could not create user package context"); 262 return null; 263 } 264 265 final ApplicationsState appState = 266 ApplicationsState.getInstance((Application) context.getApplicationContext()); 267 if (!appState.isUserAdded(userId)) { 268 Log.d(TAG, "user hasn't been fully initialized, not showing an app icon for " 269 + "packageName=" + packageName); 270 return null; 271 } 272 273 final PackageManager packageManager = userContext.getPackageManager(); 274 final AppEntry appEntry = appState.getEntry(packageName, userId); 275 if (appEntry == null || appEntry.info == null 276 || !showApplicationIcon(appEntry.info, packageManager)) { 277 return null; 278 } 279 280 final ApplicationInfo appInfo = appEntry.info; 281 UserHandle user = UserHandle.getUserHandleForUid(appInfo.uid); 282 IconFactory iconFactory = IconFactory.obtain(context); 283 Bitmap iconBmp = iconFactory.createBadgedIconBitmap( 284 appInfo.loadUnbadgedIcon(packageManager), user, true).icon; 285 return new BitmapDrawable(context.getResources(), iconBmp); 286 } 287 showApplicationIcon(ApplicationInfo appInfo, PackageManager packageManager)288 private static boolean showApplicationIcon(ApplicationInfo appInfo, 289 PackageManager packageManager) { 290 if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP)) { 291 return packageManager.getLaunchIntentForPackage(appInfo.packageName) 292 != null; 293 } 294 return !hasFlag(appInfo.flags, FLAG_SYSTEM); 295 } 296 hasFlag(int flags, int flag)297 private static boolean hasFlag(int flags, int flag) { 298 return (flags & flag) != 0; 299 } 300 } 301