SettingsActivity may send Settings 2-pane deep link Intent to SettingsHomepageActivity if split callback tells that there is no SplitInfo. Wallpaper app sends deep link Intent to SettingsHomepageActivity, and then SettingsHomepageActivity starts Wallpaper app page. The flow should be 2-pane organized but the split callback tells that splitInfos is empty. This change checks if the Intent is from SettingsHomepageActivity to prevent the Intent loop by the bug. Bug: 205206004 Test: manual Long click Launcher -> Wallpaper & style Change-Id: I6b9d76c136238e556384e891ae1700ae7c1bb41f
335 lines
13 KiB
Java
335 lines
13 KiB
Java
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.settings.homepage;
|
|
|
|
import static android.provider.Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY;
|
|
import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY;
|
|
import static android.provider.Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI;
|
|
|
|
import android.animation.LayoutTransition;
|
|
import android.app.ActivityManager;
|
|
import android.app.settings.SettingsEnums;
|
|
import android.content.ComponentName;
|
|
import android.content.Intent;
|
|
import android.os.Bundle;
|
|
import android.text.TextUtils;
|
|
import android.util.ArraySet;
|
|
import android.util.FeatureFlagUtils;
|
|
import android.util.Log;
|
|
import android.view.View;
|
|
import android.widget.FrameLayout;
|
|
import android.widget.ImageView;
|
|
import android.widget.Toolbar;
|
|
|
|
import androidx.fragment.app.Fragment;
|
|
import androidx.fragment.app.FragmentActivity;
|
|
import androidx.fragment.app.FragmentManager;
|
|
import androidx.fragment.app.FragmentTransaction;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settings.Settings;
|
|
import com.android.settings.SettingsActivity;
|
|
import com.android.settings.SettingsApplication;
|
|
import com.android.settings.Utils;
|
|
import com.android.settings.accounts.AvatarViewMixin;
|
|
import com.android.settings.activityembedding.ActivityEmbeddingRulesController;
|
|
import com.android.settings.activityembedding.ActivityEmbeddingUtils;
|
|
import com.android.settings.core.CategoryMixin;
|
|
import com.android.settings.core.FeatureFlags;
|
|
import com.android.settings.homepage.contextualcards.ContextualCardsFragment;
|
|
import com.android.settings.overlay.FeatureFactory;
|
|
import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin;
|
|
|
|
import java.net.URISyntaxException;
|
|
import java.util.Set;
|
|
|
|
/** Settings homepage activity */
|
|
public class SettingsHomepageActivity extends FragmentActivity implements
|
|
CategoryMixin.CategoryHandler {
|
|
|
|
private static final String TAG = "SettingsHomepageActivity";
|
|
|
|
// Additional extra of Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK.
|
|
// Put true value to the intent when startActivity for a deep link intent from this Activity.
|
|
public static final String EXTRA_IS_FROM_SETTINGS_HOMEPAGE = "is_from_settings_homepage";
|
|
|
|
// Additional extra of Settings#ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK.
|
|
// Set & get Uri of the Intent separately to prevent failure of Intent#ParseUri.
|
|
public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA =
|
|
"settings_large_screen_deep_link_intent_data";
|
|
|
|
// An alias class name of SettingsHomepageActivity.
|
|
public static final String ALIAS_DEEP_LINK = "com.android.settings.DeepLinkHomepageActivity";
|
|
|
|
private static final int DEFAULT_HIGHLIGHT_MENU_KEY = R.string.menu_key_network;
|
|
private static final long HOMEPAGE_LOADING_TIMEOUT_MS = 300;
|
|
|
|
private View mHomepageView;
|
|
private View mSuggestionView;
|
|
private CategoryMixin mCategoryMixin;
|
|
private Set<HomepageLoadedListener> mLoadedListeners;
|
|
|
|
/** A listener receiving homepage loaded events. */
|
|
public interface HomepageLoadedListener {
|
|
/** Called when the homepage is loaded. */
|
|
void onHomepageLoaded();
|
|
}
|
|
|
|
/**
|
|
* Try to register a {@link HomepageLoadedListener}. If homepage is already loaded, the
|
|
* listener will not be notified.
|
|
*
|
|
* @return Whether the listener should be registered.
|
|
*/
|
|
public boolean registerHomepageLoadedListenerIfNeeded(HomepageLoadedListener listener) {
|
|
if (mHomepageView == null) {
|
|
return false;
|
|
} else {
|
|
mLoadedListeners.add(listener);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shows the homepage and shows/hides the suggestion together. Only allows to be executed once
|
|
* to avoid the flicker caused by the suggestion suddenly appearing/disappearing.
|
|
*/
|
|
public void showHomepageWithSuggestion(boolean showSuggestion) {
|
|
if (mHomepageView == null) {
|
|
return;
|
|
}
|
|
Log.i(TAG, "showHomepageWithSuggestion: " + showSuggestion);
|
|
mSuggestionView.setVisibility(showSuggestion ? View.VISIBLE : View.GONE);
|
|
mHomepageView.setVisibility(View.VISIBLE);
|
|
mHomepageView = null;
|
|
mLoadedListeners.forEach(listener -> listener.onHomepageLoaded());
|
|
mLoadedListeners.clear();
|
|
}
|
|
|
|
@Override
|
|
public CategoryMixin getCategoryMixin() {
|
|
return mCategoryMixin;
|
|
}
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
setHomeActivity();
|
|
setContentView(R.layout.settings_homepage_container);
|
|
|
|
final View appBar = findViewById(R.id.app_bar_container);
|
|
appBar.setMinimumHeight(getSearchBoxHeight());
|
|
initHomepageContainer();
|
|
mLoadedListeners = new ArraySet<>();
|
|
|
|
final Toolbar toolbar = findViewById(R.id.search_action_bar);
|
|
FeatureFactory.getFactory(this).getSearchFeatureProvider()
|
|
.initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE);
|
|
|
|
getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
|
|
mCategoryMixin = new CategoryMixin(this);
|
|
getLifecycle().addObserver(mCategoryMixin);
|
|
|
|
if (!getSystemService(ActivityManager.class).isLowRamDevice()) {
|
|
// Only allow features on high ram devices.
|
|
final ImageView avatarView = findViewById(R.id.account_avatar);
|
|
if (AvatarViewMixin.isAvatarSupported(this)) {
|
|
avatarView.setVisibility(View.VISIBLE);
|
|
getLifecycle().addObserver(new AvatarViewMixin(this, avatarView));
|
|
}
|
|
|
|
showSuggestionFragment();
|
|
|
|
if (FeatureFlagUtils.isEnabled(this, FeatureFlags.CONTEXTUAL_HOME)) {
|
|
showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content);
|
|
}
|
|
}
|
|
final Fragment fragment = new TopLevelSettings();
|
|
fragment.getArguments().putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY,
|
|
getHighlightMenuKey());
|
|
showFragment(fragment, R.id.main_content);
|
|
|
|
((FrameLayout) findViewById(R.id.main_content))
|
|
.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
|
|
|
|
// Launch the intent from deep link for large screen devices.
|
|
launchDeepLinkIntentToRight();
|
|
}
|
|
|
|
@Override
|
|
protected void onNewIntent(Intent intent) {
|
|
super.onNewIntent(intent);
|
|
|
|
// When it's large screen 2-pane and Settings app is in the background, receiving an Intent
|
|
// will not recreate this activity. Update the intent for this case.
|
|
setIntent(intent);
|
|
reloadHighlightMenuKey();
|
|
if (isFinishing()) {
|
|
return;
|
|
}
|
|
// Launch the intent from deep link for large screen devices.
|
|
launchDeepLinkIntentToRight();
|
|
}
|
|
|
|
protected void setHomeActivity() {
|
|
((SettingsApplication) getApplication()).setHomeActivity(this);
|
|
}
|
|
|
|
private void showSuggestionFragment() {
|
|
final Class<? extends Fragment> fragment = FeatureFactory.getFactory(this)
|
|
.getSuggestionFeatureProvider(this).getContextualSuggestionFragment();
|
|
if (fragment == null) {
|
|
return;
|
|
}
|
|
|
|
mSuggestionView = findViewById(R.id.suggestion_content);
|
|
mHomepageView = findViewById(R.id.settings_homepage_container);
|
|
// Hide the homepage for preparing the suggestion.
|
|
mHomepageView.setVisibility(View.GONE);
|
|
// Schedule a timer to show the homepage and hide the suggestion on timeout.
|
|
mHomepageView.postDelayed(() -> showHomepageWithSuggestion(false),
|
|
HOMEPAGE_LOADING_TIMEOUT_MS);
|
|
try {
|
|
showFragment(fragment.getConstructor().newInstance(), R.id.suggestion_content);
|
|
} catch (Exception e) {
|
|
Log.w(TAG, "Cannot show fragment", e);
|
|
}
|
|
}
|
|
|
|
private void showFragment(Fragment fragment, int id) {
|
|
final FragmentManager fragmentManager = getSupportFragmentManager();
|
|
final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
|
final Fragment showFragment = fragmentManager.findFragmentById(id);
|
|
|
|
if (showFragment == null) {
|
|
fragmentTransaction.add(id, fragment);
|
|
} else {
|
|
fragmentTransaction.show(showFragment);
|
|
}
|
|
fragmentTransaction.commit();
|
|
}
|
|
|
|
private void launchDeepLinkIntentToRight() {
|
|
if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) {
|
|
return;
|
|
}
|
|
|
|
final Intent intent = getIntent();
|
|
if (intent == null || !TextUtils.equals(intent.getAction(),
|
|
ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY)) {
|
|
return;
|
|
}
|
|
|
|
final String intentUriString = intent.getStringExtra(
|
|
EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI);
|
|
if (TextUtils.isEmpty(intentUriString)) {
|
|
Log.e(TAG, "No EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI to deep link");
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
final Intent targetIntent;
|
|
try {
|
|
targetIntent = Intent.parseUri(intentUriString, Intent.URI_INTENT_SCHEME);
|
|
} catch (URISyntaxException e) {
|
|
Log.e(TAG, "Failed to parse deep link intent: " + e);
|
|
finish();
|
|
return;
|
|
}
|
|
|
|
final ComponentName targetComponentName = targetIntent.resolveActivity(getPackageManager());
|
|
if (targetComponentName == null) {
|
|
Log.e(TAG, "No valid target for the deep link intent: " + targetIntent);
|
|
finish();
|
|
return;
|
|
}
|
|
targetIntent.setComponent(targetComponentName);
|
|
|
|
// To prevent launchDeepLinkIntentToRight again for configuration change.
|
|
intent.setAction(null);
|
|
|
|
targetIntent.setFlags(targetIntent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
targetIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
|
|
|
// Sender of intent may want to send intent extra data to the destination of targetIntent.
|
|
targetIntent.replaceExtras(intent);
|
|
|
|
targetIntent.putExtra(EXTRA_IS_FROM_SETTINGS_HOMEPAGE, true);
|
|
targetIntent.putExtra(SettingsActivity.EXTRA_IS_FROM_SLICE, false);
|
|
|
|
targetIntent.setData(intent.getParcelableExtra(
|
|
SettingsHomepageActivity.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA));
|
|
|
|
// Set 2-pane pair rule for the deep link page.
|
|
ActivityEmbeddingRulesController.registerTwoPanePairRule(this,
|
|
getDeepLinkComponent(),
|
|
targetComponentName,
|
|
targetIntent.getAction(),
|
|
true /* finishPrimaryWithSecondary */,
|
|
true /* finishSecondaryWithPrimary */,
|
|
true /* clearTop*/);
|
|
ActivityEmbeddingRulesController.registerTwoPanePairRule(this,
|
|
new ComponentName(Settings.class.getPackageName(), Settings.class.getName()),
|
|
targetComponentName,
|
|
targetIntent.getAction(),
|
|
true /* finishPrimaryWithSecondary */,
|
|
true /* finishSecondaryWithPrimary */,
|
|
true /* clearTop*/);
|
|
startActivity(targetIntent);
|
|
}
|
|
|
|
protected ComponentName getDeepLinkComponent() {
|
|
return new ComponentName(Utils.SETTINGS_PACKAGE_NAME, ALIAS_DEEP_LINK);
|
|
}
|
|
|
|
private String getHighlightMenuKey() {
|
|
final Intent intent = getIntent();
|
|
if (intent != null && TextUtils.equals(intent.getAction(),
|
|
ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY)) {
|
|
final String menuKey = intent.getStringExtra(
|
|
EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY);
|
|
if (!TextUtils.isEmpty(menuKey)) {
|
|
return menuKey;
|
|
}
|
|
}
|
|
return getString(DEFAULT_HIGHLIGHT_MENU_KEY);
|
|
}
|
|
|
|
private void reloadHighlightMenuKey() {
|
|
final TopLevelSettings fragment =
|
|
(TopLevelSettings) getSupportFragmentManager().findFragmentById(R.id.main_content);
|
|
if (fragment != null) {
|
|
fragment.getArguments().putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY,
|
|
getHighlightMenuKey());
|
|
fragment.reloadHighlightMenuKey();
|
|
}
|
|
}
|
|
|
|
private void initHomepageContainer() {
|
|
final View view = findViewById(R.id.homepage_container);
|
|
// Prevent inner RecyclerView gets focus and invokes scrolling.
|
|
view.setFocusableInTouchMode(true);
|
|
view.requestFocus();
|
|
}
|
|
|
|
private int getSearchBoxHeight() {
|
|
final int searchBarHeight = getResources().getDimensionPixelSize(R.dimen.search_bar_height);
|
|
final int searchBarMargin = getResources().getDimensionPixelSize(R.dimen.search_bar_margin);
|
|
return searchBarHeight + searchBarMargin * 2;
|
|
}
|
|
}
|