OSDN Git Service

Add way to add to prototype QS editing
authorJason Monk <jmonk@google.com>
Wed, 21 Oct 2015 22:16:23 +0000 (15:16 -0700)
committerJason Monk <jmonk@google.com>
Wed, 21 Oct 2015 23:03:52 +0000 (16:03 -0700)
Change-Id: Ib5ab3f76d22db82c9dcf4e9a1bd618acd8ac1236

14 files changed:
packages/SystemUI/res/layout/qs_add_tile_layout.xml [new file with mode: 0644]
packages/SystemUI/res/layout/qs_add_tiles_list.xml [new file with mode: 0644]
packages/SystemUI/res/layout/tile_listing.xml [new file with mode: 0644]
packages/SystemUI/res/values/strings.xml
packages/SystemUI/src/com/android/systemui/qs/QSTile.java
packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
packages/SystemUI/src/com/android/systemui/qs/customize/CustomQSTileHost.java
packages/SystemUI/src/com/android/systemui/qs/customize/DropButton.java
packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java

diff --git a/packages/SystemUI/res/layout/qs_add_tile_layout.xml b/packages/SystemUI/res/layout/qs_add_tile_layout.xml
new file mode 100644 (file)
index 0000000..962b00e
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:paddingTop="20dp"
+    android:paddingStart="7dp"
+    android:paddingEnd="7dp">
+    <LinearLayout
+        android:layout_height="wrap_content"
+        android:layout_width="80dp"
+        android:orientation="vertical">
+        <ImageView
+            android:id="@+id/tile_icon"
+            android:layout_gravity="center"
+            android:layout_width="@dimen/qs_tile_icon_size"
+            android:layout_height="@dimen/qs_tile_icon_size" />
+        <TextView
+            android:id="@+id/tile_label"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center|bottom"
+            android:paddingTop="10dp"
+            android:gravity="center"
+            android:textSize="@dimen/qs_tile_text_size" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/qs_add_tiles_list.xml b/packages/SystemUI/res/layout/qs_add_tiles_list.xml
new file mode 100644 (file)
index 0000000..312c207
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <ListView
+            android:id="@android:id/list"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+        <TextView
+            android:paddingTop="10dp"
+            android:id="@+id/empty_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:text="@string/no_tiles_add" />
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/tile_listing.xml b/packages/SystemUI/res/layout/tile_listing.xml
new file mode 100644 (file)
index 0000000..9ab62ca
--- /dev/null
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ** Copyright 2015, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License"); 
+ ** you may not use this file except in compliance with the License. 
+ ** You may obtain a copy of the License at 
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0 
+ **
+ ** Unless required by applicable law or agreed to in writing, software 
+ ** distributed under the License is distributed on an "AS IS" BASIS, 
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+ ** See the License for the specific language governing permissions and 
+ ** limitations under the License.
+ -->
+
+ <LinearLayout
+     xmlns:android="http://schemas.android.com/apk/res/android"
+     android:layout_width="match_parent"
+     android:layout_height="wrap_content"
+     android:background="@drawable/qs_background_primary"
+     android:paddingBottom="20dp"
+     android:orientation="vertical">
+
+     <LinearLayout
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:background="@drawable/notification_header_bg"
+         android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+         android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+         android:paddingTop="10dp"
+         android:paddingBottom="10dp"
+         android:orientation="horizontal">
+         <ImageView
+             android:id="@android:id/icon"
+             android:layout_width="36dp"
+             android:layout_height="36dp" />
+         <TextView
+             android:id="@android:id/title"
+             android:paddingStart="10dp"
+             android:textColor="@android:color/white"
+             android:textAppearance="?android:attr/textAppearanceSmall"
+             android:layout_width="wrap_content"
+             android:layout_height="wrap_content"
+             android:layout_gravity="center_vertical" />
+     </LinearLayout>
+
+     <GridLayout
+         android:id="@+id/tile_grid"
+         android:layout_width="match_parent"
+         android:layout_height="wrap_content"
+         android:columnCount="4" />
+
+ </LinearLayout>
index a525fbb..5c738d3 100644 (file)
     <string name="qs_customize" translatable="false">Allow long-press customize in Quick Settings</string>
     <string name="qs_customize_info" translatable="false">Info</string>
     <string name="qs_customize_remove" translatable="false">Remove</string>
