1 /* 2 * Copyright (C) 2014 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.tv.settings.dialog.old; 18 19 import android.app.Fragment; 20 import android.app.FragmentManager; 21 import android.app.FragmentManager.OnBackStackChangedListener; 22 import android.app.FragmentTransaction; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.graphics.Color; 26 import android.graphics.drawable.ColorDrawable; 27 import android.net.Uri; 28 import android.os.Build; 29 import android.os.Bundle; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.view.animation.Interpolator; 34 35 import androidx.annotation.NonNull; 36 37 import com.android.tv.settings.R; 38 import com.android.tv.settings.core.instrumentation.InstrumentedActivity; 39 40 import java.util.ArrayList; 41 42 /** 43 * A DialogActivity has 2 fragments, a content fragment and a list fragment. 44 * <p> 45 * Subclasses should override to supply the content fragment and list items. 46 * <p> 47 * The DialogActivity will handle animating in and out. 48 * <p> 49 * This class will use a default layout, but a custom layout can be provided by 50 * calling {@link #setLayoutProperties} 51 */ 52 public abstract class DialogActivity extends InstrumentedActivity 53 implements ActionAdapter.Listener, OnBackStackChangedListener { 54 55 /** 56 * Dialog Content Fragment title. 57 */ 58 public static final String EXTRA_DIALOG_TITLE = "dialog_title"; 59 60 /** 61 * Dialog Content Fragment breadcrumb. 62 */ 63 public static final String EXTRA_DIALOG_BREADCRUMB = "dialog_breadcrumb"; 64 65 /** 66 * Dialog Content Fragment description. 67 */ 68 public static final String EXTRA_DIALOG_DESCRIPTION = "dialog_description"; 69 70 /** 71 * Dialog Content Fragment image uri. 72 */ 73 public static final String EXTRA_DIALOG_IMAGE_URI = "dialog_image_uri"; 74 75 /** 76 * Dialog Content Fragment image background color 77 */ 78 public static final String EXTRA_DIALOG_IMAGE_BACKGROUND_COLOR 79 = "dialog_image_background_color"; 80 81 /** 82 * Dialog Action Fragment actions starting index. 83 */ 84 public static final String EXTRA_DIALOG_ACTIONS_START_INDEX = "dialog_actions_start_index"; 85 86 /** 87 * Dialog Action Fragment actions. 88 */ 89 public static final String EXTRA_PARCELABLE_ACTIONS = "parcelable_actions"; 90 91 /** 92 * Whether DialogActivity should create Content Fragment and Action Fragment from extras. 93 */ 94 public static final String EXTRA_CREATE_FRAGMENT_FROM_EXTRA = "create_fragment_from_extra"; 95 96 public static final String TAG_DIALOG = "tag_dialog"; 97 public static final String BACKSTACK_NAME_DIALOG = "backstack_name_dialog"; 98 public static final String KEY_BACKSTACK_COUNT = "backstack_count"; 99 100 protected static final int ANIMATE_IN_DURATION = 250; 101 102 private DialogFragment mDialogFragment; 103 private int mLayoutResId = R.layout.lb_dialog_fragment; 104 private View mContent; 105 private int mLastBackStackCount = 0; 106 DialogActivity()107 public DialogActivity() { 108 mDialogFragment = new DialogFragment(); 109 mDialogFragment.setActivity(this); 110 } 111 createIntent(Context context, String title, String breadcrumb, String description, String imageUri, ArrayList<Action> actions)112 public static Intent createIntent(Context context, String title, 113 String breadcrumb, String description, String imageUri, 114 ArrayList<Action> actions) { 115 return createIntent(context, title, breadcrumb, description, imageUri, 116 Color.TRANSPARENT, actions); 117 } 118 createIntent(Context context, String title, String breadcrumb, String description, String imageUri, int imageBackground, ArrayList<Action> actions)119 public static Intent createIntent(Context context, String title, 120 String breadcrumb, String description, String imageUri, 121 int imageBackground, ArrayList<Action> actions) { 122 Intent intent = new Intent(context, DialogActivity.class); 123 intent.putExtra(EXTRA_DIALOG_TITLE, title); 124 intent.putExtra(EXTRA_DIALOG_BREADCRUMB, breadcrumb); 125 intent.putExtra(EXTRA_DIALOG_DESCRIPTION, description); 126 intent.putExtra(EXTRA_DIALOG_IMAGE_URI, imageUri); 127 intent.putExtra(EXTRA_DIALOG_IMAGE_BACKGROUND_COLOR, imageBackground); 128 intent.putParcelableArrayListExtra(EXTRA_PARCELABLE_ACTIONS, actions); 129 130 return intent; 131 } 132 createIntent(Context context, String title, String breadcrumb, String description, String imageUri, ArrayList<Action> actions, Class<? extends DialogActivity> activityClass)133 public static Intent createIntent(Context context, String title, 134 String breadcrumb, String description, String imageUri, 135 ArrayList<Action> actions, Class<? extends DialogActivity> activityClass) { 136 return createIntent(context, title, breadcrumb, description, imageUri, Color.TRANSPARENT, 137 actions, activityClass); 138 } 139 createIntent(Context context, String title, String breadcrumb, String description, String imageUri, int imageBackground, ArrayList<Action> actions, Class<? extends DialogActivity> activityClass)140 public static Intent createIntent(Context context, String title, 141 String breadcrumb, String description, String imageUri, int imageBackground, 142 ArrayList<Action> actions, Class<? extends DialogActivity> activityClass) { 143 Intent intent = new Intent(context, activityClass); 144 intent.putExtra(EXTRA_DIALOG_TITLE, title); 145 intent.putExtra(EXTRA_DIALOG_BREADCRUMB, breadcrumb); 146 intent.putExtra(EXTRA_DIALOG_DESCRIPTION, description); 147 intent.putExtra(EXTRA_DIALOG_IMAGE_URI, imageUri); 148 intent.putExtra(EXTRA_DIALOG_IMAGE_BACKGROUND_COLOR, imageBackground); 149 intent.putParcelableArrayListExtra(EXTRA_PARCELABLE_ACTIONS, actions); 150 151 return intent; 152 } 153 createIntent(Context context, String title, String breadcrumb, String description, String imageUri, int imageBackground, ArrayList<Action> actions, Class<? extends DialogActivity> activityClass, int startIndex)154 public static Intent createIntent(Context context, String title, 155 String breadcrumb, String description, String imageUri, int imageBackground, 156 ArrayList<Action> actions, Class<? extends DialogActivity> activityClass, 157 int startIndex) { 158 Intent intent = new Intent(context, activityClass); 159 intent.putExtra(EXTRA_DIALOG_TITLE, title); 160 intent.putExtra(EXTRA_DIALOG_BREADCRUMB, breadcrumb); 161 intent.putExtra(EXTRA_DIALOG_DESCRIPTION, description); 162 intent.putExtra(EXTRA_DIALOG_IMAGE_URI, imageUri); 163 intent.putExtra(EXTRA_DIALOG_IMAGE_BACKGROUND_COLOR, imageBackground); 164 intent.putParcelableArrayListExtra(EXTRA_PARCELABLE_ACTIONS, actions); 165 intent.putExtra(EXTRA_DIALOG_ACTIONS_START_INDEX, startIndex); 166 167 return intent; 168 } 169 getContentView()170 public View getContentView() { 171 return mContent; 172 } 173 174 @Override onCreate(Bundle savedInstanceState)175 protected void onCreate(Bundle savedInstanceState) { 176 // TODO: replace these hardcoded values with the commented constants whenever Hangouts 177 // updates their manifest to build against JB MR2. 178 if (Build.VERSION.SDK_INT >= 18 /* Build.VERSION_CODES.JELLY_BEAN_MR2 */) { 179 getWindow().addFlags(0x02000000 180 /* WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN */); 181 } 182 if(savedInstanceState != null) { 183 mLastBackStackCount = savedInstanceState.getInt(KEY_BACKSTACK_COUNT); 184 } 185 186 super.onCreate(savedInstanceState); 187 getFragmentManager().addOnBackStackChangedListener(this); 188 189 LayoutInflater helium = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); 190 mContent = helium.inflate(mLayoutResId, null); 191 setContentView(mContent); 192 if (mLayoutResId == R.layout.lb_dialog_fragment) { 193 helium.inflate(R.layout.dialog_container, (ViewGroup) mContent); 194 setDialogFragment(mDialogFragment); 195 } 196 197 Bundle bundle = getIntent().getExtras(); 198 if (bundle != null) { 199 boolean createFragmentFromExtra = bundle.getBoolean(EXTRA_CREATE_FRAGMENT_FROM_EXTRA); 200 if (createFragmentFromExtra) { 201 // If intent bundle is not null, and flag indicates that should create fragments, 202 // set ContentFragment and ActionFragment using bundle extras. 203 String title = bundle.getString(EXTRA_DIALOG_TITLE); 204 String breadcrumb = bundle.getString(EXTRA_DIALOG_BREADCRUMB); 205 String description = bundle.getString(EXTRA_DIALOG_DESCRIPTION); 206 String imageUriStr = bundle.getString(EXTRA_DIALOG_IMAGE_URI); 207 Uri imageUri = Uri.parse(imageUriStr); 208 int backgroundColor = bundle.getInt(EXTRA_DIALOG_IMAGE_BACKGROUND_COLOR); 209 210 ArrayList<Action> actions = 211 bundle.getParcelableArrayList(EXTRA_PARCELABLE_ACTIONS); 212 213 setContentFragment(ContentFragment.newInstance(title, breadcrumb, 214 description, imageUri, backgroundColor)); 215 216 setActionFragment(ActionFragment.newInstance(actions)); 217 } 218 } 219 } 220 221 @Override onSaveInstanceState(@onNull Bundle savedInstanceState)222 protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) { 223 super.onSaveInstanceState(savedInstanceState); 224 savedInstanceState.putInt(KEY_BACKSTACK_COUNT, mLastBackStackCount); 225 } 226 227 @Override onStart()228 protected void onStart() { 229 super.onStart(); 230 if (mLayoutResId == R.layout.lb_dialog_fragment) { 231 getDialogFragment().performEntryTransition(); 232 } 233 } 234 235 @Override onBackStackChanged()236 public void onBackStackChanged() { 237 int count = getFragmentManager().getBackStackEntryCount(); 238 if (count > 0 && count < mLastBackStackCount && DialogActivity.BACKSTACK_NAME_DIALOG.equals( 239 getFragmentManager().getBackStackEntryAt(count - 1).getName())) { 240 getFragmentManager().popBackStack(); 241 } 242 mLastBackStackCount = count; 243 } 244 245 @Override onActionClicked(Action action)246 public void onActionClicked(Action action) { 247 Intent intent = action.getIntent(); 248 if (intent != null) { 249 startActivity(intent); 250 finish(); 251 } 252 } 253 254 /** 255 * Disables the entry animation that normally happens onStart(). 256 */ disableEntryAnimation()257 protected void disableEntryAnimation() { 258 getDialogFragment().disableEntryAnimation(); 259 } 260 261 /** 262 * This method sets the layout property of this class. <br/> 263 * Activities extending {@link DialogActivity} should call this method 264 * before calling {@link #onCreate(Bundle)} if they want to have a 265 * custom view. 266 * 267 * @param layoutResId resource if of the activity layout 268 * @param contentAreaId id of the content area 269 * @param actionAreaId id of the action area 270 */ setLayoutProperties(int layoutResId, int contentAreaId, int actionAreaId)271 protected void setLayoutProperties(int layoutResId, int contentAreaId, int actionAreaId) { 272 mLayoutResId = layoutResId; 273 getDialogFragment().setLayoutProperties(contentAreaId, actionAreaId); 274 } 275 276 /** 277 * Animates a view. 278 * 279 * @param v view to animate 280 * @param initAlpha initial alpha 281 * @param initTransX initial translation in the X 282 * @param delay delay in ms 283 * @param duration duration in ms 284 * @param interpolator interpolator to be used, can be null 285 * @param isIcon if {@code true}, this is the main icon being moved 286 */ prepareAndAnimateView(final View v, float initAlpha, float initTransX, int delay, int duration, Interpolator interpolator, final boolean isIcon)287 protected void prepareAndAnimateView(final View v, float initAlpha, float initTransX, int delay, 288 int duration, Interpolator interpolator, final boolean isIcon) { 289 getDialogFragment().prepareAndAnimateView( 290 v, initAlpha, initTransX, delay, duration, interpolator, isIcon); 291 } 292 293 /** 294 * Called when intro animation is finished. 295 * <p> 296 * If a subclass is going to alter the view, should wait until this is called. 297 */ onIntroAnimationFinished()298 protected void onIntroAnimationFinished() { 299 getDialogFragment().onIntroAnimationFinished(); 300 } 301 isIntroAnimationInProgress()302 protected boolean isIntroAnimationInProgress() { 303 return getDialogFragment().isIntroAnimationInProgress(); 304 } 305 getBackgroundDrawable()306 protected ColorDrawable getBackgroundDrawable() { 307 return getDialogFragment().getBackgroundDrawable(); 308 } 309 setBackgroundDrawable(ColorDrawable drawable)310 protected void setBackgroundDrawable(ColorDrawable drawable) { 311 getDialogFragment().setBackgroundDrawable(drawable); 312 } 313 314 /** 315 * Sets the content fragment into the view. 316 */ setContentFragment(Fragment fragment)317 protected void setContentFragment(Fragment fragment) { 318 getDialogFragment().setContentFragment(fragment); 319 } 320 321 /** 322 * Sets the action fragment into the view. 323 * <p> 324 * If an action fragment currently exists, this will be added to the back stack. 325 */ setActionFragment(Fragment fragment)326 protected void setActionFragment(Fragment fragment) { 327 getDialogFragment().setActionFragment(fragment); 328 } 329 330 /** 331 * Sets the action fragment into the view. 332 * <p> 333 * If addToBackStack is true, and action fragment currently exists, 334 * this will be added to the back stack. 335 */ setActionFragment(Fragment fragment, boolean addToBackStack)336 protected void setActionFragment(Fragment fragment, boolean addToBackStack) { 337 getDialogFragment().setActionFragment(fragment, addToBackStack); 338 } 339 getActionFragment()340 protected Fragment getActionFragment() { 341 return getDialogFragment().getActionFragment(); 342 } 343 getContentFragment()344 protected Fragment getContentFragment() { 345 return getDialogFragment().getContentFragment(); 346 } 347 348 /** 349 * Set the content and action fragments in the same transaction. 350 * <p> 351 * If an action fragment currently exists, this will be added to the back stack. 352 */ setContentAndActionFragments(Fragment contentFragment, Fragment actionFragment)353 protected void setContentAndActionFragments(Fragment contentFragment, Fragment actionFragment) { 354 getDialogFragment().setContentAndActionFragments(contentFragment, actionFragment); 355 } 356 357 /** 358 * Set the content and action fragments in the same transaction. 359 * <p> 360 * If addToBackStack and an action fragment currently exists, 361 * this will be added to the back stack. 362 */ setContentAndActionFragments(Fragment contentFragment, Fragment actionFragment, boolean addToBackStack)363 protected void setContentAndActionFragments(Fragment contentFragment, Fragment actionFragment, 364 boolean addToBackStack) { 365 getDialogFragment().setContentAndActionFragments( 366 contentFragment, actionFragment, addToBackStack); 367 } 368 setDialogFragment(DialogFragment fragment)369 protected void setDialogFragment(DialogFragment fragment) { 370 setDialogFragment(fragment, true); 371 } 372 setDialogFragment(DialogFragment fragment, boolean addToBackStack)373 protected void setDialogFragment(DialogFragment fragment, boolean addToBackStack) { 374 mDialogFragment = fragment; 375 fragment.setActivity(this); 376 FragmentManager fm = getFragmentManager(); 377 FragmentTransaction ft = fm.beginTransaction(); 378 boolean hasDialog = fm.findFragmentByTag(DialogActivity.TAG_DIALOG) != null; 379 if (hasDialog) { 380 if (addToBackStack) { 381 ft.addToBackStack(DialogActivity.BACKSTACK_NAME_DIALOG); 382 } 383 } 384 ft.replace(R.id.dialog_fragment, fragment, DialogActivity.TAG_DIALOG); 385 ft.commit(); 386 } 387 getDialogFragment()388 protected DialogFragment getDialogFragment() { 389 final DialogFragment fragment = 390 (DialogFragment) getFragmentManager().findFragmentByTag(TAG_DIALOG); 391 if (fragment != null) { 392 mDialogFragment = fragment; 393 } 394 395 return mDialogFragment; 396 } 397 } 398