summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoman Birg <roman@cyngn.com>2015-11-04 11:01:13 -0800
committerAdnan Begovic <adnan@cyngn.com>2015-11-23 13:22:37 -0800
commit8b59e888aa4236f29728785b1908c06989ec6f8a (patch)
tree0d37638ab669e10c6d96f644c537b59e89707555
parent36a5c9978f8ea0f61353457074814eacc4a6057a (diff)
downloadframeworks_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>
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_edit_tiles.xml30
-rw-r--r--packages/SystemUI/res/drawable/qs_tile_background_drag.xml22
-rw-r--r--packages/SystemUI/res/layout/qs_panel.xml2
-rw-r--r--packages/SystemUI/res/layout/qs_tile_top85
-rw-r--r--packages/SystemUI/res/values/cm_colors.xml3
-rw-r--r--packages/SystemUI/res/values/cm_strings.xml4
-rw-r--r--packages/SystemUI/res/values/config.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java1467
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPage.java143
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java70
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelTopView.java228
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTile.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileView.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSViewPager.java113
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/EditTile.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java276
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;
- }
}
}