| /* |
| * Copyright (C) 2020 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.systemui.qs.customize; |
| |
| import static com.android.systemui.qs.customize.QSCustomizer.EXTRA_QS_CUSTOMIZING; |
| import static com.android.systemui.qs.customize.QSCustomizer.MENU_RESET; |
| |
| import android.content.res.Configuration; |
| import android.graphics.Rect; |
| import android.os.Bundle; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.widget.TextView; |
| import android.widget.Toolbar; |
| import android.widget.Toolbar.OnMenuItemClickListener; |
| |
| import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; |
| import androidx.recyclerview.widget.GridLayoutManager; |
| import androidx.recyclerview.widget.RecyclerView; |
| |
| import com.android.internal.logging.UiEventLogger; |
| import com.android.systemui.R; |
| import com.android.systemui.keyguard.ScreenLifecycle; |
| import com.android.systemui.plugins.qs.QSTile; |
| import com.android.systemui.qs.QSEditEvent; |
| import com.android.systemui.qs.QSFragment; |
| import com.android.systemui.qs.QSTileHost; |
| import com.android.systemui.qs.dagger.QSScope; |
| import com.android.systemui.statusbar.phone.LightBarController; |
| import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; |
| import com.android.systemui.statusbar.policy.ConfigurationController; |
| import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; |
| import com.android.systemui.statusbar.policy.KeyguardStateController; |
| import com.android.systemui.util.ViewController; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import javax.inject.Inject; |
| |
| /** {@link ViewController} for {@link QSCustomizer}. */ |
| @QSScope |
| public class QSCustomizerController extends ViewController<QSCustomizer> { |
| private final TileQueryHelper mTileQueryHelper; |
| private final QSTileHost mQsTileHost; |
| private final TileAdapter mTileAdapter; |
| private final ScreenLifecycle mScreenLifecycle; |
| private final KeyguardStateController mKeyguardStateController; |
| private final LightBarController mLightBarController; |
| private final ConfigurationController mConfigurationController; |
| private final UiEventLogger mUiEventLogger; |
| private final Toolbar mToolbar; |
| |
| private final OnMenuItemClickListener mOnMenuItemClickListener = new OnMenuItemClickListener() { |
| @Override |
| public boolean onMenuItemClick(MenuItem item) { |
| if (item.getItemId() == MENU_RESET) { |
| mUiEventLogger.log(QSEditEvent.QS_EDIT_RESET); |
| reset(); |
| } |
| return false; |
| } |
| }; |
| |
| private final KeyguardStateController.Callback mKeyguardCallback = |
| new KeyguardStateController.Callback() { |
| @Override |
| public void onKeyguardShowingChanged() { |
| if (!mView.isAttachedToWindow()) return; |
| if (mKeyguardStateController.isShowing() && !mView.isOpening()) { |
| hide(); |
| } |
| } |
| }; |
| |
| private final ConfigurationListener mConfigurationListener = new ConfigurationListener() { |
| @Override |
| public void onConfigChanged(Configuration newConfig) { |
| mView.updateNavBackDrop(newConfig, mLightBarController); |
| mView.updateResources(); |
| if (mTileAdapter.updateNumColumns()) { |
| RecyclerView.LayoutManager lm = mView.getRecyclerView().getLayoutManager(); |
| if (lm instanceof GridLayoutManager) { |
| ((GridLayoutManager) lm).setSpanCount(mTileAdapter.getNumColumns()); |
| } |
| } |
| } |
| }; |
| |
| @Inject |
| protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper, |
| QSTileHost qsTileHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle, |
| KeyguardStateController keyguardStateController, LightBarController lightBarController, |
| ConfigurationController configurationController, UiEventLogger uiEventLogger) { |
| super(view); |
| mTileQueryHelper = tileQueryHelper; |
| mQsTileHost = qsTileHost; |
| mTileAdapter = tileAdapter; |
| mScreenLifecycle = screenLifecycle; |
| mKeyguardStateController = keyguardStateController; |
| mLightBarController = lightBarController; |
| mConfigurationController = configurationController; |
| mUiEventLogger = uiEventLogger; |
| |
| mToolbar = mView.findViewById(com.android.internal.R.id.action_bar); |
| } |
| |
| |
| @Override |
| protected void onViewAttached() { |
| mView.updateNavBackDrop(getResources().getConfiguration(), mLightBarController); |
| |
| mConfigurationController.addCallback(mConfigurationListener); |
| |
| mTileQueryHelper.setListener(mTileAdapter); |
| int halfMargin = |
| getResources().getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2; |
| mTileAdapter.changeHalfMargin(halfMargin); |
| |
| RecyclerView recyclerView = mView.getRecyclerView(); |
| recyclerView.setAdapter(mTileAdapter); |
| mTileAdapter.getItemTouchHelper().attachToRecyclerView(recyclerView); |
| GridLayoutManager layout = |
| new GridLayoutManager(getContext(), mTileAdapter.getNumColumns()) { |
| @Override |
| public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, |
| RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) { |
| // Do not read row and column every time it changes. |
| } |
| |
| public void calculateItemDecorationsForChild(View child, Rect outRect) { |
| // There's only a single item decoration that cares about the itemOffsets, so |
| // we just call it manually so they are never cached. This way, it's updated as the |
| // tiles are moved around. |
| // It only sets the left and right margin and only cares about tiles (not TextView). |
| if (!(child instanceof TextView)) { |
| outRect.setEmpty(); |
| mTileAdapter.getMarginItemDecoration().getItemOffsets(outRect, child, |
| recyclerView, new RecyclerView.State()); |
| ((LayoutParams) child.getLayoutParams()).leftMargin = outRect.left; |
| ((LayoutParams) child.getLayoutParams()).rightMargin = outRect.right; |
| } |
| } |
| }; |
| layout.setSpanSizeLookup(mTileAdapter.getSizeLookup()); |
| recyclerView.setLayoutManager(layout); |
| recyclerView.addItemDecoration(mTileAdapter.getItemDecoration()); |
| recyclerView.addItemDecoration(mTileAdapter.getMarginItemDecoration()); |
| |
| mToolbar.setOnMenuItemClickListener(mOnMenuItemClickListener); |
| mToolbar.setNavigationOnClickListener(v -> hide()); |
| } |
| |
| @Override |
| protected void onViewDetached() { |
| mTileQueryHelper.setListener(null); |
| mToolbar.setOnMenuItemClickListener(null); |
| mConfigurationController.removeCallback(mConfigurationListener); |
| } |
| |
| |
| private void reset() { |
| mTileAdapter.resetTileSpecs(QSTileHost.getDefaultSpecs(getContext())); |
| } |
| |
| public boolean isCustomizing() { |
| return mView.isCustomizing(); |
| } |
| |
| /** */ |
| public void show(int x, int y, boolean immediate) { |
| if (!mView.isShown()) { |
| setTileSpecs(); |
| if (immediate) { |
| mView.showImmediately(); |
| } else { |
| mView.show(x, y, mTileAdapter); |
| mUiEventLogger.log(QSEditEvent.QS_EDIT_OPEN); |
| } |
| mTileQueryHelper.queryTiles(mQsTileHost); |
| mKeyguardStateController.addCallback(mKeyguardCallback); |
| mView.updateNavColors(mLightBarController); |
| } |
| } |
| |
| /** */ |
| public void setQs(QSFragment qsFragment) { |
| mView.setQs(qsFragment); |
| } |
| |
| /** */ |
| public void restoreInstanceState(Bundle savedInstanceState) { |
| boolean customizing = savedInstanceState.getBoolean(EXTRA_QS_CUSTOMIZING); |
| if (customizing) { |
| mView.setVisibility(View.VISIBLE); |
| mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { |
| @Override |
| public void onLayoutChange(View v, int left, int top, int right, int bottom, |
| int oldLeft, |
| int oldTop, int oldRight, int oldBottom) { |
| mView.removeOnLayoutChangeListener(this); |
| show(0, 0, true); |
| } |
| }); |
| } |
| } |
| |
| /** */ |
| public void saveInstanceState(Bundle outState) { |
| if (mView.isShown()) { |
| mKeyguardStateController.removeCallback(mKeyguardCallback); |
| } |
| outState.putBoolean(EXTRA_QS_CUSTOMIZING, mView.isCustomizing()); |
| } |
| |
| /** */ |
| public void setEditLocation(int x, int y) { |
| mView.setEditLocation(x, y); |
| } |
| |
| /** */ |
| public void setContainer(NotificationsQuickSettingsContainer container) { |
| mView.setContainer(container); |
| } |
| |
| public boolean isShown() { |
| return mView.isShown(); |
| } |
| |
| /** Hice the customizer. */ |
| public void hide() { |
| final boolean animate = mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF; |
| if (mView.isShown()) { |
| mUiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED); |
| mToolbar.dismissPopupMenus(); |
| mView.setCustomizing(false); |
| save(); |
| mView.hide(animate); |
| mView.updateNavColors(mLightBarController); |
| mKeyguardStateController.removeCallback(mKeyguardCallback); |
| } |
| } |
| |
| private void save() { |
| if (mTileQueryHelper.isFinished()) { |
| mTileAdapter.saveSpecs(mQsTileHost); |
| } |
| } |
| |
| private void setTileSpecs() { |
| List<String> specs = new ArrayList<>(); |
| for (QSTile tile : mQsTileHost.getTiles()) { |
| specs.add(tile.getTileSpec()); |
| } |
| mTileAdapter.setTileSpecs(specs); |
| } |
| } |