diff options
author | Roman Birg <roman@cyngn.com> | 2015-11-04 11:01:13 -0800 |
---|---|---|
committer | Adnan Begovic <adnan@cyngn.com> | 2015-11-23 13:22:37 -0800 |
commit | 8b59e888aa4236f29728785b1908c06989ec6f8a (patch) | |
tree | 0d37638ab669e10c6d96f644c537b59e89707555 | |
parent | 36a5c9978f8ea0f61353457074814eacc4a6057a (diff) | |
download | frameworks_base-8b59e888aa4236f29728785b1908c06989ec6f8a.zip frameworks_base-8b59e888aa4236f29728785b1908c06989ec6f8a.tar.gz frameworks_base-8b59e888aa4236f29728785b1908c06989ec6f8a.tar.bz2 |
WIP: draggable quick settings tiles
Change-Id: I78105655b6811f13ef3c3e80980285387f43e52c
Signed-off-by: Roman Birg <roman@cyngn.com>
19 files changed, 2413 insertions, 297 deletions
diff --git a/packages/SystemUI/res/drawable/ic_qs_edit_tiles.xml b/packages/SystemUI/res/drawable/ic_qs_edit_tiles.xml new file mode 100644 index 0000000..218228d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_edit_tiles.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (c) 2015 The CyanogenMod 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + + <path + android:fillColor="#FFFFFF" + android:pathData="M4,32h4v4H4V32z M4,20h4v-4C5.8,16,4,17.8,4,20z M8,44v-4H4 C4,42.2,5.8,44,8,44z +M4,28h4v-4H4V28z M12,44h4v-4h-4V44z +M44,8v20c0,2.2-1.8,4-4,4h-8v4h-4v-4h-8c-2.2,0-4-1.8-4-4v-8h-4v-4h4V8 +c0-2.2,1.8-4,4-4h20C42.2,4,44,5.8,44,8z M40,8H20v20h20V8z +M28,44c2.2,0,4-1.8,4-4h-4V44z M20,44h4v-4h-4V44z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_tile_background_drag.xml b/packages/SystemUI/res/drawable/qs_tile_background_drag.xml new file mode 100644 index 0000000..0a3ba2d --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_tile_background_drag.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2015, The CyanogenMod 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. +*/ +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid android:color="#FF37474f" /> +</shape> diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index 1873168..c3d3c3e 100644 --- a/packages/SystemUI/res/layout/qs_panel.xml +++ b/packages/SystemUI/res/layout/qs_panel.xml @@ -23,7 +23,7 @@ android:paddingBottom="8dp" android:elevation="2dp"> - <com.android.systemui.qs.QSPanel + <com.android.systemui.qs.QSDragPanel android:id="@+id/quick_settings_panel" android:background="#0000" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/qs_tile_top b/packages/SystemUI/res/layout/qs_tile_top new file mode 100644 index 0000000..6d455ab --- /dev/null +++ b/packages/SystemUI/res/layout/qs_tile_top @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 The CyanogenMod 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. +--> +<com.android.systemui.qs.QSPanelTopView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:systemui="http://schemas.android.com/apk/res-auto" + android:id="@+id/qs_panel_top" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingBottom="@dimen/qs_brightness_padding_top"> + + <!-- brightness --> + <include android:id="@+id/brightness_container" + layout="@layout/quick_settings_brightness_dialog"/> + + <!-- delete target --> + <LinearLayout + android:id="@+id/delete_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:orientation="horizontal"> + + <ImageView + android:id="@+id/delete_target" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:src="@drawable/ic_delete" + /> + </LinearLayout> + + <!-- edit instructions & add target --> + <LinearLayout + android:id="@+id/edit_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:orientation="horizontal"> + + <TextView + android:layout_width="0dp" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:layout_weight="1" + android:textColor="@android:color/white" + android:text="@string/qs_tile_edit_header_instruction" + android:contentDescription="@null"/> + + <ImageView + android:id="@+id/add_target" + android:layout_width="20dp" + android:layout_height="match_parent" + android:gravity="center_vertical" + android:src="@drawable/ic_add_circle_qs" + /> + </LinearLayout> + + <TextView + android:id="@+id/qs_toast" + android:layout_height="wrap_content" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:gravity="center_vertical" + android:layout_width="match_parent" + android:textColor="@color/quick_settings_toast_color" + android:visibility="invisible"/> + + +</com.android.systemui.qs.QSPanelTopView> + diff --git a/packages/SystemUI/res/values/cm_colors.xml b/packages/SystemUI/res/values/cm_colors.xml index 74410bf..bc39082 100644 --- a/packages/SystemUI/res/values/cm_colors.xml +++ b/packages/SystemUI/res/values/cm_colors.xml @@ -62,4 +62,7 @@ <!-- Expanded Status Bar Battery Level Text Color --> <color name="status_bar_battery_level_text_color">#ffffff</color> + <!-- QS Toast Text color --> + <color name="quick_settings_toast_color">#ffbe1b</color> + </resources> diff --git a/packages/SystemUI/res/values/cm_strings.xml b/packages/SystemUI/res/values/cm_strings.xml index b90fc0c..b2f3def 100644 --- a/packages/SystemUI/res/values/cm_strings.xml +++ b/packages/SystemUI/res/values/cm_strings.xml @@ -73,4 +73,8 @@ <string name="led_notification_title">Light settings</string> <string name="led_notification_text">LED light enabled by settings</string> + <string name="qs_tile_edit_header_instruction">Press and hold a tile to edit</string> + <string name="quick_settings_edit_label">Edit Tiles</string> + <string name="quick_settings_cannot_delete_edit_tile">Cannot delete the Edit tile</string> + <string name="qs_tiles_reset_confirmation">Reset quick settings tiles to default configuration?</string> </resources> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index fcb5d54..5bac1d5 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -119,7 +119,7 @@ <!-- The default tiles to display in QuickSettings --> <string name="quick_settings_tiles_default" translatable="false"> - wifi,bt,inversion,dnd,cell,airplane,rotation,flashlight,location,cast,hotspot + wifi,bt,cell,airplane,rotation,flashlight,location,edit,cast,hotspot,inversion,dnd </string> <!-- The tiles to display in QuickSettings --> diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java new file mode 100644 index 0000000..07e18e8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java @@ -0,0 +1,1467 @@ +/* + * Copyright (C) 2015 The CyanogenMod 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; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Canvas; +import android.graphics.PointF; +import android.graphics.drawable.Drawable; +import android.provider.Settings; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.util.Log; +import android.view.DragEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.R; +import com.android.systemui.qs.tiles.EditTile; +import com.android.systemui.qs.tiles.IntentTile; +import com.android.systemui.settings.BrightnessController; +import com.android.systemui.settings.ToggleSlider; +import com.android.systemui.statusbar.phone.QSTileHost; +import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.tuner.QsTuner; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public class QSDragPanel extends QSPanel implements View.OnDragListener, View.OnLongClickListener { + + private static final String TAG = "QSDragPanel"; + + public static final boolean DEBUG_DRAG = true; + + public static final String ACTION_EDIT_TILES = "com.android.systemui.ACTION_EDIT_TILES"; + public static final String EXTRA_EDIT = "edit"; + public static final String EXTRA_RESET = "reset"; + + protected final ArrayList<QSPage> mPages = new ArrayList<>(); + + protected QSViewPager mViewPager; + protected PagerAdapter mPagerAdapter; + QSPanelTopView mQsPanelTop; + + private DragTileRecord mDraggingRecord; + private boolean mEditing; + private boolean mDragging; + private float mLastTouchLocationX, mLastTouchLocationY; + private int mLocationHits; + private int mLastLeftShift = -1; + private int mLastRightShift = -1; + private boolean mRestored; + private boolean mRestoring; + // whether the current view we are dragging in has shifted tiles + private boolean mMovedByLocation = false; + + List<TileRecord> mCurrentlyAnimating + = Collections.synchronizedList(new ArrayList<TileRecord>()); + private Collection<QSTile<?>> mTempTiles = null; + + private BroadcastReceiver mEditReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.hasExtra(EXTRA_RESET) && intent.getBooleanExtra(EXTRA_RESET, false)) { + setEditing(false); + setTiles(mHost.getTiles()); + } + } + }; + + public QSDragPanel(Context context) { + this(context, null); + } + + public QSDragPanel(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void setupViews() { + mDetail = LayoutInflater.from(mContext).inflate(R.layout.qs_detail, this, false); + mDetailContent = (ViewGroup) mDetail.findViewById(android.R.id.content); + mDetailSettingsButton = (TextView) mDetail.findViewById(android.R.id.button2); + mDetailDoneButton = (TextView) mDetail.findViewById(android.R.id.button1); + updateDetailText(); + mDetail.setVisibility(GONE); + mDetail.setClickable(true); + + LayoutInflater.from(mContext).inflate(R.layout.qs_tile_top, this, true); + mQsPanelTop = (QSPanelTopView) findViewById(R.id.qs_panel_top); + mBrightnessView = mQsPanelTop.getBrightnessView(); + mFooter = new QSFooter(this, mContext); + + // add target click listener + mQsPanelTop.findViewById(R.id.add_target).setOnClickListener( + new OnClickListener() { + @Override + public void onClick(View v) { + showAddDialog(); + } + }); + mViewPager = new QSViewPager(getContext()); + + addView(mDetail); + addView(mViewPager); + addView(mFooter.getView()); + + mClipper = new QSDetailClipper(mDetail); + + mBrightnessController = new BrightnessController(getContext(), + (ImageView) mQsPanelTop.getBrightnessView().findViewById(R.id.brightness_icon), + (ToggleSlider) mQsPanelTop.getBrightnessView().findViewById(R.id.brightness_slider)); + + mDetailDoneButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + announceForAccessibility( + mContext.getString(R.string.accessibility_desc_quick_settings)); + closeDetail(); + } + }); + + mPagerAdapter = new PagerAdapter() { + @Override + public Object instantiateItem(ViewGroup container, int position) { + if (DEBUG_DRAG) { + Log.d(TAG, "instantiateItem() called with " + + "container = [" + container + "], position = [" + position + "]"); + } + QSPage page = new QSPage(container.getContext(), QSDragPanel.this, position); + LayoutParams params = + new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.FILL_PARENT); + container.addView(page, params); + mPages.add(page); + return page; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + if (DEBUG_DRAG) { + Log.d(TAG, "destroyItem() called with " + "container = [" + + container + "], position = [" + position + "], object = [" + + object + "]"); + } + if (object instanceof View) { + mPages.remove(object); + container.removeView((View) object); + } + } + + @Override + public int getCount() { + return Math.max(getCurrentMaxPageCount(), 1); + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == object; + } + }; + mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, + int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + if (mDragging && position != mDraggingRecord.page + && !mViewPager.isFakeDragging() && !mRestoring) { + Log.w(TAG, "moving drag record to page: " + position); + + // remove it from the previous page and add it here + final QSPage sourceP = getPage(mDraggingRecord.page); + final QSPage targetP = getPage(position); + + sourceP.removeView(mDraggingRecord.tileView); + mDraggingRecord.page = position; + targetP.addView(mDraggingRecord.tileView); + + // set coords off screen until we're ready to place it + mDraggingRecord.tileView.setX(-mDraggingRecord.tileView.getMeasuredWidth()); + mDraggingRecord.tileView.setY(-mDraggingRecord.tileView.getMeasuredHeight()); + } + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + mViewPager.setAdapter(mPagerAdapter); + mViewPager.setCurrentItem(0); + + setClipChildren(false); + updateResources(); + + mViewPager.setOnDragListener(this); + mQsPanelTop.getBrightnessView().setOnDragListener(this); + mQsPanelTop.getDropTarget().setOnDragListener(this); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mContext.registerReceiver(mEditReceiver, new IntentFilter(ACTION_EDIT_TILES)); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mContext.unregisterReceiver(mEditReceiver); + } + + protected void drawTile(TileRecord r, QSTile.State state) { + final int visibility = state.visible || mEditing ? VISIBLE : GONE; + setTileVisibility(r.tileView, visibility); + r.tileView.onStateChanged(state); + } + + public void setEditing(boolean editing) { + if (mEditing == editing) return; + mEditing = editing; + + if (!editing) { + // persist the new config. + List<String> newTiles = new ArrayList<>(); + for (TileRecord record : mRecords) { + newTiles.add(mHost.getSpec(record.tile)); + } + mHost.setTiles(newTiles); + + refreshAllTiles(); + } + + // clear the record state + for (TileRecord record : mRecords) { + setupRecord(record); + drawTile(record, record.tile.getState()); + } + mQsPanelTop.setEditing(editing); + ensurePagerState(); + requestLayout(); + } + + protected void onStartDrag() { + mQsPanelTop.onStartDrag(); + } + + protected void onStopDrag() { + //mDraggingRecord.tileView.setVisibility(View.VISIBLE); + mDraggingRecord.tileView.setAlpha(1f); + + mDraggingRecord = null; + mDragging = false; + mRestored = false; + + mLastLeftShift = -1; + mLastRightShift = -1; + + mQsPanelTop.onStopDrag(); + + requestLayout(); + } + + protected View getDropTarget() { + return mQsPanelTop.getDropTarget(); + } + + public View getBrightnessView() { + return mQsPanelTop.getBrightnessView(); + } + + public boolean isEditing() { + return mEditing; + } + + protected int getPagesForCount(int size) { + return (int) Math.ceil(size / (double) getTilesPerPage()); + } + + protected int getCurrentMaxPageCount() { + int initialSize = mRecords.size(); + if (mTempTiles != null) { + return getPagesForCount(initialSize + mTempTiles.size()); + } + return getPagesForCount(initialSize); + } + + /** + * @return returns the number of pages that has at least 1 visible tile + */ + protected int getVisibleTilePageCount() { + // if all tiles are invisible on the page, do not count it + int pages = 0; + + int lastPage = -1; + boolean allTilesInvisible = true; + + for (TileRecord record : mRecords) { + DragTileRecord dr = (DragTileRecord) record; + if (dr.destinationPage != lastPage) { + if (!allTilesInvisible) { + pages++; + } + lastPage = dr.destinationPage; + allTilesInvisible = true; + } + if (allTilesInvisible && dr.tile.getState().visible) { + allTilesInvisible = false; + } + } + // last tile may have set this + if (!allTilesInvisible) { + pages++; + } + return pages; + } + + public void setTiles(Collection<QSTile<?>> tiles) { + if (DEBUG_DRAG) { + Log.i(TAG, "setTiles() called with " + "tiles = [" + + tiles + "], mTempTiles: " + mTempTiles); + if (mTempTiles != null) { + Log.e(TAG, "temp tiles being overridden... : " + + Arrays.toString(mTempTiles.toArray())); + } + } + for (Record record : mRecords) { + if (record instanceof DragTileRecord) { + DragTileRecord dr = (DragTileRecord) record; + mPages.get(dr.page).removeView(dr.tileView); + } + } + mRecords.clear(); + if (isLaidOut()) { + for (QSTile<?> tile : tiles) { + addTile(tile); + } + if (isShowingDetail()) { + mDetail.bringToFront(); + } + } else if (!isLaidOut()) { + if (DEBUG_DRAG) { + Log.w(TAG, "setting temporary tiles to layout"); + } + mTempTiles = Collections.synchronizedCollection(new ArrayList<QSTile<?>>(tiles)); + } + mPagerAdapter.notifyDataSetChanged(); + requestLayout(); + ensurePagerState(); + } + + protected void addTile(final QSTile<?> tile) { + if (DEBUG_DRAG) { + Log.d(TAG, "+++ addTile() called with " + "tile = [" + tile + "]"); + } + final DragTileRecord r = new DragTileRecord(); + + mRecords.add(r); + mPagerAdapter.notifyDataSetChanged(); + + int potentialPageIdx = getPagesForCount(mRecords.size()) - 1; + + r.tile = tile; + r.page = potentialPageIdx; + r.destinationPage = r.page; + r.tileView = tile.createTileView(mContext); + r.tileView.setVisibility(View.GONE); + final QSTile.Callback callback = new QSTile.Callback() { + @Override + public void onStateChanged(QSTile.State state) { + if (!r.openingDetail) { + drawTile(r, state); + } + } + + @Override + public void onShowDetail(boolean show) { + showDetail(show, r); + } + + @Override + public void onToggleStateChanged(boolean state) { + if (mDetailRecord == r) { + fireToggleStateChanged(state); + } + } + + @Override + public void onScanStateChanged(boolean state) { + r.scanState = state; + if (mDetailRecord == r) { + fireScanStateChanged(r.scanState); + } + } + + @Override + public void onAnnouncementRequested(CharSequence announcement) { + announceForAccessibility(announcement); + } + }; + r.tile.setCallback(callback); + final OnClickListener click = new OnClickListener() { + @Override + public void onClick(View v) { + if (!mEditing || r.tile instanceof EditTile) { + r.tile.click(); + } + } + }; + final OnClickListener clickSecondary = new OnClickListener() { + @Override + public void onClick(View v) { + if (!mEditing) { + r.tile.secondaryClick(); + } + } + }; + final OnLongClickListener longClick = new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (!mEditing) { + r.tile.longClick(); + } else { + QSDragPanel.this.onLongClick(r.tileView); + } + return true; + } + }; + r.tileView.init(click, clickSecondary, longClick); + r.tile.setListening(mListening); + r.tile.refreshState(); + if (mEditing) { + // force it to be visible, we'll refresh its state once editing is done + r.tile.getState().visible = true; + } + callback.onStateChanged(r.tile.getState()); + + mPages.get(r.page).addView(r.tileView); + + ensurePagerState(); + + if (DEBUG_DRAG) { + Log.d(TAG, "--- addTile() called with " + "tile = [" + tile + "]"); + } + } + + public void ensurePagerState() { + final boolean pagingEnabled = getVisibleTilePageCount() > 1 || mDragging; + mViewPager.setPagingEnabled(pagingEnabled); + } + + public int getTilesPerPage() { + return 8; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (DEBUG_DRAG) Log.d(TAG, "onMeasure()"); + + final int width = MeasureSpec.getSize(widthMeasureSpec); + + mQsPanelTop.measure(exactly(width), MeasureSpec.UNSPECIFIED); + + final int brightnessHeight = mQsPanelTop.getMeasuredHeight(); + + mFooter.getView().measure(exactly(width), MeasureSpec.UNSPECIFIED); + + mViewPager.measure(exactly(width), MeasureSpec.UNSPECIFIED); + + int h = brightnessHeight + mViewPager.getMeasuredHeight(); + if (mFooter.hasFooter()) { + h += mFooter.getView().getMeasuredHeight(); + } + mDetail.measure(exactly(width), MeasureSpec.UNSPECIFIED); + if (mDetail.getMeasuredHeight() < h) { + mDetail.measure(exactly(width), exactly(h)); + } + mGridHeight = h; + setMeasuredDimension(width, Math.max(h, mDetail.getMeasuredHeight())); + + for (TileRecord record : mRecords) { + setupRecord(record); + } + } + + private void setupRecord(TileRecord record) { + record.tileView.setEditing(mEditing); + record.tileView.setOnDragListener(mEditing ? this : null); + } + + public static int exactly(int size) { + return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + if (DEBUG_DRAG) Log.d(TAG, "onLayout()"); + final int w = getWidth(); + + mQsPanelTop.layout(0, t, mQsPanelTop.getMeasuredWidth(), + t + mQsPanelTop.getMeasuredHeight()); + + final int dh = Math.max(mDetail.getMeasuredHeight(), mViewPager.getMeasuredHeight()); + mViewPager.layout(0, t + mQsPanelTop.getMeasuredHeight(), w, t + dh); + mDetail.layout(0, t, mDetail.getMeasuredWidth(), t + dh); + if (mFooter.hasFooter()) { + View footer = mFooter.getView(); + footer.layout(0, getMeasuredHeight() - footer.getMeasuredHeight(), + footer.getMeasuredWidth(), getMeasuredHeight()); + } + + if (mTempTiles != null) { + final Iterator<QSTile<?>> iterator = mTempTiles.iterator(); + while (iterator.hasNext()) { + addTile(iterator.next()); + iterator.remove(); + } + mTempTiles = null; + mPagerAdapter.notifyDataSetChanged(); + } + ensurePagerState(); + } + + protected int getRowTop(int row) { + if (row <= 0) return 0; + return mLargeCellHeight - mDualTileUnderlap + (row - 1) * mCellHeight; + } + + public int getColumnCount() { + return mColumns; + } + + public int getColumnCount(int page, int row) { + int cols = 0; + for (Record record : mRecords) { + if (record instanceof DragTileRecord) { + DragTileRecord dr = (DragTileRecord) record; + if (dr.tileView.getVisibility() == GONE) continue; + if (dr.destinationPage != page) continue; + if (dr.row == row) cols++; + } + } + + if (isEditing() && (isDragging() || mRestoring) && !isDragRecordAttached()) { + // if shifting tiles back, and one moved from previous page + + // if it's the very last row on the last page, we should add an extra column to account + // for where teh dragging record would go + DragTileRecord record = (DragTileRecord) mRecords.get(mRecords.size() - 1); + + if (record.destinationPage == page && record.row == row && cols < getColumnCount()) { + cols++; + if (DEBUG_DRAG) { + Log.w(TAG, "adding another col, cols: " + cols + ", last: " + record + + ", drag: " + mDraggingRecord + ", "); + } + } + } + return cols; + } + + public int getMaxRows() { + int max = 0; + for (TileRecord record : mRecords) { + if (record.row > max) { + max = record.row; + } + } + return max; + } + + public int getLeft(int page, int row, int col) { + final boolean firstRow = page == 0 && row == 0; + int cols = firstRow ? 2 : mColumns; + return getLeft(row, col, cols, firstRow); + } + + public int getLeft(int page, int row, int col, int cols) { + final boolean firstRow = page == 0 && row == 0; + return getLeft(row, col, cols, firstRow); + } + + public int getLeft(int row, int col, int cols, boolean firstRowLarge) { + final int cw = row == 0 && firstRowLarge ? mLargeCellWidth : mCellWidth; + final int extra = (getWidth() - cw * cols) / (cols + 1); + int left = col * cw + (col + 1) * extra; + return left; + } + + public QSPage getCurrentPage() { + return mPages.get(mViewPager.getCurrentItem()); + } + + public QSPage getPage(int pos) { + if (pos >= mPages.size()) { + return null; + } + return mPages.get(pos); + } + + private TileRecord getRecord(View v) { + for (TileRecord record : mRecords) { + if (record.tileView == v) { + return record; + } + } + return null; + } + + @Override + public boolean onDrag(View v, DragEvent event) { + final DragTileRecord targetTile = (DragTileRecord) getRecord(v); + boolean originatingTileEvent = mDraggingRecord != null && v == mDraggingRecord.tileView; + + final int dragRecordIndex = mRecords.indexOf(mDraggingRecord); + boolean dragRecordAttached = dragRecordIndex != -1; + switch (event.getAction()) { + case DragEvent.ACTION_DRAG_STARTED: + if (originatingTileEvent) { + if (DEBUG_DRAG) { + Log.v(TAG, "ACTION_DRAG_STARTED on target view."); + } + mRestored = false; + } + return true; + + case DragEvent.ACTION_DRAG_ENTERED: + if (DEBUG_DRAG) { + Log.v(TAG, "ACTION_DRAG_ENTERED on view with tile: " + targetTile); + } + mLocationHits = 0; + mMovedByLocation = false; + + if (!originatingTileEvent && v != getDropTarget() && targetTile != null) { + if (DEBUG_DRAG) { + Log.e(TAG, "entered tile " + targetTile); + } + if (mCurrentlyAnimating.isEmpty() + && !mViewPager.isFakeDragging() + && !dragRecordAttached) { + mMovedByLocation = true; + shiftTiles(targetTile, true); + } else { + if (DEBUG_DRAG) { + Log.w(TAG, "ignoring action enter for animating tiles and fake drags"); + } + } + } + + return true; + case DragEvent.ACTION_DRAG_ENDED: + if (DEBUG_DRAG) { + Log.v(TAG, "ACTION_DRAG_ENDED on view: " + v + "(tile: " + + targetTile + "), result: " + event.getResult()); + } + if (originatingTileEvent && !event.getResult()) { + // view pager probably ate the event + restoreDraggingTilePosition(v); + } + + return true; + + case DragEvent.ACTION_DROP: + if (DEBUG_DRAG) { + Log.v(TAG, "ACTION_DROP, event loc: " + event.getX() + ", " + event.getY() + + " + with tile: " + targetTile + " and view: " + v); + } + mLastTouchLocationX = event.getX(); + mLastTouchLocationY = event.getY(); + + if (v == getDropTarget()) { + if (DEBUG_DRAG) { + Log.d(TAG, "dropping on delete target!!"); + } + if (mDraggingRecord.tile instanceof EditTile) { + restoreDraggingTilePosition(v); + mQsPanelTop.toast(R.string.quick_settings_cannot_delete_edit_tile); + return true; + } else { + mRestored = true; + getPage(mDraggingRecord.page).removeView(mDraggingRecord.tileView); + + // what spec is this tile? + String spec = mHost.getSpec(mDraggingRecord.tile); + if (DEBUG_DRAG) { + Log.w(TAG, "removing tile: " + mDraggingRecord + " with spec: " + spec); + } + mHost.remove(spec); + onStopDrag(); + } + } else { + restoreDraggingTilePosition(v); + } + return true; + + case DragEvent.ACTION_DRAG_EXITED: + if (DEBUG_DRAG) { + if (targetTile != null) { + Log.v(TAG, "ACTION_DRAG_EXITED on view with tile: " + targetTile); + } else { + Log.v(TAG, "ACTION_DRAG_EXITED on view: " + v); + } + } + if (originatingTileEvent + && mCurrentlyAnimating.isEmpty() + && !mViewPager.isFakeDragging() + && dragRecordAttached + && mLastLeftShift == -1) { + + if (DEBUG_DRAG) { + Log.v(TAG, "target: " + targetTile + ", hit mLastRightShift: " + + mLastRightShift + ", mLastLeftShift: " + + mLastLeftShift + ", dragRecordIndex: " + + dragRecordIndex); + } + + // move tiles back + shiftTiles(mDraggingRecord, false); + return true; + } + // fall through so exit events can trigger a left shift + case DragEvent.ACTION_DRAG_LOCATION: + mLastTouchLocationX = event.getX(); + mLastTouchLocationY = event.getY(); + + // do nothing if we're animating tiles + if (mCurrentlyAnimating.isEmpty() && !mViewPager.isFakeDragging()) { + if (v == mViewPager) { + // do we need to change pages? + int x = (int) event.getX(); + int width = mViewPager.getWidth(); + int scrollPadding = (int) (width * QSViewPager.SCROLL_PERCENT); + if (x < scrollPadding) { + if (mViewPager.canScrollHorizontally(-1)) { + mViewPager.animatePagerTransition(false); + return true; + } + } else if (x > width - scrollPadding) { + if (mViewPager.canScrollHorizontally(1)) { + mViewPager.animatePagerTransition(true); + return true; + } + } + } + if (DEBUG_DRAG) { + Log.v(TAG, "location hit:// target: " + targetTile + + ", hit mLastRightShift: " + mLastRightShift + + ", mLastLeftShift: " + mLastLeftShift + + ", dragRecordIndex: " + dragRecordIndex + + ", originatingTileEvent: " + originatingTileEvent + + ", mLocationHits: " + mLocationHits + + ", mMovedByLocation: " + mMovedByLocation); + } + + if (v != getDropTarget() && targetTile != null && !dragRecordAttached) { + // dragging around on another tile + if (mLocationHits++ == 30) { + if (DEBUG_DRAG) { + Log.w(TAG, "shifting right due to location hits."); + } + // add dragging tile to current page + shiftTiles(targetTile, true); + mMovedByLocation = true; + } else { + mLocationHits++; + } + } else if (mLastRightShift != -1 // right has shifted recently + && mLastLeftShift == -1 // -1 means its attached + && dragRecordIndex == mLastRightShift + && !originatingTileEvent + && !mMovedByLocation /* helps avoid continuous shifting */) { + // check if the location is on another tile/view + // that is not the last drag index, shift back left to revert back and + // potentially get ready for shifting right + if (DEBUG_DRAG) { + Log.w(TAG, "conditions met to reverse!!!! shifting left. <<<<<<<"); + } + shiftTiles((DragTileRecord) mRecords.get(mLastRightShift), false); + mMovedByLocation = true; + } + } else { + if (DEBUG_DRAG) { + Log.i(TAG, "ignoring location event because things are animating, size: " + + mCurrentlyAnimating.size()); + } + } + return true; + default: + Log.w(TAG, "unhandled event"); + } + return false; + } + + private void restoreDraggingTilePosition(View v) { + if (mRestored) { + return; + } + mRestored = true; + mRestoring = true; + mCurrentlyAnimating.add(mDraggingRecord); + + if (DEBUG_DRAG) { + Log.i(TAG, "restoreDraggingTilePosition() called with " + + "v = [" + (v.getTag() != null ? v.getTag() : v) + "]"); + } + final boolean dragRecordDetached = mRecords.indexOf(mDraggingRecord) == -1; + + if (DEBUG_DRAG) { + Log.v(TAG, "mLastLeftShift: " + mLastLeftShift + + ", detached: " + dragRecordDetached + ", drag record: " + mDraggingRecord); + } + + final QSPage originalPage = getPage(mDraggingRecord.page); + originalPage.removeView(mDraggingRecord.tileView); + addTransientView(mDraggingRecord.tileView, 0); + mDraggingRecord.tileView.setTransitionVisibility(View.VISIBLE); + + mLastTouchLocationY += mViewPager.getTop(); // we are in the threshold of the viewpager meow + + // need to move center of the dragging view to the coords of the event. + final float touchEventBoxLeft = v.getX() + + (mLastTouchLocationX - (mDraggingRecord.tileView.getWidth() / 2)); + final float touchEventBoxTop = v.getY() + + (mLastTouchLocationY - (mDraggingRecord.tileView.getHeight() / 2)); + + mDraggingRecord.tileView.setX(touchEventBoxLeft); + mDraggingRecord.tileView.setY(touchEventBoxTop); + + if (dragRecordDetached) { + setToLastDestination(mDraggingRecord); + if (DEBUG_DRAG) { + Log.d(TAG, "setting drag record view to coords: x:" + touchEventBoxLeft + + ", y:" + touchEventBoxTop); + Log.d(TAG, "animating drag record to: " + mDraggingRecord + ", loc: " + + mDraggingRecord.destination); + } + } else { + mDraggingRecord.destination.x = getLeft(mDraggingRecord.destinationPage, + mDraggingRecord.row, mDraggingRecord.col, + getColumnCount(mDraggingRecord.destinationPage, mDraggingRecord.row)); + + mDraggingRecord.destination.y = getRowTop(mDraggingRecord.row); + } + + // setup x destination to animate to + float destinationX = mDraggingRecord.destination.x; + if (mDraggingRecord.destinationPage > mViewPager.getCurrentItem()) { + destinationX += getWidth(); + } else if (mDraggingRecord.destinationPage < mViewPager.getCurrentItem()) { + destinationX -= getWidth(); + } + + // setup y + float destinationY = mDraggingRecord.destination.y + mViewPager.getTop(); + + mDraggingRecord.tileView.animate() + .x(destinationX) + .y(destinationY) // part of the viewpager now + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mDraggingRecord.tileView.setAlpha(1); + mDraggingRecord.tileView.setBackground( + mDraggingRecord.tileView.newTileBackground()); + } + + @Override + public void onAnimationEnd(Animator animation) { + mViewPager.requestDisallowInterceptTouchEvent(false); + + removeTransientView(mDraggingRecord.tileView); + + final QSPage targetP = getPage(mDraggingRecord.destinationPage); + + if (dragRecordDetached) { + Log.i(TAG, "drag record was detached"); + + } else { + Log.i(TAG, "drag record was attached"); + } + mDraggingRecord.page = mDraggingRecord.destinationPage; + targetP.addView(mDraggingRecord.tileView); + + mDraggingRecord.tileView.setX(mDraggingRecord.destination.x); + // reset this to be in the coords of the page, not viewpager anymore + mDraggingRecord.tileView.setY(mDraggingRecord.destination.y); + + mCurrentlyAnimating.remove(mDraggingRecord); + + mRestoring = false; + + if (dragRecordDetached) { + mRecords.add(mDraggingRecord); + mPagerAdapter.notifyDataSetChanged(); + } + onStopDrag(); + } + }); + } + + private void setToNextDestination(DragTileRecord tile) { + if (DEBUG_DRAG) { + Log.i(TAG, "+++setToNextDestination() called with " + "tile = [" + tile + "], at: " + + tile.destination); + } + tile.col++; + int maxCols = getColumnCount(); + + if (tile.col >= maxCols) { + tile.col = 0; + tile.row++; + if (DEBUG_DRAG) { + Log.w(TAG, "reached max column count, shifting to next row: " + tile.row); + } + } + + int maxRows = getMaxRows(); + + if (tile.row >= maxRows) { + tile.destinationPage = tile.destinationPage + 1; + tile.row = 0; + tile.col = 0; + + if (DEBUG_DRAG) { + Log.w(TAG, "tile's destination page moved to: " + tile.destinationPage); + } + } + int columnCount = Math.max(1, getColumnCount(tile.destinationPage, tile.row)); + if (DEBUG_DRAG) { + Log.w(TAG, "columCount initially at: " + columnCount); + } + + if (!mRecords.contains(tile) && tile != mDraggingRecord) { + // increase column count for the destination location to account for this tile being added + columnCount++; + if (DEBUG_DRAG) { + Log.w(TAG, "column count adjusted to: " + columnCount); + } + } + boolean firstRowLarge = tile.row == 0 && tile.destinationPage == 0; + + tile.destination.x = getLeft(tile.row, tile.col, columnCount, firstRowLarge); + tile.destination.y = getRowTop(tile.row); + + if (DEBUG_DRAG) { + Log.i(TAG, "---setToNextDestination() called with " + "tile = [" + tile + "], now at: " + + tile.destination); + } + } + + private void setToLastDestination(DragTileRecord record) { + DragTileRecord last = (DragTileRecord) mRecords.get(mRecords.size() - 1); + Log.d(TAG, "setToLastDestination() called with record = [" + + record + "], and last record is: " + last); + if (record != last && record.destinationPage <= last.destinationPage) { + record.destinationPage = last.destinationPage; + record.row = last.row; + record.col = last.col; + record.destination.x = last.destination.x; + record.destination.y = last.destination.y; + setToNextDestination(record); + } + } + + @Override + public boolean onLongClick(View v) { + final DragTileRecord record = (DragTileRecord) getRecord(v); + if (record == null) { + // TODO couldn't find a matching tag? + Log.e(TAG, "got a null record on touh down."); + return false; + } + + mDraggingRecord = record; + + mDraggingRecord.tileView.setAlpha(0); + mDraggingRecord.tileView.setTileBackground(null); + mDraggingRecord.tileView.setDual(false); + TileShadow mTileShadow = new TileShadow(mDraggingRecord.tileView); + + v.startDrag(null, mTileShadow, null, 0); + + mViewPager.requestDisallowInterceptTouchEvent(true); + + onStartDrag(); + mDragging = true; + return true; + } + + private void shiftTiles(DragTileRecord startingTile, boolean forward) { + if (DEBUG_DRAG) { + Log.i(TAG, "shiftTiles() called with " + "startingTile = [" + startingTile + + "], forward = [" + forward + "]"); + } + + if (forward) { + // startingTile and all after will need to be shifted one to the right + // dragging tile needs room + + final int destP = startingTile.destinationPage; + final int rowF = startingTile.row; + final int colF = startingTile.col; + PointF loc = new PointF(startingTile.destination.x, startingTile.destination.y); + + // the index of the original position of the statingTile before it moved + int startingIndex = mRecords.indexOf(startingTile); + mLastRightShift = startingIndex; + mLastLeftShift = -1; + + shiftAllTilesRight(startingIndex); + mRecords.add(startingIndex, mDraggingRecord); + + mPagerAdapter.notifyDataSetChanged(); + + mDraggingRecord.col = colF; + mDraggingRecord.row = rowF; + mDraggingRecord.destination = loc; + mDraggingRecord.destinationPage = destP; + + mDraggingRecord.tileView.setX(mDraggingRecord.destination.x); + mDraggingRecord.tileView.setY(mDraggingRecord.destination.y); + + } else { + // it is also probably the dragging tile + final int startingIndex = mRecords.indexOf(startingTile); + mLastLeftShift = startingIndex; + mLastRightShift = -1; + + final int draggingIndex = mRecords.indexOf(mDraggingRecord); + + if (startingIndex != draggingIndex) { + if (DEBUG_DRAG) { + Log.e(TAG, "startinIndex: " + startingIndex + ", draggingIndex: " + + draggingIndex + ", and they differ!!!!"); + } + } + + // startingTile should be the "empty" tile that things should start shifting into + shiftAllTilesLeft(startingIndex); + + // remove the dragging record + if (mRecords.remove(mDraggingRecord)) { + mPagerAdapter.notifyDataSetChanged(); + if (DEBUG_DRAG) { + Log.v(TAG, "removed dragging record after moving tiles back"); + } + } + + // set coords off screen until we're ready to place it + mDraggingRecord.tileView.setX(-mDraggingRecord.tileView.getMeasuredWidth()); + mDraggingRecord.tileView.setY(-mDraggingRecord.tileView.getMeasuredHeight()); + } + + mViewPager.getAdapter().notifyDataSetChanged(); + } + + private void shiftAllTilesRight(int startingIndex) { + int desiredColumnCount = -1; + for (int j = startingIndex; j < mRecords.size() - 1; j++) { + final DragTileRecord ti = (DragTileRecord) mRecords.get(j); + final DragTileRecord tnext = (DragTileRecord) mRecords.get(j + 1); + + mCurrentlyAnimating.add(ti); + if (tnext.row != ti.row || desiredColumnCount == -1) { + desiredColumnCount = getColumnCount(tnext.destinationPage, tnext.row); + //Log.w(TAG, "updated desiredColumnCount: " + desiredColumnCount); + } + + if (DEBUG_DRAG) { + Log.v(TAG, "moving " + ti + " to page " + tnext.destinationPage + ", at coords: " + + tnext.row + ", col: " + tnext.col + ", dest: " + tnext.destination); + } + + ti.row = tnext.row; + ti.col = tnext.col; + ti.destination.x = getLeft(tnext.destinationPage, ti.row, ti.col, desiredColumnCount); +// ti.destination.x = getLeft(ti.row, ti.col, desiredColumnCount, +// tnext.destinationPage == 0 && ti.row == 0); + ti.destination.y = getRowTop(ti.row); + + if (ti.destinationPage != tnext.destinationPage) { + ti.destinationPage = tnext.destinationPage; + + final QSPage tilePageSource = getPage(ti.page); + final QSPage tilePageTarget = getPage(ti.destinationPage); + tilePageSource.removeView(ti.tileView); + + tilePageSource.addTransientView(ti.tileView, 0); + ti.tileView.animate() + .x(ti.destination.x + getWidth()) + .y(ti.destination.y) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + tilePageSource.removeTransientView(ti.tileView); + ti.page = tilePageTarget.getPageIndex(); + tilePageTarget.addView(ti.tileView); + ti.tileView.setX(ti.destination.x); + ti.tileView.setY(ti.destination.y); + + mCurrentlyAnimating.remove(ti); + requestLayout(); + } + }); + + } else { + ti.tileView.animate() + .x(ti.destination.x) + .y(ti.destination.y) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mCurrentlyAnimating.remove(ti); + } + }); + } + } + + // need to do last tile manually + final DragTileRecord last = (DragTileRecord) mRecords.get(mRecords.size() - 1); + mCurrentlyAnimating.add(last); + + if (DEBUG_DRAG) { + Log.i(TAG, "last tile shifting to the right: " + last); + } + setToNextDestination(last); + if (last.page != last.destinationPage) { + final QSPage tilePageSource = getPage(last.page); + final QSPage tilePageTarget = getPage(last.destinationPage); + tilePageSource.removeView(last.tileView); + tilePageSource.addTransientView(last.tileView, 0); + + last.tileView.animate() + .x(last.destination.x + getWidth()) + .y(last.destination.y) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + tilePageSource.removeTransientView(last.tileView); + last.page = tilePageTarget.getPageIndex(); + tilePageTarget.addView(last.tileView); + last.tileView.setX(last.destination.x); + last.tileView.setY(last.destination.y); + + if (DEBUG_DRAG) { + Log.i(TAG, "page shift finished: " + last); + } + + mCurrentlyAnimating.remove(last); + requestLayout(); + } + }); + } else { + last.tileView.animate() + .x(last.destination.x) + .y(last.destination.y) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (DEBUG_DRAG) { + Log.i(TAG, "shift finished: " + last); + } + + mCurrentlyAnimating.remove(last); + } + }); + } + } + + private void shiftAllTilesLeft(int startingIndex) { + DragTileRecord startingTile = (DragTileRecord) mRecords.get(startingIndex); + + final PointF lastLocation = new PointF(startingTile.destination.x, + startingTile.destination.y); + PointF reallyTempLoc = new PointF(); + int lastRow = startingTile.row, lastCol = startingTile.col, tempRow, + tempCol, lastPage = startingTile.destinationPage, tempPage; + + int desiredColCount = getColumnCount(startingTile.destinationPage, startingTile.row); + for (int j = startingIndex + 1; j < mRecords.size(); j++) { + final DragTileRecord ti = (DragTileRecord) mRecords.get(j); + + mCurrentlyAnimating.add(ti); + + if (DEBUG_DRAG) { + Log.v(TAG, "moving " + ti + " to " + lastPage + ", at coords: " + + lastRow + ", col: " + lastCol); + Log.i(TAG, "and will have desiredColCount: " + desiredColCount); + } + + final int columnCountF = desiredColCount; + + if (ti.row != lastRow) { + desiredColCount = getColumnCount(ti.destinationPage, ti.row); + Log.e(TAG, "updating desired colum count to: " + desiredColCount); + } + + // save current tile's loc + reallyTempLoc.x = ti.destination.x; + reallyTempLoc.y = ti.destination.y; + + tempRow = ti.row; + tempCol = ti.col; + tempPage = ti.destinationPage; + + ti.row = lastRow; + ti.col = lastCol; + + ti.destination.x = getLeft(lastRow, lastCol, columnCountF, + lastPage == 0 && lastRow == 0); + ti.destination.y = getRowTop(lastRow); + + final boolean dual = getPage(ti.destinationPage).dualRecord(ti); + + if (ti.destinationPage != lastPage) { + ti.destinationPage = lastPage; + + ti.tileView.setX(reallyTempLoc.x + getWidth()); + ti.tileView.setY(reallyTempLoc.y); + + final QSPage originalPage = getPage(ti.page); + final QSPage page = getPage(lastPage); + + originalPage.removeView(ti.tileView); + + ti.tileView.animate() + .x(ti.destination.x) + .y(ti.destination.y) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + page.addTransientView(ti.tileView, 0); + } + + @Override + public void onAnimationEnd(Animator animation) { + page.removeTransientView(ti.tileView); + ti.page = page.getPageIndex(); + page.addView(ti.tileView); + + mCurrentlyAnimating.remove(ti); + requestLayout(); + } + }); + } else { + ti.tileView.animate() + .x(ti.destination.x) + .y(ti.destination.y) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (ti.tileView.setDual(dual)) { + if (DEBUG_DRAG) { + Log.w(TAG, ti + " changed dual state to : " + + ti.tileView.isDual()); + } + ti.tileView.handleStateChanged(ti.tile.getState()); + ti.tileView.invalidate(); + } + + mCurrentlyAnimating.remove(ti); + requestLayout(); + } + }); + } + + // update previous location + lastLocation.x = reallyTempLoc.x; + lastLocation.y = reallyTempLoc.y; + + lastRow = tempRow; + lastCol = tempCol; + lastPage = tempPage; + } + } + + public int getDesiredColumnCount(int page, int row) { + if (page == 0 && row == 0) { + return 2; // TODO change if large tiles are disabled + } else { + return mColumns; + } + } + + @Override + public void setExpanded(boolean expanded) { + super.setExpanded(expanded); + if (!expanded) { + if (mEditing) { + setEditing(false); + } + } + } + + public boolean isAnimating(TileRecord t) { + return mCurrentlyAnimating.contains(t); + } + + // todo implement proper add tile ui + protected void showAddDialog() { + List<String> tiles = mHost.getTileSpecs(); + int numBroadcast = 0; + for (int i = 0; i < tiles.size(); i++) { + if (tiles.get(i).startsWith(IntentTile.PREFIX)) { + numBroadcast++; + } + } + String[] defaults = + getContext().getString(R.string.quick_settings_tiles_default).split(","); + final String[] available = new String[defaults.length + 1 + - (tiles.size() - numBroadcast)]; + final String[] availableTiles = new String[available.length]; + int index = 0; + for (int i = 0; i < defaults.length; i++) { + if (tiles.contains(defaults[i])) { + continue; + } + int resource = mHost.getLabelResource(defaults[i]); + if (resource != 0) { + availableTiles[index] = defaults[i]; + available[index++] = getContext().getString(resource); + } else { + availableTiles[index] = defaults[i]; + available[index++] = defaults[i]; + } + } + available[index++] = getContext().getString(R.string.broadcast_tile); + + final AlertDialog d = new AlertDialog.Builder(getContext(), R.style.Theme_SystemUI_Dialog) + .setTitle(R.string.add_tile) + .setItems(available, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + if (which < available.length - 1) { + add(availableTiles[which]); + } else { + showBroadcastTileDialog(); + } + } + }).create(); + SystemUIDialog.makeSystemUIDialog(d); + d.show(); + } + + public void showBroadcastTileDialog() { + final EditText editText = new EditText(getContext()); + final AlertDialog d = new AlertDialog.Builder(getContext()) + .setTitle(R.string.broadcast_tile) + .setView(editText) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + String action = editText.getText().toString(); + if (isValid(action)) { + add(IntentTile.PREFIX + action + ')'); + } + } + }).create(); + SystemUIDialog.makeSystemUIDialog(d); + d.show(); + } + + private boolean isValid(String action) { + for (int i = 0; i < action.length(); i++) { + char c = action.charAt(i); + if (!Character.isAlphabetic(c) && !Character.isDigit(c) && c != '.') { + return false; + } + } + return true; + } + + public void add(String tile) { + MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_ADD, tile); + List<String> tiles = new ArrayList<>(mHost.getTileSpecs()); + tiles.add(tile); + mHost.setTiles(tiles); + } + + public void reset() { + Settings.Secure.putStringForUser(getContext().getContentResolver(), + QSTileHost.TILES_SETTING, "default", ActivityManager.getCurrentUser()); + setEditing(false); + setTiles(mHost.getTiles()); + requestLayout(); + } + + public boolean isDragging() { + return mDragging; + } + + public boolean isDragRecordAttached() { + return mDragging && mDraggingRecord != null && mRecords.indexOf(mDraggingRecord) >= 0; + } + + public static final class DragTileRecord extends TileRecord { + public int page; + public int destinationPage; + public PointF destination = new PointF(); + + @Override + public String toString() { + String label = tile instanceof QsTuner.DraggableTile ? tile.toString() : + tile.getClass().getSimpleName(); + + String p = "at page: " + page; + if (destinationPage != page) { + p += "{-> " + destinationPage + "} "; + } + + return "[" + label + ", coords: (" + row + ", " + col + ") " + p + "]"; + } + } + + private static class TileShadow extends View.DragShadowBuilder { + + public TileShadow(View view) { + super(view); + Drawable shadow = view.getContext().getDrawable(R.drawable.qs_tile_background_drag); + view.setBackground(shadow); + } + + @Override + public void onDrawShadow(Canvas canvas) { + super.onDrawShadow(canvas); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPage.java b/packages/SystemUI/src/com/android/systemui/qs/QSPage.java new file mode 100644 index 0000000..f06e6f8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPage.java @@ -0,0 +1,143 @@ +package com.android.systemui.qs; + +import android.content.Context; +import android.content.res.Resources; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import com.android.systemui.R; + +public class QSPage extends ViewGroup { + + private static final String TAG = "QSPage"; + + static final float TILE_ASPECT = 1.2f; + + private int mCellWidth; + private int mCellHeight; + private int mLargeCellWidth; + private int mLargeCellHeight; + private int mGridHeight; + + private QSDragPanel mPanel; + + private int mPage; + + public QSPage(Context context, QSDragPanel panel, int page) { + super(context); + mPanel = panel; + mPage = page; + updateResources(); + setClipChildren(false); + setClipToPadding(false); + setClipToOutline(false); + } + + public int getPageIndex() { + return mPage; + } + + public void updateResources() { + final Resources res = mContext.getResources(); + final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns)); + mCellHeight = res.getDimensionPixelSize(R.dimen.qs_tile_height); + mCellWidth = (int)(mCellHeight * TILE_ASPECT); + mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height); + mLargeCellWidth = (int)(mLargeCellHeight * TILE_ASPECT); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + final int width = MeasureSpec.getSize(widthMeasureSpec); + if (mPanel.mCurrentlyAnimating.isEmpty() && !mPanel.isDragging()) { + int r = -1; + int c = -1; + int rows = 0; + for (QSPanel.TileRecord ts : mPanel.mRecords) { + QSDragPanel.DragTileRecord record = (QSDragPanel.DragTileRecord) ts; + if (record.page != mPage) continue; + if (record.tileView.getVisibility() == GONE) continue; + + boolean dual = dualRecord(record); + if (mPage == 0 && r == 0 && c == 1) { + r = 1; + c = 0; + } else if (r == -1 || c == (mPanel.getColumnCount() - 1)) { + r++; + c = 0; + } else { + c++; + } + record.row = r; + record.col = c; + rows = r + 1; + } + mGridHeight = mPanel.getRowTop(rows); + } + + View previousView = mPanel.getBrightnessView(); + for (QSPanel.TileRecord ts : mPanel.mRecords) { + QSDragPanel.DragTileRecord record = (QSDragPanel.DragTileRecord) ts; + if (record.page != mPage) continue; + if (record.page != record.destinationPage) continue; + + final boolean dual = dualRecord(record); + if (record.tileView.setDual(dual)) { + record.tileView.handleStateChanged(record.tile.getState()); + } + if (record.tileView.getVisibility() == GONE) continue; + final int cw = dual ? mLargeCellWidth : mCellWidth; + final int ch = dual ? mLargeCellHeight : mCellHeight; + record.tileView.measure(exactly(cw), exactly(ch)); + previousView = record.tileView.updateAccessibilityOrder(previousView); + } + setMeasuredDimension(width, mGridHeight); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + final int w = getWidth(); + boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; + for (QSPanel.TileRecord ts : mPanel.mRecords) { + QSDragPanel.DragTileRecord record = (QSDragPanel.DragTileRecord) ts; + if (record.page != mPage) continue; + if (record.page != record.destinationPage) continue; + if (record.tileView.getVisibility() == GONE) continue; + + final int cols = mPanel.getColumnCount(mPage, record.row); + + int left = mPanel.getLeft(record.row, record.col, cols, dualRecord(record)); + final int top = mPanel.getRowTop(record.row); + int right; + int tileWith = record.tileView.getMeasuredWidth(); + if (isRtl) { + right = w - left; + left = right - tileWith; + } else { + right = left + tileWith; + } + if (mPanel.isAnimating(record)) continue; + if (false) { + Log.v(TAG + "-" + mPage, "laying out " + record + ", top: " + top + ", left: " + left); + Log.d(TAG, record + " wiping translations: " + + record.tileView.getTranslationX() + + ", " + record.tileView.getTranslationY()); + } + record.tileView.setTranslationX(0); + record.tileView.setTranslationY(0); + + record.destination.x = record.tileView.getX(); + record.destination.y = record.tileView.getY(); + + record.tileView.layout(left, top, right, top + record.tileView.getMeasuredHeight()); + } + } + + public boolean dualRecord(QSPanel.TileRecord record) { + return record.row == 0 && mPage == 0; + } + + private static int exactly(int size) { + return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 3755a01..88bcbff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -53,35 +53,34 @@ import cyanogenmod.providers.CMSettings; public class QSPanel extends ViewGroup { private static final float TILE_ASPECT = 1.2f; - private final Context mContext; - protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>(); - private final View mDetail; - private final ViewGroup mDetailContent; - private final TextView mDetailSettingsButton; - private final TextView mDetailDoneButton; - protected final View mBrightnessView; - private final QSDetailClipper mClipper; + protected final ArrayList<TileRecord> mRecords = new ArrayList<>(); + protected View mDetail; + protected ViewGroup mDetailContent; + protected TextView mDetailSettingsButton; + protected TextView mDetailDoneButton; + protected View mBrightnessView; + protected QSDetailClipper mClipper; private final H mHandler = new H(); - private int mColumns; - private int mCellWidth; - private int mCellHeight; - private int mLargeCellWidth; - private int mLargeCellHeight; - private int mPanelPaddingBottom; - private int mDualTileUnderlap; - private int mBrightnessPaddingTop; - private int mGridHeight; + protected int mColumns; + protected int mCellWidth; + protected int mCellHeight; + protected int mLargeCellWidth; + protected int mLargeCellHeight; + protected int mPanelPaddingBottom; + protected int mDualTileUnderlap; + protected int mBrightnessPaddingTop; + protected int mGridHeight; private boolean mExpanded; - private boolean mListening; + protected boolean mListening; private boolean mClosingDetail; - private Record mDetailRecord; + protected Record mDetailRecord; private Callback mCallback; - private BrightnessController mBrightnessController; - private QSTileHost mHost; + protected BrightnessController mBrightnessController; + protected QSTileHost mHost; - private QSFooter mFooter; + protected QSFooter mFooter; private boolean mGridContentVisible = true; public QSPanel(Context context) { @@ -91,17 +90,20 @@ public class QSPanel extends ViewGroup { public QSPanel(Context context, AttributeSet attrs) { super(context, attrs); mContext = context; + setupViews(); + } - mDetail = LayoutInflater.from(context).inflate(R.layout.qs_detail, this, false); + protected void setupViews() { + mDetail = LayoutInflater.from(mContext).inflate(R.layout.qs_detail, this, false); mDetailContent = (ViewGroup) mDetail.findViewById(android.R.id.content); mDetailSettingsButton = (TextView) mDetail.findViewById(android.R.id.button2); mDetailDoneButton = (TextView) mDetail.findViewById(android.R.id.button1); updateDetailText(); mDetail.setVisibility(GONE); mDetail.setClickable(true); - mBrightnessView = LayoutInflater.from(context).inflate( + mBrightnessView = LayoutInflater.from(mContext).inflate( R.layout.quick_settings_brightness_dialog, this, false); - mFooter = new QSFooter(this, context); + mFooter = new QSFooter(this, mContext); addView(mDetail); addView(mBrightnessView); addView(mFooter.getView()); @@ -141,7 +143,7 @@ public class QSPanel extends ViewGroup { return brightnessSliderEnabled; } - private void updateDetailText() { + protected void updateDetailText() { mDetailDoneButton.setText(R.string.quick_settings_done); mDetailSettingsButton.setText(R.string.quick_settings_more_settings); } @@ -259,11 +261,11 @@ public class QSPanel extends ViewGroup { showDetail(show, r); } - private void showDetail(boolean show, Record r) { + protected void showDetail(boolean show, Record r) { mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget(); } - private void setTileVisibility(View v, int visibility) { + protected void setTileVisibility(View v, int visibility) { mHandler.obtainMessage(H.SET_TILE_VISIBILITY, visibility, 0, v).sendToTarget(); } @@ -288,7 +290,7 @@ public class QSPanel extends ViewGroup { } } - private void drawTile(TileRecord r, QSTile.State state) { + protected void drawTile(TileRecord r, QSTile.State state) { final int visibility = state.visible ? VISIBLE : GONE; setTileVisibility(r.tileView, visibility); r.tileView.onStateChanged(state); @@ -558,7 +560,7 @@ public class QSPanel extends ViewGroup { } } - private int getRowTop(int row) { + protected int getRowTop(int row) { if (row <= 0) return mBrightnessView.getMeasuredHeight() + mBrightnessPaddingTop; return mBrightnessView.getMeasuredHeight() + mBrightnessPaddingTop + mLargeCellHeight - mDualTileUnderlap + (row - 1) * mCellHeight; @@ -579,13 +581,13 @@ public class QSPanel extends ViewGroup { } } - private void fireToggleStateChanged(boolean state) { + protected void fireToggleStateChanged(boolean state) { if (mCallback != null) { mCallback.onToggleStateChanged(state); } } - private void fireScanStateChanged(boolean state) { + protected void fireScanStateChanged(boolean state) { if (mCallback != null) { mCallback.onScanStateChanged(state); } @@ -612,14 +614,14 @@ public class QSPanel extends ViewGroup { } } - private static class Record { + protected static class Record { View detailView; DetailAdapter detailAdapter; int x; int y; } - protected static final class TileRecord extends Record { + protected static class TileRecord extends Record { public QSTile<?> tile; public QSTileView tileView; public int row; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelTopView.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelTopView.java new file mode 100644 index 0000000..c724ace --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelTopView.java @@ -0,0 +1,228 @@ +package com.android.systemui.qs; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.annotation.Nullable; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.TextView; +import com.android.systemui.R; + +public class QSPanelTopView extends FrameLayout { + + public static final int TOAST_DURATION = 2000; + + protected View mEditTileInstructionView; + protected View mDropTarget; + protected View mBrightnessView; + protected TextView mToastView; + + private boolean mEditing; + private boolean mDisplayingInstructions; + private boolean mDisplayingTrash; + private boolean mDisplayingToast; + + public QSPanelTopView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public QSPanelTopView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public QSPanelTopView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + setClipToPadding(false); + } + + public View getDropTarget() { + return mDropTarget; + } + + public View getBrightnessView() { + return mBrightnessView; + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mDropTarget = findViewById(R.id.delete_container); + mEditTileInstructionView = findViewById(R.id.edit_container); + mBrightnessView = findViewById(R.id.brightness_container); + mToastView = (TextView) findViewById(R.id.qs_toast); + + mDropTarget.setVisibility(View.GONE); + mEditTileInstructionView.setVisibility(View.GONE); + } + + public void setEditing(boolean editing) { + mEditing = editing; + if (editing) { + mDisplayingInstructions = true; + mDisplayingTrash = false; + } else { + mDisplayingInstructions = false; + mDisplayingTrash = false; + } + animateToState(); + } + + public void onStopDrag() { + mDisplayingTrash = false; + animateToState(); + } + + public void onStartDrag() { + mDisplayingTrash = true; + animateToState(); + } + + public void toast(int textStrResId) { + mDisplayingToast = true; + mToastView.setText(textStrResId); + animateToState(); + } + + private void animateToState() { + showBrightnessSlider(!mEditing && !mDisplayingToast); + showInstructions(mEditing + && mDisplayingInstructions + && !mDisplayingTrash + && !mDisplayingToast); + showTrash(mEditing && mDisplayingTrash && !mDisplayingToast); + showToast(mDisplayingToast); + } + + private void showBrightnessSlider(boolean show) { + if (show) { + // slide brightness in + mBrightnessView + .animate() + .withLayer() + .y(getTop()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mBrightnessView.setVisibility(View.VISIBLE); + } + }); + } else { + // slide out brightness + mBrightnessView + .animate() + .withLayer() + .y(-getHeight()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mBrightnessView.setVisibility(View.INVISIBLE); + } + }); + } + } + + private void showInstructions(boolean show) { + if (show) { + // slide in instructions + mEditTileInstructionView.animate() + .withLayer() + .y(getTop()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mEditTileInstructionView.setVisibility(View.VISIBLE); + } + }); + } else { + // animate instructions out + mEditTileInstructionView.animate() + .withLayer() + .y(-getHeight()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mEditTileInstructionView.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + mEditTileInstructionView.setVisibility(View.GONE); + } + }); + } + } + + private void showTrash(boolean show) { + if (show) { + // animate drop target in + mDropTarget.animate() + .withLayer() + .y(getTop()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mDropTarget.setVisibility(View.VISIBLE); + } + }); + } else { + // drop target animates up + mDropTarget.animate() + .withLayer() + .y(-getHeight()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mDropTarget.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + mDropTarget.setVisibility(View.GONE); + } + }); + } + } + + private void showToast(boolean show) { + if (show) { + mToastView.animate() + .withLayer() + .y(getTop()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mToastView.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + mDisplayingToast = false; + mToastView.postDelayed(new Runnable() { + @Override + public void run() { + animateToState(); + } + }, TOAST_DURATION); + } + }); + } else { + mToastView.animate() + .withLayer() + .y(-getHeight()) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mToastView.setVisibility(View.VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + mToastView.setVisibility(View.GONE); + } + }); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index 3202250..934964a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -85,7 +85,7 @@ public abstract class QSTile<TState extends State> implements Listenable { } public boolean supportsDualTargets() { - return false; + return true; } public Host getHost() { @@ -335,9 +335,13 @@ public abstract class QSTile<TState extends State> implements Listenable { CastController getCastController(); FlashlightController getFlashlightController(); KeyguardMonitor getKeyguardMonitor(); + boolean isEditing(); + void setEditing(boolean editing); public interface Callback { void onTilesChanged(); + void setEditing(boolean editing); + boolean isEditing(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index a1346e7..d109782 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -16,6 +16,7 @@ package com.android.systemui.qs; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -27,6 +28,7 @@ import android.graphics.drawable.RippleDrawable; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.util.Log; import android.util.MathUtils; import android.util.TypedValue; import android.view.Gravity; @@ -48,6 +50,8 @@ public class QSTileView extends ViewGroup { private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed", Typeface.NORMAL); + private static final String TAG = "QSTileView"; + protected final Context mContext; private final View mIcon; private final View mDivider; @@ -122,6 +126,7 @@ public class QSTileView extends ViewGroup { } private void recreateLabel() { + Log.d(TAG, "recreateLabel() called with " + ""); CharSequence labelText = null; CharSequence labelDescription = null; if (mLabel != null) { @@ -131,7 +136,7 @@ public class QSTileView extends ViewGroup { } if (mDualLabel != null) { labelText = mDualLabel.getText(); - labelDescription = mLabel.getContentDescription(); + labelDescription = mDualLabel.getContentDescription(); removeView(mDualLabel); mDualLabel = null; } @@ -174,29 +179,30 @@ public class QSTileView extends ViewGroup { } } + public boolean isDual() { + return mDual; + } + public boolean setDual(boolean dual) { final boolean changed = dual != mDual; mDual = dual; if (changed) { recreateLabel(); } - if (mTileBackground instanceof RippleDrawable) { - setRipple((RippleDrawable) mTileBackground); - } + if (dual) { mTopBackgroundView.setOnClickListener(mClickPrimary); setOnClickListener(null); setClickable(false); setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - mTopBackgroundView.setBackground(mTileBackground); } else { mTopBackgroundView.setOnClickListener(null); mTopBackgroundView.setClickable(false); setOnClickListener(mClickPrimary); setOnLongClickListener(mLongClick); setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - setBackground(mTileBackground); } + setTileBackground(mTileBackground); mTopBackgroundView.setFocusable(dual); setFocusable(!dual); mDivider.setVisibility(dual ? VISIBLE : GONE); @@ -204,9 +210,23 @@ public class QSTileView extends ViewGroup { return changed; } + protected void setTileBackground(Drawable background) { + mTileBackground = background; + if (mTileBackground instanceof RippleDrawable) { + setRipple((RippleDrawable) mTileBackground); + } else { + setRipple(null); + } + if (mDual) { + mTopBackgroundView.setBackground(mTileBackground); + } else { + setBackground(mTileBackground); + } + } + private void setRipple(RippleDrawable tileBackground) { mRipple = tileBackground; - if (getWidth() != 0) { + if (getWidth() != 0 && mRipple != null) { updateRippleSize(getWidth(), getHeight()); } } @@ -225,7 +245,7 @@ public class QSTileView extends ViewGroup { return icon; } - private Drawable newTileBackground() { + public Drawable newTileBackground() { final int[] attrs = new int[] { android.R.attr.selectableItemBackgroundBorderless }; final TypedArray ta = mContext.obtainStyledAttributes(attrs); final Drawable d = ta.getDrawable(0); @@ -349,6 +369,25 @@ public class QSTileView extends ViewGroup { return lastView; } + public void setEditing(boolean editing) { + if (mDual) { + if (mTopBackgroundView != null) { + mTopBackgroundView.setFocusable(!editing); + } + if (mDualLabel != null) { + mDualLabel.setFocusable(!editing); + mDualLabel.setClickable(!editing); + } + setClickable(editing); + setFocusable(editing); + setOnLongClickListener(editing ? mLongClick : null); + } else { + if (mLabel != null) { + mLabel.setFocusable(!editing); + } + } + } + private class H extends Handler { private static final int STATE_CHANGED = 1; public H() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSViewPager.java b/packages/SystemUI/src/com/android/systemui/qs/QSViewPager.java new file mode 100644 index 0000000..36ad954 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSViewPager.java @@ -0,0 +1,113 @@ +package com.android.systemui.qs; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.support.v4.view.ViewPager; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.AccelerateInterpolator; + +/** + * Created by roman on 11/5/15. + */ +public class QSViewPager extends ViewPager { + + private static final String TAG = "QSViewPager"; + + protected static final float SCROLL_PERCENT = .10f; + private boolean mPagingEnabled; + + public QSViewPager(Context context) { + super(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int height = 0; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + int h = child.getMeasuredHeight(); + if (h > height) height = h; + } + heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + public void animatePagerTransition(final boolean forward) { + ValueAnimator animator = ValueAnimator.ofInt(0, getWidth()); + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + if (isFakeDragging()) { + endFakeDrag(); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (isFakeDragging()) { + endFakeDrag(); + } + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + + animator.setInterpolator(new AccelerateInterpolator()); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + + private int oldDragPosition = 0; + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (isFakeDragging()) { + int dragPosition = (Integer) animation.getAnimatedValue(); + int dragOffset = dragPosition - oldDragPosition; + oldDragPosition = dragPosition; + fakeDragBy(dragOffset * (forward ? -1 : 1)); + } + } + }); + if (beginFakeDrag()) { + animator.setDuration(500); + animator.start(); + } else { + Log.e(TAG, "can't start fake drag?"); + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + if (mPagingEnabled) { + return super.onInterceptTouchEvent(event); + } + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mPagingEnabled) { + return super.onTouchEvent(event); + } + return false; + } + + public void setPagingEnabled(boolean enabled) { + if (mPagingEnabled == enabled) return; + mPagingEnabled = enabled; + Log.i(TAG, "setPagingEnabled() called with " + "enabled = [" + enabled + "]"); + if (getCurrentItem() > 0 && !mPagingEnabled) { + Log.w(TAG, "resetting to item 0 because paging is disabled."); + setCurrentItem(0, true); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/EditTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/EditTile.java new file mode 100644 index 0000000..045e4f0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/EditTile.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2015 The CyanogenMod 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.tiles; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import com.android.internal.logging.MetricsLogger; +import com.android.systemui.R; +import com.android.systemui.qs.QSDragPanel; +import com.android.systemui.qs.QSTile; +import com.android.systemui.statusbar.phone.SystemUIDialog; + +public class EditTile extends QSTile<QSTile.BooleanState> { + + public EditTile(Host host) { + super(host); + } + + @Override + protected void handleDestroy() { + super.handleDestroy(); + } + + @Override + protected BooleanState newTileState() { + return new BooleanState(); + } + + @Override + public void setCallback(Callback callback) { + super.setCallback(callback); + } + + @Override + protected void handleClick() { + getHost().setEditing(!mState.value); + refreshState(!mState.value); + } + + @Override + protected void handleLongClick() { + final AlertDialog d = new AlertDialog.Builder(mContext) + .setMessage(R.string.qs_tiles_reset_confirmation) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(com.android.internal.R.string.reset, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent edit = new Intent(QSDragPanel.ACTION_EDIT_TILES); + edit.putExtra(QSDragPanel.EXTRA_RESET, true); + mContext.sendBroadcast(edit); + refreshState(false); + } + }).create(); + SystemUIDialog.makeSystemUIDialog(d); + d.show(); + } + + @Override + protected void handleUpdateState(BooleanState state, Object arg) { + state.visible = !getHost().getKeyguardMonitor().isShowing(); + state.label = mContext.getString(R.string.quick_settings_edit_label); + + if (arg instanceof Boolean) { + state.value = (boolean) arg; + } else { + state.value = getHost().isEditing(); + } + state.icon = ResourceIcon.get(R.drawable.ic_qs_edit_tiles); + } + + @Override + public int getMetricsCategory() { + return MetricsLogger.DONT_TRACK_ME_BRO; + } + + @Override + protected String composeChangeAnnouncement() { + // TODO + return null; + } + + @Override + public void setListening(boolean listening) { + // not interested + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 31d9523..a9363b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -135,6 +135,7 @@ import com.android.systemui.cm.UserContentObserver; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.qs.QSDragPanel; import com.android.systemui.qs.QSPanel; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.settings.BrightnessController; @@ -341,7 +342,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, TextView mNotificationPanelDebugText; // settings - private QSPanel mQSPanel; + private QSDragPanel mQSPanel; private DevForceNavbarObserver mDevForceNavbarObserver; // top bar @@ -1122,7 +1123,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, // Set up the quick settings tile panel - mQSPanel = (QSPanel) mStatusBarWindowContent.findViewById(R.id.quick_settings_panel); + mQSPanel = (QSDragPanel) mStatusBarWindowContent.findViewById(R.id.quick_settings_panel); if (mQSPanel != null) { final QSTileHost qsh = new QSTileHost(mContext, this, mBluetoothController, mLocationController, mRotationLockController, @@ -1138,7 +1139,27 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, qsh.setCallback(new QSTileHost.Callback() { @Override public void onTilesChanged() { - mQSPanel.setTiles(qsh.getTiles()); + mHandler.post(new Runnable() { + @Override + public void run() { + mQSPanel.setTiles(qsh.getTiles()); + } + }); + } + + @Override + public void setEditing(final boolean editing) { + mHandler.post(new Runnable() { + @Override + public void run() { + mQSPanel.setEditing(editing); + } + }); + } + + @Override + public boolean isEditing() { + return mQSPanel.isEditing(); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index 12434ac..32ee52c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -16,14 +16,18 @@ package com.android.systemui.statusbar.phone; +import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.HandlerThread; import android.os.Looper; import android.os.Process; +import android.provider.Settings; +import android.text.TextUtils; import android.util.Log; +import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.qs.QSTile; import com.android.systemui.qs.tiles.AirplaneModeTile; @@ -32,6 +36,7 @@ import com.android.systemui.qs.tiles.CastTile; import com.android.systemui.qs.tiles.CellularTile; import com.android.systemui.qs.tiles.ColorInversionTile; import com.android.systemui.qs.tiles.DndTile; +import com.android.systemui.qs.tiles.EditTile; import com.android.systemui.qs.tiles.FlashlightTile; import com.android.systemui.qs.tiles.HotspotTile; import com.android.systemui.qs.tiles.IntentTile; @@ -64,7 +69,7 @@ public class QSTileHost implements QSTile.Host, Tunable { private static final String TAG = "QSTileHost"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - protected static final String TILES_SETTING = "sysui_qs_tiles"; + public static final String TILES_SETTING = "sysui_qs_tiles"; private final Context mContext; private final PhoneStatusBar mStatusBar; @@ -118,6 +123,14 @@ public class QSTileHost implements QSTile.Host, Tunable { TunerService.get(mContext).removeTunable(this); } + public boolean isEditing() { + return mCallback.isEditing(); + } + + public void setEditing(boolean editing) { + mCallback.setEditing(editing); + } + @Override public void setCallback(Callback callback) { mCallback = callback; @@ -128,6 +141,19 @@ public class QSTileHost implements QSTile.Host, Tunable { return mTiles.values(); } + public List<String> getTileSpecs() { + return mTileSpecs; + } + + public String getSpec(QSTile<?> tile) { + for (Map.Entry<String, QSTile<?>> entry : mTiles.entrySet()) { + if (entry.getValue() == tile) { + return entry.getKey(); + } + } + return null; + } + @Override public void startActivityDismissingKeyguard(final Intent intent) { mStatusBar.postStartActivityDismissingKeyguard(intent, 0); @@ -242,7 +268,7 @@ public class QSTileHost implements QSTile.Host, Tunable { } } - protected QSTile<?> createTile(String tileSpec) { + public QSTile<?> createTile(String tileSpec) { if (tileSpec.equals("wifi")) return new WifiTile(this); else if (tileSpec.equals("bt")) return new BluetoothTile(this); else if (tileSpec.equals("inversion")) return new ColorInversionTile(this); @@ -254,6 +280,7 @@ public class QSTileHost implements QSTile.Host, Tunable { else if (tileSpec.equals("location")) return new LocationTile(this); else if (tileSpec.equals("cast")) return new CastTile(this); else if (tileSpec.equals("hotspot")) return new HotspotTile(this); + else if (tileSpec.equals("edit")) return new EditTile(this); else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec); else throw new IllegalArgumentException("Bad tile spec: " + tileSpec); } @@ -283,4 +310,32 @@ public class QSTileHost implements QSTile.Host, Tunable { } return tiles; } + + public void remove(String tile) { + MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REMOVE, tile); + List<String> tiles = new ArrayList<>(mTileSpecs); + tiles.remove(tile); + setTiles(tiles); + } + + public void setTiles(List<String> tiles) { + Settings.Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING, + TextUtils.join(",", tiles), ActivityManager.getCurrentUser()); + } + + public static int getLabelResource(String spec) { + if (spec.equals("wifi")) return R.string.quick_settings_wifi_label; + else if (spec.equals("bt")) return R.string.quick_settings_bluetooth_label; + else if (spec.equals("inversion")) return R.string.quick_settings_inversion_label; + else if (spec.equals("cell")) return R.string.quick_settings_cellular_detail_title; + else if (spec.equals("airplane")) return R.string.airplane_mode; + else if (spec.equals("dnd")) return R.string.quick_settings_dnd_label; + else if (spec.equals("rotation")) return R.string.quick_settings_rotation_locked_label; + else if (spec.equals("flashlight")) return R.string.quick_settings_flashlight_label; + else if (spec.equals("location")) return R.string.quick_settings_location_label; + else if (spec.equals("cast")) return R.string.quick_settings_cast_title; + else if (spec.equals("hotspot")) return R.string.quick_settings_hotspot_label; + else if (spec.equals("edit")) return R.string.quick_settings_edit_label; + return 0; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index d701b3c..9ebb79f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -33,12 +33,7 @@ public class SystemUIDialog extends AlertDialog { super(context, R.style.Theme_SystemUI_Dialog); mContext = context; - getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL); - getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - WindowManager.LayoutParams attrs = getWindow().getAttributes(); - attrs.setTitle(getClass().getSimpleName()); - getWindow().setAttributes(attrs); + makeSystemUIDialog(this); } public void setShowForAllUsers(boolean show) { @@ -62,4 +57,13 @@ public class SystemUIDialog extends AlertDialog { public void setNegativeButton(int resId, OnClickListener onClick) { setButton(BUTTON_NEGATIVE, mContext.getString(resId), onClick); } + + public static void makeSystemUIDialog(AlertDialog d) { + d.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL); + d.getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + WindowManager.LayoutParams attrs = d.getWindow().getAttributes(); + attrs.setTitle(SystemUIDialog.class.getClass().getSimpleName()); + d.getWindow().setAttributes(attrs); + } } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java index 37ac098..cae0d05 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java @@ -15,14 +15,23 @@ */ package com.android.systemui.tuner; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Fragment; import android.content.ClipData; +import android.content.ClipDescription; import android.content.Context; import android.content.DialogInterface; +import android.graphics.Canvas; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.provider.Settings.Secure; +import android.support.v4.view.ViewPager; import android.text.TextUtils; import android.util.Log; import android.view.DragEvent; @@ -30,11 +39,9 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnDragListener; -import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.widget.EditText; import android.widget.FrameLayout; @@ -42,6 +49,8 @@ import android.widget.ScrollView; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; +import com.android.systemui.qs.QSDragPanel; +import com.android.systemui.qs.QSPage; import com.android.systemui.qs.QSPanel; import com.android.systemui.qs.QSTile; import com.android.systemui.qs.QSTile.Host.Callback; @@ -52,6 +61,7 @@ import com.android.systemui.statusbar.phone.QSTileHost; import com.android.systemui.statusbar.policy.SecurityController; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class QsTuner extends Fragment implements Callback { @@ -59,16 +69,13 @@ public class QsTuner extends Fragment implements Callback { private static final String TAG = "QsTuner"; private static final int MENU_RESET = Menu.FIRST; + private static final int MENU_EDIT = Menu.FIRST + 1; private DraggableQsPanel mQsPanel; private CustomHost mTileHost; - private FrameLayout mDropTarget; - private ScrollView mScrollRoot; - private FrameLayout mAddTarget; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -78,6 +85,7 @@ public class QsTuner extends Fragment implements Callback { @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { menu.add(0, MENU_RESET, 0, com.android.internal.R.string.reset); + menu.add(0, MENU_EDIT, 0, "toggle edit"); } public void onResume() { @@ -93,8 +101,11 @@ public class QsTuner extends Fragment implements Callback { @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { + case MENU_EDIT: + mQsPanel.setEditing(!mQsPanel.isEditing()); + break; case MENU_RESET: - mTileHost.reset(); + mQsPanel.reset(); break; case android.R.id.home: getFragmentManager().popBackStack(); @@ -105,7 +116,7 @@ public class QsTuner extends Fragment implements Callback { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { + Bundle savedInstanceState) { mScrollRoot = (ScrollView) inflater.inflate(R.layout.tuner_qs, container, false); mQsPanel = new DraggableQsPanel(getContext()); @@ -116,10 +127,6 @@ public class QsTuner extends Fragment implements Callback { mQsPanel.refreshAllTiles(); ((ViewGroup) mScrollRoot.findViewById(R.id.all_details)).addView(mQsPanel, 0); - mDropTarget = (FrameLayout) mScrollRoot.findViewById(R.id.remove_target); - setupDropTarget(); - mAddTarget = (FrameLayout) mScrollRoot.findViewById(R.id.add_target); - setupAddTarget(); return mScrollRoot; } @@ -129,78 +136,19 @@ public class QsTuner extends Fragment implements Callback { super.onDestroyView(); } - private void setupDropTarget() { - QSTileView tileView = new QSTileView(getContext()); - QSTile.State state = new QSTile.State(); - state.visible = true; - state.icon = ResourceIcon.get(R.drawable.ic_delete); - state.label = getString(com.android.internal.R.string.delete); - tileView.onStateChanged(state); - mDropTarget.addView(tileView); - mDropTarget.setVisibility(View.GONE); - new DragHelper(tileView, new DropListener() { - @Override - public void onDrop(String sourceText) { - mTileHost.remove(sourceText); - } - }); - } - - private void setupAddTarget() { - QSTileView tileView = new QSTileView(getContext()); - QSTile.State state = new QSTile.State(); - state.visible = true; - state.icon = ResourceIcon.get(R.drawable.ic_add_circle_qs); - state.label = getString(R.string.add_tile); - tileView.onStateChanged(state); - mAddTarget.addView(tileView); - tileView.setClickable(true); - tileView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mTileHost.showAddDialog(); - } - }); - } - - public void onStartDrag() { - mDropTarget.post(new Runnable() { - @Override - public void run() { - mDropTarget.setVisibility(View.VISIBLE); - mAddTarget.setVisibility(View.GONE); - } - }); - } - - public void stopDrag() { - mDropTarget.post(new Runnable() { - @Override - public void run() { - mDropTarget.setVisibility(View.GONE); - mAddTarget.setVisibility(View.VISIBLE); - } - }); - } - @Override public void onTilesChanged() { mQsPanel.setTiles(mTileHost.getTiles()); } - private static int getLabelResource(String spec) { - if (spec.equals("wifi")) return R.string.quick_settings_wifi_label; - else if (spec.equals("bt")) return R.string.quick_settings_bluetooth_label; - else if (spec.equals("inversion")) return R.string.quick_settings_inversion_label; - else if (spec.equals("cell")) return R.string.quick_settings_cellular_detail_title; - else if (spec.equals("airplane")) return R.string.airplane_mode; - else if (spec.equals("dnd")) return R.string.quick_settings_dnd_label; - else if (spec.equals("rotation")) return R.string.quick_settings_rotation_locked_label; - else if (spec.equals("flashlight")) return R.string.quick_settings_flashlight_label; - else if (spec.equals("location")) return R.string.quick_settings_location_label; - else if (spec.equals("cast")) return R.string.quick_settings_cast_title; - else if (spec.equals("hotspot")) return R.string.quick_settings_hotspot_label; - return 0; + @Override + public void setEditing(boolean editing) { + mQsPanel.setEditing(editing); + } + + @Override + public boolean isEditing() { + return mTileHost.isEditing(); } private static class CustomHost extends QSTileHost { @@ -211,7 +159,7 @@ public class QsTuner extends Fragment implements Callback { } @Override - protected QSTile<?> createTile(String tileSpec) { + public QSTile<?> createTile(String tileSpec) { return new DraggableTile(this, tileSpec); } @@ -232,97 +180,6 @@ public class QsTuner extends Fragment implements Callback { setTiles(order); } - public void remove(String tile) { - MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REMOVE, tile); - List<String> tiles = new ArrayList<>(mTileSpecs); - tiles.remove(tile); - setTiles(tiles); - } - - public void add(String tile) { - MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_ADD, tile); - List<String> tiles = new ArrayList<>(mTileSpecs); - tiles.add(tile); - setTiles(tiles); - } - - public void reset() { - Secure.putStringForUser(getContext().getContentResolver(), - TILES_SETTING, "default", ActivityManager.getCurrentUser()); - } - - private void setTiles(List<String> tiles) { - Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING, - TextUtils.join(",", tiles), ActivityManager.getCurrentUser()); - } - - public void showAddDialog() { - List<String> tiles = mTileSpecs; - int numBroadcast = 0; - for (int i = 0; i < tiles.size(); i++) { - if (tiles.get(i).startsWith(IntentTile.PREFIX)) { - numBroadcast++; - } - } - String[] defaults = - getContext().getString(R.string.quick_settings_tiles_default).split(","); - final String[] available = new String[defaults.length + 1 - - (tiles.size() - numBroadcast)]; - final String[] availableTiles = new String[available.length]; - int index = 0; - for (int i = 0; i < defaults.length; i++) { - if (tiles.contains(defaults[i])) { - continue; - } - int resource = getLabelResource(defaults[i]); - if (resource != 0) { - availableTiles[index] = defaults[i]; - available[index++] = getContext().getString(resource); - } else { - availableTiles[index] = defaults[i]; - available[index++] = defaults[i]; - } - } - available[index++] = getContext().getString(R.string.broadcast_tile); - new AlertDialog.Builder(getContext()) - .setTitle(R.string.add_tile) - .setItems(available, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (which < available.length - 1) { - add(availableTiles[which]); - } else { - showBroadcastTileDialog(); - } - } - }).show(); - } - - public void showBroadcastTileDialog() { - final EditText editText = new EditText(getContext()); - new AlertDialog.Builder(getContext()) - .setTitle(R.string.broadcast_tile) - .setView(editText) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - String action = editText.getText().toString(); - if (isValid(action)) { - add(IntentTile.PREFIX + action + ')'); - } - } - }).show(); - } - - private boolean isValid(String action) { - for (int i = 0; i < action.length(); i++) { - char c = action.charAt(i); - if (!Character.isAlphabetic(c) && !Character.isDigit(c) && c != '.') { - return false; - } - } - return true; - } - private static class BlankSecurityController implements SecurityController { @Override public boolean hasDeviceOwner() { @@ -373,8 +230,7 @@ public class QsTuner extends Fragment implements Callback { } } - private static class DraggableTile extends QSTile<QSTile.State> - implements DropListener { + public static class DraggableTile extends QSTile<QSTile.State> { private String mSpec; private QSTileView mView; @@ -392,7 +248,7 @@ public class QsTuner extends Fragment implements Callback { @Override public boolean supportsDualTargets() { - return "wifi".equals(mSpec) || "bt".equals(mSpec); + return true; } @Override @@ -416,7 +272,7 @@ public class QsTuner extends Fragment implements Callback { } private String getLabel() { - int resource = getLabelResource(mSpec); + int resource = QSTileHost.getLabelResource(mSpec); if (resource != 0) { return mContext.getString(resource); } @@ -460,81 +316,19 @@ public class QsTuner extends Fragment implements Callback { } @Override - public void onDrop(String sourceText) { - ((CustomHost) mHost).replace(mSpec, sourceText); - } - - } - - private class DragHelper implements OnDragListener { - - private final View mView; - private final DropListener mListener; - - public DragHelper(View view, DropListener dropListener) { - mView = view; - mListener = dropListener; - mView.setOnDragListener(this); - } - - @Override - public boolean onDrag(View v, DragEvent event) { - switch (event.getAction()) { - case DragEvent.ACTION_DRAG_ENTERED: - mView.setBackgroundColor(0x77ffffff); - break; - case DragEvent.ACTION_DRAG_ENDED: - stopDrag(); - case DragEvent.ACTION_DRAG_EXITED: - mView.setBackgroundColor(0x0); - break; - case DragEvent.ACTION_DROP: - stopDrag(); - String text = event.getClipData().getItemAt(0).getText().toString(); - mListener.onDrop(text); - break; - } - return true; + public String toString() { + return mSpec; } - } - public interface DropListener { - void onDrop(String sourceText); - } + private class DraggableQsPanel extends QSDragPanel { - private class DraggableQsPanel extends QSPanel implements OnTouchListener { public DraggableQsPanel(Context context) { super(context); - mBrightnessView.setVisibility(View.GONE); - } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - for (TileRecord r : mRecords) { - new DragHelper(r.tileView, (DraggableTile) r.tile); - r.tileView.setTag(r.tile); - r.tileView.setOnTouchListener(this); - - for (int i = 0; i < r.tileView.getChildCount(); i++) { - r.tileView.getChildAt(i).setClickable(false); - } - } + setEditing(true); } - @Override - public boolean onTouch(View v, MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - String tileSpec = (String) ((DraggableTile) v.getTag()).mSpec; - ClipData data = ClipData.newPlainText(tileSpec, tileSpec); - v.startDrag(data, new View.DragShadowBuilder(v), null, 0); - onStartDrag(); - return true; - } - return false; - } } } |