+    <string name="no_tiles_add" translatable="false">No tiles to add</string>
 
 </resources>
index 8e98f10..61cb224 100644 (file)
@@ -137,7 +137,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
     }
 
-    protected final void refreshState() {
+    public final void refreshState() {
         refreshState(null);
     }
 
@@ -360,6 +360,19 @@ public abstract class QSTile<TState extends State> implements Listenable {
         }
     }
 
+    public static class DrawableIcon extends Icon {
+        protected final Drawable mDrawable;
+
+        public DrawableIcon(Drawable drawable) {
+            mDrawable = drawable;
+        }
+
+        @Override
+        public Drawable getDrawable(Context context) {
+            return mDrawable;
+        }
+    }
+
     public static class ResourceIcon extends Icon {
         private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
 
index 12a099d..1336eec 100644 (file)
@@ -72,7 +72,8 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
         mLargeCellHeight = mAllowDual ? res.getDimensionPixelSize(R.dimen.qs_dual_tile_height)
                 : mCellHeight;
         mLargeCellWidth = mAllowDual ? (int) (mLargeCellHeight * TILE_ASPECT) : mCellWidth;
-        mDualTileUnderlap = res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
+        mDualTileUnderlap = mAllowDual
+                ? res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical) : 0;
         if (mColumns != columns) {
             mColumns = columns;
             postInvalidate();
index 3491cb6..3f85982 100644 (file)
@@ -49,7 +49,7 @@ public class CustomQSTileHost extends QSTileHost {
     }
 
     @Override
-    protected QSTile<?> createTile(String tileSpec) {
+    public QSTile<?> createTile(String tileSpec) {
         QSTile<?> tile = super.createTile(tileSpec);
         tile.setTileSpec(tileSpec);
         return tile;
@@ -113,6 +113,11 @@ public class CustomQSTileHost extends QSTileHost {
         return mTiles;
     }
 
+    public void addTile(String spec) {
+        mTiles.add(spec);
+        super.onTuningChanged(TILES_SETTING, null);
+    }
+
     public void replace(String oldTile, String newTile) {
         if (oldTile.equals(newTile)) {
             return;
index 0e15f2b..3135408 100644 (file)
@@ -43,7 +43,7 @@ public class DropButton extends TextView implements OnDragListener {
     }
 
     private void setHovering(boolean hovering) {
-        setAlpha(hovering ? .5f : 1);
+        setAlpha(hovering ? .3f : 1);
     }
 
     @Override
index fe8d78b..b5a885c 100644 (file)
@@ -18,18 +18,31 @@ package com.android.systemui.qs.customize;
 import android.animation.Animator;
 import android.content.ClipData;
 import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnDismissListener;
 import android.util.AttributeSet;
 import android.util.TypedValue;
-import android.view.*;
+import android.view.ContextThemeWrapper;
+import android.view.DragEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.widget.LinearLayout;
+import android.widget.ListView;
 import android.widget.Toolbar;
 import android.widget.Toolbar.OnMenuItemClickListener;
+
 import com.android.systemui.R;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.qs.QSDetailClipper;
 import com.android.systemui.qs.QSTile.Host.Callback;
 import com.android.systemui.qs.customize.DropButton.OnDropListener;
+import com.android.systemui.qs.customize.TileAdapter.TileSelectedListener;
 import com.android.systemui.statusbar.phone.PhoneStatusBar;
 import com.android.systemui.statusbar.phone.QSTileHost;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -44,7 +57,8 @@ import java.util.ArrayList;
  * *someday* do fancy animations to get into/out of it.
  */
 public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener, Callback,
-        OnDropListener, OnClickListener, Animator.AnimatorListener {
+        OnDropListener, OnClickListener, Animator.AnimatorListener, TileSelectedListener,
+        OnCancelListener, OnDismissListener {
 
     private static final int MENU_SAVE = Menu.FIRST;
     private static final int MENU_RESET = Menu.FIRST + 1;
@@ -61,6 +75,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
     private DropButton mInfoButton;
     private DropButton mRemoveButton;
     private FloatingActionButton mFab;
+    private SystemUIDialog mDialog;
 
     public QSCustomizer(Context context, AttributeSet attrs) {
         super(new ContextThemeWrapper(context, android.R.style.Theme_Material), attrs);
@@ -162,6 +177,14 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
     }
 
     @Override
+    public void onTileSelected(String spec) {
+        if (mDialog != null) {
+            mHost.addTile(spec);
+            mDialog.dismiss();
+        }
+    }
+
+    @Override
     public void onTilesChanged() {
         mQsPanel.setTiles(mHost.getTiles());
     }
@@ -193,12 +216,41 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
     @Override
     public void onClick(View v) {
         if (mFab == v) {
-            SystemUIDialog dialog = new SystemUIDialog(mContext);
-            dialog.show();
+            mDialog = new SystemUIDialog(mContext,
+                    android.R.style.Theme_Material_Dialog);
+            View view = LayoutInflater.from(mContext).inflate(R.layout.qs_add_tiles_list, null);
+            ListView listView = (ListView) view.findViewById(android.R.id.list);
+            TileAdapter adapter = new TileAdapter(mContext, mHost.getTiles(), mHost);
+            adapter.setListener(this);
+            listView.setDivider(null);
+            listView.setDividerHeight(0);
+            listView.setAdapter(adapter);
+            listView.setEmptyView(view.findViewById(R.id.empty_text));
+            mDialog.setView(view);
+            mDialog.setOnDismissListener(this);
+            mDialog.setOnCancelListener(this);
+            mDialog.show();
+            // Too lazy to figure out what this will be now, but it should probably be something
+            // besides just a dialog.
+            // For now, just make it big.
+            WindowManager.LayoutParams params = mDialog.getWindow().getAttributes();
+            params.width = WindowManager.LayoutParams.MATCH_PARENT;
+            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+            mDialog.getWindow().setAttributes(params);
         }
     }
 
     @Override
+    public void onDismiss(DialogInterface dialog) {
+        mDialog = null;
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        mDialog = null;
+    }
+
+    @Override
     public void onAnimationEnd(Animator animation) {
         if (!isShown) {
             mPhoneStatusBar.getStatusBarWindow().removeView(this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
new file mode 100644 (file)
index 0000000..579f58d
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.customize;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.GridLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTile.Icon;
+import com.android.systemui.qs.tiles.CustomTile;
+import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.tuner.QSPagingSwitch;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+public class TileAdapter extends BaseAdapter {
+
+    private static final String TAG = "TileAdapter";
+
+    private final ArrayList<TileGroup> mGroups = new ArrayList<>();
+    private final Context mContext;
+
+    private TileSelectedListener mListener;
+    private ArrayList<String> mCurrentTiles;
+
+    public TileAdapter(Context context, Collection<QSTile<?>> currentTiles, QSTileHost host) {
+        mContext = context;
+        addSystemTiles(currentTiles, host);
+        // TODO: Live?
+    }
+
+    private void addSystemTiles(Collection<QSTile<?>> currentTiles, QSTileHost host) {
+        try {
+            ArrayList<String> tileSpecs = new ArrayList<>();
+            for (QSTile<?> tile : currentTiles) {
+                tileSpecs.add(tile.getTileSpec());
+            }
+            mCurrentTiles = tileSpecs;
+            final TileGroup group = new TileGroup("com.android.settings", mContext);
+            // TODO: Pull this list from a more authoritative place.
+            String[] possibleTiles = QSPagingSwitch.QS_PAGE_TILES.split(",");
+            for (int i = 0; i < possibleTiles.length; i++) {
+                final String spec = possibleTiles[i];
+                if (spec.startsWith("q")) {
+                    // Quick tiles can't be customized.
+                    continue;
+                }
+                if (tileSpecs.contains(spec)) {
+                    continue;
+                }
+                final QSTile<?> tile = host.createTile(spec);
+                // Bad, bad, very bad.
+                tile.setListening(true);
+                tile.clearState();
+                tile.refreshState();
+                tile.setListening(false);
+                new Handler(host.getLooper()).post(new Runnable() {
+                    @Override
+                    public void run() {
+                        group.addTile(spec, tile.getState().icon, tile.getState().label, mContext);
+                    }
+                });
+            }
+            // Error: Badness (10000).
+            // Serialize this work after the host's looper's queue is empty.
+            new Handler(host.getLooper()).post(new Runnable() {
+                @Override
+                public void run() {
+                    new Handler(Looper.getMainLooper()).post(new Runnable() {
+                        @Override
+                        public void run() {
+                            if (group.mTiles.size() > 0) {
+                                mGroups.add(group);
+                                notifyDataSetChanged();
+                            }
+                            new QueryTilesTask().execute();
+                        }
+                    });
+                }
+            });
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Couldn't load system tiles", e);
+        }
+    }
+
+    public void setListener(TileSelectedListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public int getCount() {
+        return mGroups.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return mGroups.get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        return mGroups.get(position).getView(mContext, convertView, parent, mListener);
+    }
+
+    private static class TileGroup {
+        private final ArrayList<TileInfo> mTiles = new ArrayList<>();
+        private CharSequence mLabel;
+        private Drawable mIcon;
+
+        public TileGroup(String pkg, Context context) throws NameNotFoundException {
+            PackageManager pm = context.getPackageManager();
+            ApplicationInfo info = pm.getApplicationInfo(pkg, 0);
+            mLabel = info.loadLabel(pm);
+            mIcon = info.loadIcon(pm);
+            Log.d(TAG, "Added " + mLabel);
+        }
+
+        private void addTile(String spec, Drawable icon, String label) {
+            TileInfo info = new TileInfo();
+            info.label = label;
+            info.drawable = icon;
+            info.spec = spec;
+            mTiles.add(info);
+        }
+
+        private void addTile(String spec, Icon icon, String label, Context context) {
+            addTile(spec, icon.getDrawable(context), label);
+        }
+
+        private View getView(Context context, View convertView, ViewGroup parent,
+                final TileSelectedListener listener) {
+            if (convertView == null) {
+                convertView = LayoutInflater.from(context).inflate(R.layout.tile_listing, parent,
+                        false);
+            }
+            ((TextView) convertView.findViewById(android.R.id.title)).setText(mLabel);
+            ((ImageView) convertView.findViewById(android.R.id.icon)).setImageDrawable(mIcon);
+            GridLayout grid = (GridLayout) convertView.findViewById(R.id.tile_grid);
+            final int N = mTiles.size();
+            if (grid.getChildCount() != N) {
+                grid.removeAllViews();
+            }
+            for (int i = 0; i < N; i++) {
+                if (grid.getChildCount() <= i) {
+                    grid.addView(createTile(context));
+                }
+                View view = grid.getChildAt(i);
+                final TileInfo tileInfo = mTiles.get(i);
+                ((ImageView) view.findViewById(R.id.tile_icon)).setImageDrawable(tileInfo.drawable);
+                ((TextView) view.findViewById(R.id.tile_label)).setText(tileInfo.label);
+                view.setClickable(true);
+                view.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        listener.onTileSelected(tileInfo.spec);
+                    }
+                });
+            }
+            return convertView;
+        }
+
+        private View createTile(Context context) {
+            return LayoutInflater.from(context).inflate(R.layout.qs_add_tile_layout, null);
+        }
+    }
+
+    private static class TileInfo {
+        private String spec;
+        private Drawable drawable;
+        private String label;
+    }
+
+    private class QueryTilesTask extends AsyncTask<Void, Void, Collection<TileGroup>> {
+        // TODO: Become non-prototype and an API.
+        private static final String TILE_ACTION = "android.intent.action.QS_TILE";
+
+        @Override
+        protected Collection<TileGroup> doInBackground(Void... params) {
+            HashMap<String, TileGroup> pkgMap = new HashMap<>();
+            PackageManager pm = mContext.getPackageManager();
+            // TODO: Handle userness.
+            List<ResolveInfo> services = pm.queryIntentServices(new Intent(TILE_ACTION), 0);
+            for (ResolveInfo info : services) {
+                String packageName = info.serviceInfo.packageName;
+                ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name);
+                String spec = CustomTile.PREFIX + componentName.flattenToShortString() + ")";
+                if (mCurrentTiles.contains(spec)) {
+                    continue;
+                }
+                try {
+                    TileGroup group = pkgMap.get(packageName);
+                    if (group == null) {
+                        group = new TileGroup(packageName, mContext);
+                        pkgMap.put(packageName, group);
+                    }
+                    Drawable icon = info.serviceInfo.loadIcon(pm);
+                    CharSequence label = info.serviceInfo.loadLabel(pm);
+                    group.addTile(spec, icon, label != null ? label.toString() : "null");
+                } catch (NameNotFoundException e) {
+                    Log.w(TAG, "Couldn't find resolved package... " + packageName, e);
+                }
+            }
+            return pkgMap.values();
+        }
+
+        @Override
+        protected void onPostExecute(Collection<TileGroup> result) {
+            mGroups.addAll(result);
+            notifyDataSetChanged();
+        }
+    }
+
+    public interface TileSelectedListener {
+        void onTileSelected(String spec);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomTile.java
new file mode 100644 (file)
index 0000000..cf76ed4
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.qs.QSTile;
+
+public class CustomTile extends QSTile<QSTile.State> {
+    public static final String PREFIX = "custom(";
+
+    private final ComponentName mComponent;
+
+    private CustomTile(Host host, String action) {
+        super(host);
+        mComponent = ComponentName.unflattenFromString(action);
+    }
+
+    public static QSTile<?> create(Host host, String spec) {
+        if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
+            throw new IllegalArgumentException("Bad intent tile spec: " + spec);
+        }
+        final String action = spec.substring(PREFIX.length(), spec.length() - 1);
+        if (action.isEmpty()) {
+            throw new IllegalArgumentException("Empty intent tile spec action");
+        }
+        return new CustomTile(host, action);
+    }
+
+    @Override
+    public void setListening(boolean listening) {
+    }
+
+    @Override
+    protected State newTileState() {
+        return new State();
+    }
+
+    @Override
+    protected void handleUserSwitch(int newUserId) {
+        super.handleUserSwitch(newUserId);
+    }
+
+    @Override
+    protected void handleClick() {
+        MetricsLogger.action(mContext, getMetricsCategory(), mComponent.getPackageName());
+    }
+
+    @Override
+    protected void handleLongClick() {
+    }
+
+    @Override
+    protected void handleUpdateState(State state, Object arg) {
+        // TODO: Actual things.
+        try {
+            PackageManager pm = mContext.getPackageManager();
+            ServiceInfo info = pm.getServiceInfo(mComponent, 0);
+            state.visible = true;
+            state.icon = new DrawableIcon(info.loadIcon(pm));
+            state.label = info.loadLabel(pm).toString();
+            state.contentDescription = state.label;
+        } catch (Exception e) {
+            state.visible = false;
+        }
+    }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsLogger.QS_INTENT;
+    }
+}
index f8ddc73..96b919e 100644 (file)
@@ -246,7 +246,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, false);
         else if (tileSpec.equals("bt")) return new BluetoothTile(this, false);
         else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
@@ -272,6 +272,7 @@ public class QSTileHost implements QSTile.Host, Tunable {
         else if (tileSpec.equals("qlock")) return new QLockTile(this);
         // Intent tiles.
         else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
+        else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this,tileSpec);
         else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
     }
 
index d701b3c..116237d 100644 (file)
@@ -30,7 +30,11 @@ public class SystemUIDialog extends AlertDialog {
     private final Context mContext;
 
     public SystemUIDialog(Context context) {
-        super(context, R.style.Theme_SystemUI_Dialog);
+        this(context, R.style.Theme_SystemUI_Dialog);
+    }
+
+    public SystemUIDialog(Context context, int theme) {
+        super(context, theme);
         mContext = context;
 
         getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL);
index 3ac2a94..4ce0933 100644 (file)
@@ -211,7 +211,7 @@ public class QsTuner extends Fragment implements Callback {
         }
 
         @Override
-        protected QSTile<?> createTile(String tileSpec) {
+        public QSTile<?> createTile(String tileSpec) {
             return new DraggableTile(this, tileSpec);
         }