1 /* 2 * Copyright (C) 2017 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.settings.applications.manageapplications; 18 19 import static com.android.settings.applications.manageapplications.ManageApplications.ApplicationsAdapter; 20 import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_CLONED_APPS; 21 import static com.android.settings.applications.manageapplications.ManageApplications.LIST_TYPE_NONE; 22 23 import android.app.settings.SettingsEnums; 24 import android.content.Context; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.PackageManager; 27 import android.graphics.drawable.Drawable; 28 import android.os.AsyncTask; 29 import android.text.TextUtils; 30 import android.util.Log; 31 import android.view.LayoutInflater; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.widget.ImageView; 35 import android.widget.ProgressBar; 36 import android.widget.Switch; 37 import android.widget.TextView; 38 39 import androidx.annotation.StringRes; 40 import androidx.annotation.VisibleForTesting; 41 import androidx.fragment.app.FragmentActivity; 42 import androidx.recyclerview.widget.RecyclerView; 43 44 import com.android.settings.R; 45 import com.android.settings.overlay.FeatureFactory; 46 import com.android.settingslib.applications.ApplicationsState; 47 import com.android.settingslib.applications.ApplicationsState.AppEntry; 48 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 49 import com.android.settingslib.widget.LottieColorUtils; 50 51 import com.airbnb.lottie.LottieAnimationView; 52 53 54 public class ApplicationViewHolder extends RecyclerView.ViewHolder { 55 56 @VisibleForTesting 57 final TextView mAppName; 58 @VisibleForTesting 59 final TextView mSummary; 60 @VisibleForTesting 61 final TextView mDisabled; 62 @VisibleForTesting 63 final ViewGroup mWidgetContainer; 64 @VisibleForTesting 65 final Switch mSwitch; 66 final ImageView mAddIcon; 67 final ProgressBar mProgressBar; 68 69 private final ImageView mAppIcon; 70 ApplicationViewHolder(View itemView)71 ApplicationViewHolder(View itemView) { 72 super(itemView); 73 mAppName = itemView.findViewById(android.R.id.title); 74 mAppIcon = itemView.findViewById(android.R.id.icon); 75 mSummary = itemView.findViewById(android.R.id.summary); 76 mDisabled = itemView.findViewById(R.id.appendix); 77 mSwitch = itemView.findViewById(R.id.switchWidget); 78 mWidgetContainer = itemView.findViewById(android.R.id.widget_frame); 79 mAddIcon = itemView.findViewById(R.id.add_preference_widget); 80 mProgressBar = itemView.findViewById(R.id.progressBar_cyclic); 81 } 82 newView(ViewGroup parent)83 static View newView(ViewGroup parent) { 84 return newView(parent, false /* twoTarget */, LIST_TYPE_NONE /* listType */); 85 } 86 newView(ViewGroup parent, boolean twoTarget, int listType)87 static View newView(ViewGroup parent, boolean twoTarget, int listType) { 88 ViewGroup view = (ViewGroup) LayoutInflater.from(parent.getContext()) 89 .inflate(R.layout.preference_app, parent, false); 90 ViewGroup widgetFrame = view.findViewById(android.R.id.widget_frame); 91 if (twoTarget) { 92 if (widgetFrame != null) { 93 if (listType == LIST_TYPE_CLONED_APPS) { 94 LayoutInflater.from(parent.getContext()) 95 .inflate(R.layout.preference_widget_add_progressbar, widgetFrame, true); 96 } else { 97 LayoutInflater.from(parent.getContext()) 98 .inflate(R.layout.preference_widget_primary_switch, widgetFrame, true); 99 } 100 View divider = LayoutInflater.from(parent.getContext()).inflate( 101 R.layout.preference_two_target_divider, view, false); 102 // second to last, before widget frame 103 view.addView(divider, view.getChildCount() - 1); 104 } 105 } else if (widgetFrame != null) { 106 widgetFrame.setVisibility(View.GONE); 107 } 108 return view; 109 } 110 newHeader(ViewGroup parent, int resText)111 static View newHeader(ViewGroup parent, int resText) { 112 ViewGroup view = (ViewGroup) LayoutInflater.from(parent.getContext()) 113 .inflate(R.layout.preference_app_header, parent, false); 114 TextView textView = view.findViewById(R.id.apps_top_intro_text); 115 textView.setText(resText); 116 return view; 117 } 118 newHeaderWithAnimation(Context context, ViewGroup parent, int resIntroText, int resAnimation, int resAppListText)119 static View newHeaderWithAnimation(Context context, ViewGroup parent, int resIntroText, 120 int resAnimation, int resAppListText) { 121 ViewGroup view = (ViewGroup) LayoutInflater.from(parent.getContext()) 122 .inflate(R.layout.preference_app_header_animation, parent, false); 123 TextView introText = view.findViewById(R.id.apps_top_intro_text); 124 introText.setText(resIntroText); 125 126 LottieAnimationView illustrationLottie = view.findViewById(R.id.illustration_lottie); 127 illustrationLottie.setAnimation(resAnimation); 128 illustrationLottie.setVisibility(View.VISIBLE); 129 LottieColorUtils.applyDynamicColors(context, illustrationLottie); 130 131 TextView appListText = view.findViewById(R.id.app_list_text); 132 appListText.setText(resAppListText); 133 134 return view; 135 } 136 setSummary(CharSequence summary)137 void setSummary(CharSequence summary) { 138 mSummary.setText(summary); 139 updateSummaryVisibility(); 140 } 141 setSummary(@tringRes int summary)142 void setSummary(@StringRes int summary) { 143 mSummary.setText(summary); 144 updateSummaryVisibility(); 145 } 146 updateSummaryVisibility()147 private void updateSummaryVisibility() { 148 // Hide an empty summary and then title will be vertically centered. 149 mSummary.setVisibility(TextUtils.isEmpty(mSummary.getText()) ? View.GONE : View.VISIBLE); 150 } 151 setEnabled(boolean isEnabled)152 void setEnabled(boolean isEnabled) { 153 itemView.setEnabled(isEnabled); 154 } 155 setTitle(CharSequence title, CharSequence contentDescription)156 void setTitle(CharSequence title, CharSequence contentDescription) { 157 if (title == null) { 158 return; 159 } 160 mAppName.setText(title); 161 162 if (TextUtils.isEmpty(contentDescription)) { 163 return; 164 } 165 mAppName.setContentDescription(contentDescription); 166 } 167 setIcon(int drawableRes)168 void setIcon(int drawableRes) { 169 mAppIcon.setImageResource(drawableRes); 170 } 171 setIcon(Drawable icon)172 void setIcon(Drawable icon) { 173 if (icon == null) { 174 return; 175 } 176 mAppIcon.setImageDrawable(icon); 177 } 178 updateDisableView(ApplicationInfo info)179 void updateDisableView(ApplicationInfo info) { 180 if ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { 181 mDisabled.setVisibility(View.VISIBLE); 182 mDisabled.setText(R.string.not_installed); 183 } else if (!info.enabled || info.enabledSetting 184 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { 185 mDisabled.setVisibility(View.VISIBLE); 186 mDisabled.setText(R.string.disabled); 187 } else { 188 mDisabled.setVisibility(View.GONE); 189 } 190 } 191 updateSizeText(AppEntry entry, CharSequence invalidSizeStr, int whichSize)192 void updateSizeText(AppEntry entry, CharSequence invalidSizeStr, int whichSize) { 193 if (ManageApplications.DEBUG) { 194 Log.d(ManageApplications.TAG, "updateSizeText of " 195 + entry.label + " " + entry + ": " + entry.sizeStr); 196 } 197 if (entry.sizeStr != null) { 198 switch (whichSize) { 199 case ManageApplications.SIZE_INTERNAL: 200 setSummary(entry.internalSizeStr); 201 break; 202 case ManageApplications.SIZE_EXTERNAL: 203 setSummary(entry.externalSizeStr); 204 break; 205 default: 206 setSummary(entry.sizeStr); 207 break; 208 } 209 } else if (entry.size == ApplicationsState.SIZE_INVALID) { 210 setSummary(invalidSizeStr); 211 } 212 } 213 updateSwitch(Switch.OnCheckedChangeListener listener, boolean enabled, boolean checked)214 void updateSwitch(Switch.OnCheckedChangeListener listener, boolean enabled, boolean checked) { 215 if (mSwitch != null && mWidgetContainer != null) { 216 mWidgetContainer.setFocusable(false); 217 mWidgetContainer.setClickable(false); 218 mSwitch.setFocusable(true); 219 mSwitch.setClickable(true); 220 mSwitch.setOnCheckedChangeListener(listener); 221 mSwitch.setChecked(checked); 222 mSwitch.setEnabled(enabled); 223 } 224 } 225 updateAppCloneWidget(Context context, View.OnClickListener onClickListener, AppEntry entry)226 void updateAppCloneWidget(Context context, View.OnClickListener onClickListener, 227 AppEntry entry) { 228 if (mAddIcon != null) { 229 if (!entry.isCloned) { 230 mAddIcon.setBackground(context.getDrawable(R.drawable.ic_add_24dp)); 231 } else { 232 mAddIcon.setBackground(context.getDrawable(R.drawable.ic_trash_can)); 233 setSummary(R.string.cloned_app_created_summary); 234 } 235 mAddIcon.setOnClickListener(onClickListener); 236 } 237 } 238 appCloneOnClickListener(AppEntry entry, ApplicationsAdapter adapter, FragmentActivity manageApplicationsActivity)239 View.OnClickListener appCloneOnClickListener(AppEntry entry, 240 ApplicationsAdapter adapter, FragmentActivity manageApplicationsActivity) { 241 Context context = manageApplicationsActivity.getApplicationContext(); 242 return new View.OnClickListener() { 243 @Override 244 public void onClick(View v) { 245 CloneBackend cloneBackend = CloneBackend.getInstance(context); 246 final MetricsFeatureProvider metricsFeatureProvider = 247 FeatureFactory.getFactory(context).getMetricsFeatureProvider(); 248 249 String packageName = entry.info.packageName; 250 251 if (mWidgetContainer != null) { 252 if (!entry.isCloned) { 253 metricsFeatureProvider.action(context, 254 SettingsEnums.ACTION_CREATE_CLONE_APP); 255 mAddIcon.setVisibility(View.INVISIBLE); 256 mProgressBar.setVisibility(View.VISIBLE); 257 setSummary(R.string.cloned_app_creation_summary); 258 259 // todo(b/262352524): To figure out a way to prevent memory leak 260 // without making this static. 261 new AsyncTask<Void, Void, Integer>(){ 262 263 @Override 264 protected Integer doInBackground(Void... unused) { 265 return cloneBackend.installCloneApp(packageName); 266 } 267 268 @Override 269 protected void onPostExecute(Integer res) { 270 mProgressBar.setVisibility(View.INVISIBLE); 271 mAddIcon.setVisibility(View.VISIBLE); 272 273 if (res != CloneBackend.SUCCESS) { 274 setSummary(null); 275 return; 276 } 277 278 // Refresh the page to reflect newly created cloned app. 279 adapter.rebuild(); 280 } 281 }.execute(); 282 283 } else if (entry.isCloned) { 284 metricsFeatureProvider.action(context, 285 SettingsEnums.ACTION_DELETE_CLONE_APP); 286 cloneBackend.uninstallClonedApp(packageName, /*allUsers*/ false, 287 manageApplicationsActivity); 288 } 289 } 290 } 291 }; 292 } 293 } 294