1 /* 2 * Copyright (C) 2019 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.twopanelsettings.slices; 18 19 import static android.app.slice.Slice.EXTRA_SLIDER_VALUE; 20 import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; 21 import static android.app.slice.Slice.HINT_PARTIAL; 22 23 import static com.android.tv.twopanelsettings.slices.InstrumentationUtils.logEntrySelected; 24 import static com.android.tv.twopanelsettings.slices.InstrumentationUtils.logToggleInteracted; 25 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_PREFERENCE_INFO_STATUS; 26 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_PREFERENCE_KEY; 27 import static com.android.tv.twopanelsettings.slices.SlicesConstants.EXTRA_SLICE_FOLLOWUP; 28 29 import android.app.Activity; 30 import android.app.PendingIntent; 31 import android.app.PendingIntent.CanceledException; 32 import android.app.tvsettings.TvSettingsEnums; 33 import android.content.ContentProviderClient; 34 import android.content.Intent; 35 import android.content.IntentSender; 36 import android.database.ContentObserver; 37 import android.graphics.drawable.Icon; 38 import android.net.Uri; 39 import android.os.Bundle; 40 import android.os.Handler; 41 import android.os.Parcelable; 42 import android.text.TextUtils; 43 import android.util.Log; 44 import android.util.TypedValue; 45 import android.view.ContextThemeWrapper; 46 import android.view.Gravity; 47 import android.view.LayoutInflater; 48 import android.view.View; 49 import android.view.ViewGroup; 50 import android.widget.ImageView; 51 import android.widget.TextView; 52 53 import androidx.activity.result.ActivityResult; 54 import androidx.activity.result.ActivityResultCallback; 55 import androidx.activity.result.ActivityResultLauncher; 56 import androidx.activity.result.IntentSenderRequest; 57 import androidx.activity.result.contract.ActivityResultContracts; 58 import androidx.annotation.Keep; 59 import androidx.annotation.NonNull; 60 import androidx.lifecycle.Observer; 61 import androidx.preference.Preference; 62 import androidx.preference.PreferenceScreen; 63 import androidx.preference.TwoStatePreference; 64 import androidx.slice.Slice; 65 import androidx.slice.SliceItem; 66 import androidx.slice.widget.ListContent; 67 import androidx.slice.widget.SliceContent; 68 69 import com.android.tv.twopanelsettings.R; 70 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment; 71 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment.SliceFragmentCallback; 72 import com.android.tv.twopanelsettings.slices.PreferenceSliceLiveData.SliceLiveDataImpl; 73 import com.android.tv.twopanelsettings.slices.SlicePreferencesUtil.Data; 74 75 import java.util.ArrayList; 76 import java.util.List; 77 78 /** 79 * A screen presenting a slice in TV settings. 80 */ 81 @Keep 82 public class SliceFragment extends SettingsPreferenceFragment implements Observer<Slice>, 83 SliceFragmentCallback { 84 private static final int SLICE_REQUEST_CODE = 10000; 85 private static final String TAG = "SliceFragment"; 86 private static final String KEY_PREFERENCE_FOLLOWUP_INTENT = "key_preference_followup_intent"; 87 private static final String KEY_PREFERENCE_FOLLOWUP_RESULT_CODE = 88 "key_preference_followup_result_code"; 89 private static final String KEY_SCREEN_TITLE = "key_screen_title"; 90 private static final String KEY_SCREEN_SUBTITLE = "key_screen_subtitle"; 91 private static final String KEY_SCREEN_ICON = "key_screen_icon"; 92 private static final String KEY_LAST_PREFERENCE = "key_last_preference"; 93 private static final String KEY_URI_STRING = "key_uri_string"; 94 private ListContent mListContent; 95 private Slice mSlice; 96 private ContextThemeWrapper mContextThemeWrapper; 97 private String mUriString = null; 98 private int mCurrentPageId; 99 private CharSequence mScreenTitle; 100 private CharSequence mScreenSubtitle; 101 private Icon mScreenIcon; 102 private PendingIntent mPreferenceFollowupIntent; 103 private int mFollowupPendingIntentResultCode; 104 private Intent mFollowupPendingIntentExtras; 105 private Intent mFollowupPendingIntentExtrasCopy; 106 private String mLastFocusedPreferenceKey; 107 private boolean mIsMainPanelReady = true; 108 109 private final Handler mHandler = new Handler(); 110 private final ActivityResultLauncher<IntentSenderRequest> mActivityResultLauncher = 111 registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(), 112 new ActivityResultCallback<ActivityResult>() { 113 @Override 114 public void onActivityResult(ActivityResult result) { 115 Intent data = result.getData(); 116 mFollowupPendingIntentExtras = data; 117 mFollowupPendingIntentExtrasCopy = data == null ? null : new Intent( 118 data); 119 mFollowupPendingIntentResultCode = result.getResultCode(); 120 } 121 }); 122 private final ContentObserver mContentObserver = new ContentObserver(new Handler()) { 123 @Override 124 public void onChange(boolean selfChange, Uri uri) { 125 handleUri(uri); 126 super.onChange(selfChange, uri); 127 } 128 }; 129 130 /** Callback for one panel settings fragment **/ 131 public interface OnePanelSliceFragmentContainer { navigateBack()132 void navigateBack(); 133 } 134 135 @Override onCreate(Bundle savedInstanceState)136 public void onCreate(Bundle savedInstanceState) { 137 mUriString = getArguments().getString(SlicesConstants.TAG_TARGET_URI); 138 ContextSingleton.getInstance().grantFullAccess(getContext(), Uri.parse(mUriString)); 139 super.onCreate(savedInstanceState); 140 } 141 142 @Override onResume()143 public void onResume() { 144 this.setTitle(mScreenTitle); 145 this.setSubtitle(mScreenSubtitle); 146 this.setIcon(mScreenIcon); 147 this.getPreferenceScreen().removeAll(); 148 149 showProgressBar(); 150 getSliceLiveData().observeForever(this); 151 if (TextUtils.isEmpty(mScreenTitle)) { 152 mScreenTitle = getArguments().getCharSequence(SlicesConstants.TAG_SCREEN_TITLE, ""); 153 } 154 super.onResume(); 155 getContext().getContentResolver().registerContentObserver( 156 SlicePreferencesUtil.getStatusPath(mUriString), false, mContentObserver); 157 fireFollowupPendingIntent(); 158 } 159 getSliceLiveData()160 private SliceLiveDataImpl getSliceLiveData() { 161 return ContextSingleton.getInstance() 162 .getSliceLiveData(getActivity(), Uri.parse(mUriString)); 163 } 164 fireFollowupPendingIntent()165 private void fireFollowupPendingIntent() { 166 if (mFollowupPendingIntentExtras == null) { 167 return; 168 } 169 // If there is followup pendingIntent returned from initial activity, send it. 170 // Otherwise send the followup pendingIntent provided by slice api. 171 Parcelable followupPendingIntent; 172 try { 173 followupPendingIntent = mFollowupPendingIntentExtrasCopy.getParcelableExtra( 174 EXTRA_SLICE_FOLLOWUP); 175 } catch (Throwable ex) { 176 // unable to parse, the Intent has custom Parcelable, fallback 177 followupPendingIntent = null; 178 } 179 if (followupPendingIntent instanceof PendingIntent) { 180 try { 181 ((PendingIntent) followupPendingIntent).send(); 182 } catch (CanceledException e) { 183 Log.e(TAG, "Followup PendingIntent for slice cannot be sent", e); 184 } 185 } else { 186 if (mPreferenceFollowupIntent == null) { 187 return; 188 } 189 try { 190 mPreferenceFollowupIntent.send(getContext(), 191 mFollowupPendingIntentResultCode, mFollowupPendingIntentExtras); 192 } catch (CanceledException e) { 193 Log.e(TAG, "Followup PendingIntent for slice cannot be sent", e); 194 } 195 mPreferenceFollowupIntent = null; 196 } 197 } 198 199 @Override onPause()200 public void onPause() { 201 super.onPause(); 202 hideProgressBar(); 203 getContext().getContentResolver().unregisterContentObserver(mContentObserver); 204 getSliceLiveData().removeObserver(this); 205 } 206 207 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)208 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 209 PreferenceScreen preferenceScreen = getPreferenceManager() 210 .createPreferenceScreen(getContext()); 211 setPreferenceScreen(preferenceScreen); 212 213 TypedValue themeTypedValue = new TypedValue(); 214 getActivity().getTheme().resolveAttribute(R.attr.preferenceTheme, themeTypedValue, true); 215 mContextThemeWrapper = new ContextThemeWrapper(getActivity(), themeTypedValue.resourceId); 216 217 } 218 isUriValid(String uri)219 private boolean isUriValid(String uri) { 220 if (uri == null) { 221 return false; 222 } 223 ContentProviderClient client = 224 getContext().getContentResolver().acquireContentProviderClient(Uri.parse(uri)); 225 if (client != null) { 226 client.close(); 227 return true; 228 } else { 229 return false; 230 } 231 } 232 update()233 private void update() { 234 mListContent = new ListContent(mSlice); 235 PreferenceScreen preferenceScreen = 236 getPreferenceManager().getPreferenceScreen(); 237 238 if (preferenceScreen == null) { 239 return; 240 } 241 242 List<SliceContent> items = mListContent.getRowItems(); 243 if (items == null || items.size() == 0) { 244 return; 245 } 246 247 SliceItem redirectSliceItem = SlicePreferencesUtil.getRedirectSlice(items); 248 String redirectSlice = null; 249 if (redirectSliceItem != null) { 250 Data data = SlicePreferencesUtil.extract(redirectSliceItem); 251 CharSequence title = SlicePreferencesUtil.getText(data.mTitleItem); 252 if (!TextUtils.isEmpty(title)) { 253 redirectSlice = title.toString(); 254 } 255 } 256 if (isUriValid(redirectSlice)) { 257 getSliceLiveData().removeObserver(this); 258 getContext().getContentResolver().unregisterContentObserver(mContentObserver); 259 mUriString = redirectSlice; 260 getSliceLiveData().observeForever(this); 261 getContext().getContentResolver().registerContentObserver( 262 SlicePreferencesUtil.getStatusPath(mUriString), false, mContentObserver); 263 } 264 265 SliceItem screenTitleItem = SlicePreferencesUtil.getScreenTitleItem(items); 266 if (screenTitleItem == null) { 267 setTitle(mScreenTitle); 268 } else { 269 Data data = SlicePreferencesUtil.extract(screenTitleItem); 270 mCurrentPageId = SlicePreferencesUtil.getPageId(screenTitleItem); 271 CharSequence title = SlicePreferencesUtil.getText(data.mTitleItem); 272 if (!TextUtils.isEmpty(title)) { 273 setTitle(title); 274 mScreenTitle = title; 275 } else { 276 setTitle(mScreenTitle); 277 } 278 279 CharSequence subtitle = SlicePreferencesUtil.getText(data.mSubtitleItem); 280 setSubtitle(subtitle); 281 282 Icon icon = SlicePreferencesUtil.getIcon(data.mStartItem); 283 setIcon(icon); 284 } 285 286 SliceItem focusedPrefItem = SlicePreferencesUtil.getFocusedPreferenceItem(items); 287 CharSequence defaultFocusedKey = null; 288 if (focusedPrefItem != null) { 289 Data data = SlicePreferencesUtil.extract(focusedPrefItem); 290 CharSequence title = SlicePreferencesUtil.getText(data.mTitleItem); 291 if (!TextUtils.isEmpty(title)) { 292 defaultFocusedKey = title; 293 } 294 } 295 296 List<Preference> newPrefs = new ArrayList<>(); 297 for (SliceContent contentItem : items) { 298 SliceItem item = contentItem.getSliceItem(); 299 if (SlicesConstants.TYPE_PREFERENCE.equals(item.getSubType()) 300 || SlicesConstants.TYPE_PREFERENCE_CATEGORY.equals(item.getSubType())) { 301 Preference preference = 302 SlicePreferencesUtil.getPreference( 303 item, mContextThemeWrapper, getClass().getCanonicalName(), 304 getParentFragment() instanceof TwoPanelSettingsFragment); 305 if (preference != null) { 306 newPrefs.add(preference); 307 } 308 } 309 } 310 updatePreferenceScreen(preferenceScreen, newPrefs); 311 if (defaultFocusedKey != null) { 312 scrollToPreference(defaultFocusedKey.toString()); 313 } else if (mLastFocusedPreferenceKey != null) { 314 scrollToPreference(mLastFocusedPreferenceKey); 315 } 316 317 if (getParentFragment() instanceof TwoPanelSettingsFragment) { 318 ((TwoPanelSettingsFragment) getParentFragment()).refocusPreference(this); 319 } 320 mIsMainPanelReady = true; 321 } 322 323 back()324 private void back() { 325 if (getCallbackFragment() instanceof TwoPanelSettingsFragment) { 326 TwoPanelSettingsFragment parentFragment = 327 (TwoPanelSettingsFragment) getCallbackFragment(); 328 if (parentFragment.isFragmentInTheMainPanel(this)) { 329 parentFragment.navigateBack(); 330 } 331 } else if (getCallbackFragment() instanceof OnePanelSliceFragmentContainer) { 332 ((OnePanelSliceFragmentContainer) getCallbackFragment()).navigateBack(); 333 } 334 } 335 forward()336 private void forward() { 337 if (mIsMainPanelReady) { 338 if (getCallbackFragment() instanceof TwoPanelSettingsFragment) { 339 TwoPanelSettingsFragment parentFragment = 340 (TwoPanelSettingsFragment) getCallbackFragment(); 341 Preference chosenPreference = TwoPanelSettingsFragment.getChosenPreference(this); 342 if (chosenPreference == null && mLastFocusedPreferenceKey != null) { 343 chosenPreference = findPreference(mLastFocusedPreferenceKey); 344 } 345 if (chosenPreference != null && chosenPreference instanceof HasSliceUri 346 && ((HasSliceUri) chosenPreference).getUri() != null) { 347 chosenPreference.setFragment(SliceFragment.class.getCanonicalName()); 348 parentFragment.refocusPreferenceForceRefresh(chosenPreference); 349 } 350 if (parentFragment.isFragmentInTheMainPanel(this)) { 351 parentFragment.navigateToPreviewFragment(); 352 } 353 } 354 } else { 355 mHandler.post(() -> forward()); 356 } 357 } 358 updatePreferenceScreen(PreferenceScreen screen, List<Preference> newPrefs)359 private void updatePreferenceScreen(PreferenceScreen screen, List<Preference> newPrefs) { 360 // Remove all the preferences in the screen that satisfy such two cases: 361 // (a) Preference without key 362 // (b) Preference with key which does not appear in the new list. 363 int index = 0; 364 while (index < screen.getPreferenceCount()) { 365 boolean needToRemoveCurrentPref = true; 366 Preference oldPref = screen.getPreference(index); 367 if (oldPref != null && oldPref.getKey() != null) { 368 for (Preference newPref : newPrefs) { 369 if (newPref.getKey() != null && newPref.getKey().equals(oldPref.getKey())) { 370 needToRemoveCurrentPref = false; 371 break; 372 } 373 } 374 } 375 if (needToRemoveCurrentPref) { 376 screen.removePreference(oldPref); 377 } else { 378 index++; 379 } 380 } 381 382 //Iterate the new preferences list and give each preference a correct order 383 for (int i = 0; i < newPrefs.size(); i++) { 384 Preference newPref = newPrefs.get(i); 385 boolean neededToAddNewPref = true; 386 // If the newPref has a key and has a corresponding old preference, update the old 387 // preference and give it a new order. 388 if (newPref.getKey() != null) { 389 for (int j = 0; j < screen.getPreferenceCount(); j++) { 390 Preference oldPref = screen.getPreference(j); 391 if (oldPref.getKey() != null && oldPref.getKey().equals(newPref.getKey())) { 392 oldPref.setIcon(newPref.getIcon()); 393 oldPref.setTitle(newPref.getTitle()); 394 oldPref.setSummary(newPref.getSummary()); 395 oldPref.setEnabled(newPref.isEnabled()); 396 oldPref.setSelectable(newPref.isSelectable()); 397 oldPref.setFragment(newPref.getFragment()); 398 oldPref.getExtras().putAll(newPref.getExtras()); 399 if ((oldPref instanceof TwoStatePreference) 400 && (newPref instanceof TwoStatePreference)) { 401 ((TwoStatePreference) oldPref) 402 .setChecked(((TwoStatePreference) newPref).isChecked()); 403 } 404 if ((oldPref instanceof HasSliceAction) 405 && (newPref instanceof HasSliceAction)) { 406 ((HasSliceAction) oldPref) 407 .setSliceAction(((HasSliceAction) newPref).getSliceAction()); 408 } 409 if ((oldPref instanceof HasSliceUri) 410 && (newPref instanceof HasSliceUri)) { 411 ((HasSliceUri) oldPref) 412 .setUri(((HasSliceUri) newPref).getUri()); 413 } 414 oldPref.setOrder(i); 415 neededToAddNewPref = false; 416 break; 417 } 418 } 419 } 420 // If the newPref cannot find a corresponding old preference, or it does not have a key, 421 // add it to the screen with the correct order. 422 if (neededToAddNewPref) { 423 newPref.setOrder(i); 424 screen.addPreference(newPref); 425 } 426 } 427 removeAnimationClipping(getView()); 428 } 429 removeAnimationClipping(View v)430 protected void removeAnimationClipping(View v) { 431 if (v instanceof ViewGroup) { 432 ((ViewGroup) v).setClipChildren(false); 433 ((ViewGroup) v).setClipToPadding(false); 434 for (int index = 0; index < ((ViewGroup) v).getChildCount(); index++) { 435 View child = ((ViewGroup) v).getChildAt(index); 436 removeAnimationClipping(child); 437 } 438 } 439 } 440 441 @Override onPreferenceFocused(Preference preference)442 public void onPreferenceFocused(Preference preference) { 443 setLastFocused(preference); 444 } 445 446 @Override onSeekbarPreferenceChanged(SliceSeekbarPreference preference, int addValue)447 public void onSeekbarPreferenceChanged(SliceSeekbarPreference preference, int addValue) { 448 int curValue = preference.getValue(); 449 if((addValue > 0 && curValue < preference.getMax()) || 450 (addValue < 0 && curValue > preference.getMin())) { 451 preference.setValue(curValue + addValue); 452 453 try { 454 Intent fillInIntent = 455 new Intent() 456 .putExtra(EXTRA_SLIDER_VALUE, preference.getValue()) 457 .putExtra(EXTRA_PREFERENCE_KEY, preference.getKey()); 458 firePendingIntent((HasSliceAction) preference, fillInIntent); 459 } catch (Exception e) { 460 Log.e(TAG, "PendingIntent for slice cannot be sent", e); 461 } 462 } 463 } 464 465 @Override onPreferenceTreeClick(Preference preference)466 public boolean onPreferenceTreeClick(Preference preference) { 467 if (preference instanceof SliceRadioPreference) { 468 SliceRadioPreference radioPref = (SliceRadioPreference) preference; 469 if (!radioPref.isChecked()) { 470 radioPref.setChecked(true); 471 if (TextUtils.isEmpty(radioPref.getUri())) { 472 return true; 473 } 474 } 475 476 logEntrySelected(getPreferenceActionId(preference)); 477 Intent fillInIntent = new Intent().putExtra(EXTRA_PREFERENCE_KEY, preference.getKey()); 478 479 boolean result = firePendingIntent(radioPref, fillInIntent); 480 radioPref.clearOtherRadioPreferences(getPreferenceScreen()); 481 if (result) { 482 return true; 483 } 484 } else if (preference instanceof TwoStatePreference 485 && preference instanceof HasSliceAction) { 486 boolean isChecked = ((TwoStatePreference) preference).isChecked(); 487 preference.getExtras().putBoolean(EXTRA_PREFERENCE_INFO_STATUS, isChecked); 488 if (getParentFragment() instanceof TwoPanelSettingsFragment) { 489 ((TwoPanelSettingsFragment) getParentFragment()).refocusPreference(this); 490 } 491 logToggleInteracted(getPreferenceActionId(preference), isChecked); 492 Intent fillInIntent = 493 new Intent() 494 .putExtra(EXTRA_TOGGLE_STATE, isChecked) 495 .putExtra(EXTRA_PREFERENCE_KEY, preference.getKey()); 496 if (firePendingIntent((HasSliceAction) preference, fillInIntent)) { 497 return true; 498 } 499 return true; 500 } else if (preference instanceof SlicePreference) { 501 // In this case, we may intentionally ignore this entry selection to avoid double 502 // logging as the action should result in a PAGE_FOCUSED event being logged. 503 if (getPreferenceActionId(preference) != TvSettingsEnums.ENTRY_DEFAULT) { 504 logEntrySelected(getPreferenceActionId(preference)); 505 } 506 Intent fillInIntent = 507 new Intent().putExtra(EXTRA_PREFERENCE_KEY, preference.getKey()); 508 if (firePendingIntent((HasSliceAction) preference, fillInIntent)) { 509 return true; 510 } 511 } 512 513 return super.onPreferenceTreeClick(preference); 514 } 515 firePendingIntent(@onNull HasSliceAction preference, Intent fillInIntent)516 private boolean firePendingIntent(@NonNull HasSliceAction preference, Intent fillInIntent) { 517 if (preference.getSliceAction() == null) { 518 return false; 519 } 520 IntentSender intentSender = preference.getSliceAction().getAction().getIntentSender(); 521 mActivityResultLauncher.launch( 522 new IntentSenderRequest.Builder(intentSender).setFillInIntent( 523 fillInIntent).build()); 524 if (preference.getFollowupSliceAction() != null) { 525 mPreferenceFollowupIntent = preference.getFollowupSliceAction().getAction(); 526 } 527 528 return true; 529 } 530 531 @Override onSaveInstanceState(Bundle outState)532 public void onSaveInstanceState(Bundle outState) { 533 super.onSaveInstanceState(outState); 534 outState.putParcelable(KEY_PREFERENCE_FOLLOWUP_INTENT, mPreferenceFollowupIntent); 535 outState.putInt(KEY_PREFERENCE_FOLLOWUP_RESULT_CODE, mFollowupPendingIntentResultCode); 536 outState.putCharSequence(KEY_SCREEN_TITLE, mScreenTitle); 537 outState.putCharSequence(KEY_SCREEN_SUBTITLE, mScreenSubtitle); 538 outState.putParcelable(KEY_SCREEN_ICON, mScreenIcon); 539 outState.putString(KEY_LAST_PREFERENCE, mLastFocusedPreferenceKey); 540 outState.putString(KEY_URI_STRING, mUriString); 541 } 542 543 @Override onActivityCreated(Bundle savedInstanceState)544 public void onActivityCreated(Bundle savedInstanceState) { 545 super.onActivityCreated(savedInstanceState); 546 if (savedInstanceState != null) { 547 mPreferenceFollowupIntent = 548 savedInstanceState.getParcelable(KEY_PREFERENCE_FOLLOWUP_INTENT); 549 mFollowupPendingIntentResultCode = 550 savedInstanceState.getInt(KEY_PREFERENCE_FOLLOWUP_RESULT_CODE); 551 mScreenTitle = savedInstanceState.getCharSequence(KEY_SCREEN_TITLE); 552 mScreenSubtitle = savedInstanceState.getCharSequence(KEY_SCREEN_SUBTITLE); 553 mScreenIcon = savedInstanceState.getParcelable(KEY_SCREEN_ICON); 554 mLastFocusedPreferenceKey = savedInstanceState.getString(KEY_LAST_PREFERENCE); 555 mUriString = savedInstanceState.getString(KEY_URI_STRING); 556 } 557 } 558 559 @Override onChanged(@onNull Slice slice)560 public void onChanged(@NonNull Slice slice) { 561 mSlice = slice; 562 // Make TvSettings guard against the case that slice provider is not set up correctly 563 if (slice == null || slice.getHints() == null) { 564 return; 565 } 566 567 if (slice.getHints().contains(HINT_PARTIAL)) { 568 showProgressBar(); 569 } else { 570 hideProgressBar(); 571 } 572 mIsMainPanelReady = false; 573 update(); 574 } 575 showProgressBar()576 private void showProgressBar() { 577 View view = this.getView(); 578 View progressBar = view == null ? null : getView().findViewById(R.id.progress_bar); 579 if (progressBar != null) { 580 progressBar.bringToFront(); 581 progressBar.setVisibility(View.VISIBLE); 582 } 583 } 584 hideProgressBar()585 private void hideProgressBar() { 586 View view = this.getView(); 587 View progressBar = view == null ? null : getView().findViewById(R.id.progress_bar); 588 if (progressBar != null) { 589 progressBar.setVisibility(View.GONE); 590 } 591 } 592 setSubtitle(CharSequence subtitle)593 private void setSubtitle(CharSequence subtitle) { 594 View view = this.getView(); 595 TextView decorSubtitle = view == null 596 ? null 597 : (TextView) view.findViewById(R.id.decor_subtitle); 598 if (decorSubtitle != null) { 599 // This is to remedy some complicated RTL scenario such as Hebrew RTL Account slice with 600 // English account name subtitle. 601 if (getResources().getConfiguration().getLayoutDirection() 602 == View.LAYOUT_DIRECTION_RTL) { 603 decorSubtitle.setGravity(Gravity.TOP | Gravity.RIGHT); 604 } 605 if (TextUtils.isEmpty(subtitle)) { 606 decorSubtitle.setVisibility(View.GONE); 607 } else { 608 decorSubtitle.setVisibility(View.VISIBLE); 609 decorSubtitle.setText(subtitle); 610 } 611 } 612 mScreenSubtitle = subtitle; 613 } 614 setIcon(Icon icon)615 private void setIcon(Icon icon) { 616 View view = this.getView(); 617 ImageView decorIcon = view == null ? null : (ImageView) view.findViewById(R.id.decor_icon); 618 if (decorIcon != null && icon != null) { 619 TextView decorTitle = view.findViewById(R.id.decor_title); 620 if (decorTitle != null) { 621 decorTitle.setMaxWidth( 622 getResources().getDimensionPixelSize(R.dimen.decor_title_width)); 623 } 624 decorIcon.setImageDrawable(icon.loadDrawable(mContextThemeWrapper)); 625 decorIcon.setVisibility(View.VISIBLE); 626 } else if (decorIcon != null) { 627 decorIcon.setVisibility(View.GONE); 628 } 629 mScreenIcon = icon; 630 } 631 632 @Override onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)633 public View onCreateView( 634 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 635 final ViewGroup view = 636 (ViewGroup) super.onCreateView(inflater, container, savedInstanceState); 637 LayoutInflater themedInflater = LayoutInflater.from(view.getContext()); 638 final View newTitleContainer = themedInflater.inflate( 639 R.layout.slice_title_container, container, false); 640 view.removeView(view.findViewById(R.id.decor_title_container)); 641 view.addView(newTitleContainer, 0); 642 643 if (newTitleContainer != null) { 644 newTitleContainer.setOutlineProvider(null); 645 newTitleContainer.setBackgroundResource(R.color.tp_preference_panel_background_color); 646 } 647 648 final View newContainer = 649 themedInflater.inflate(R.layout.slice_progress_bar, container, false); 650 if (newContainer != null) { 651 ((ViewGroup) newContainer).addView(view); 652 } 653 return newContainer; 654 } 655 setLastFocused(Preference preference)656 public void setLastFocused(Preference preference) { 657 mLastFocusedPreferenceKey = preference.getKey(); 658 } 659 handleUri(Uri uri)660 private void handleUri(Uri uri) { 661 String uriString = uri.getQueryParameter(SlicesConstants.PARAMETER_URI); 662 // Provider should provide the correct slice uri in the parameter if it wants to do certain 663 // action(includes go back, forward, error message), otherwise TvSettings would ignore it. 664 if (uriString == null || !uriString.equals(mUriString)) { 665 return; 666 } 667 String direction = uri.getQueryParameter(SlicesConstants.PARAMETER_DIRECTION); 668 if (direction != null) { 669 if (direction.equals(SlicesConstants.FORWARD)) { 670 forward(); 671 } else if (direction.equals(SlicesConstants.BACKWARD)) { 672 back(); 673 } else if (direction.equals(SlicesConstants.EXIT)) { 674 finish(); 675 } 676 } 677 678 String errorMessage = uri.getQueryParameter(SlicesConstants.PARAMETER_ERROR); 679 if (errorMessage != null) { 680 showErrorMessage(errorMessage); 681 } 682 } 683 finish()684 private void finish() { 685 getActivity().setResult(Activity.RESULT_OK); 686 getActivity().finish(); 687 } 688 showErrorMessage(String errorMessage)689 private void showErrorMessage(String errorMessage) { 690 if (getCallbackFragment() instanceof TwoPanelSettingsFragment) { 691 ((TwoPanelSettingsFragment) getCallbackFragment()).showErrorMessage(errorMessage, this); 692 } 693 } 694 getPreferenceActionId(Preference preference)695 private int getPreferenceActionId(Preference preference) { 696 if (preference instanceof HasSliceAction) { 697 return ((HasSliceAction) preference).getActionId() != 0 698 ? ((HasSliceAction) preference).getActionId() 699 : TvSettingsEnums.ENTRY_DEFAULT; 700 } 701 return TvSettingsEnums.ENTRY_DEFAULT; 702 } 703 getScreenTitle()704 public CharSequence getScreenTitle() { 705 return mScreenTitle; 706 } 707 708 @Override getPageId()709 protected int getPageId() { 710 return mCurrentPageId != 0 ? mCurrentPageId : TvSettingsEnums.PAGE_SLICE_DEFAULT; 711 } 712 713 @Deprecated getMetricsCategory()714 public int getMetricsCategory() { 715 return 0; 716 } 717 } 718