First pass at drawer support for SettingsLib
Change-Id: I9669bd511dd3c4b7f59b8600a5af65d9837af015
diff --git a/packages/SettingsLib/Android.mk b/packages/SettingsLib/Android.mk
index 0245ed3..c860668 100644
--- a/packages/SettingsLib/Android.mk
+++ b/packages/SettingsLib/Android.mk
@@ -3,6 +3,8 @@
LOCAL_MODULE := SettingsLib
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/packages/SettingsLib/res/drawable/ic_menu.xml b/packages/SettingsLib/res/drawable/ic_menu.xml
new file mode 100644
index 0000000..910a3d0
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_menu.xml
@@ -0,0 +1,24 @@
+<!--
+ 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M3.0,18.0l18.0,0.0l0.0,-2.0L3.0,16.0l0.0,2.0zm0.0,-5.0l18.0,0.0l0.0,-2.0L3.0,11.0l0.0,2.0zm0.0,-7.0l0.0,2.0l18.0,0.0L21.0,6.0L3.0,6.0z"/>
+</vector>
diff --git a/packages/SettingsLib/res/layout/drawer_category.xml b/packages/SettingsLib/res/layout/drawer_category.xml
new file mode 100644
index 0000000..20afcd4
--- /dev/null
+++ b/packages/SettingsLib/res/layout/drawer_category.xml
@@ -0,0 +1,36 @@
+<!--
+ 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?android:attr/listDivider" />
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:paddingStart="16dp"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/res/layout/drawer_item.xml b/packages/SettingsLib/res/layout/drawer_item.xml
new file mode 100644
index 0000000..4b53049
--- /dev/null
+++ b/packages/SettingsLib/res/layout/drawer_item.xml
@@ -0,0 +1,40 @@
+<!--
+ 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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/tile_item"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:orientation="horizontal" >
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="72dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical"
+ android:tint="?android:attr/colorAccent"
+ android:paddingStart="16dp"
+ android:paddingEnd="32dp" />
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:textColor="?android:attr/colorControlNormal"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/res/layout/settings_with_drawer.xml b/packages/SettingsLib/res/layout/settings_with_drawer.xml
new file mode 100644
index 0000000..a9a78e8
--- /dev/null
+++ b/packages/SettingsLib/res/layout/settings_with_drawer.xml
@@ -0,0 +1,55 @@
+<!--
+ 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.
+-->
+<android.support.v4.widget.DrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <!-- The main content view -->
+ <LinearLayout
+ android:id="@+id/content_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" >
+ <FrameLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="?android:attr/actionBarStyle">
+ <Toolbar
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:navigationContentDescription="@*android:string/action_bar_up_description"
+ android:theme="?android:attr/actionBarTheme"
+ style="?android:attr/toolbarStyle"
+ android:background="?android:attr/colorPrimary" />
+ </FrameLayout>
+ <FrameLayout
+ android:id="@+id/content_frame"
+ android:layout_width="match_parent"
+ android:layout_height="fill_parent" />
+ </LinearLayout>
+ <!-- The navigation drawer -->
+ <ListView android:id="@+id/left_drawer"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:choiceMode="singleChoice"
+ android:divider="@android:color/transparent"
+ android:dividerHeight="0dp"
+ android:background="?android:attr/colorBackground" />
+</android.support.v4.widget.DrawerLayout>
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
new file mode 100644
index 0000000..0f322cf
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
@@ -0,0 +1,123 @@
+/**
+ * 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.settingslib.drawer;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DashboardCategory implements Parcelable {
+
+ /**
+ * Title of the category that is shown to the user.
+ */
+ public CharSequence title;
+
+ /**
+ * Key used for placing external tiles.
+ */
+ public String key;
+
+ /**
+ * Used to control display order.
+ */
+ public int priority;
+
+ /**
+ * List of the category's children
+ */
+ public List<DashboardTile> tiles = new ArrayList<DashboardTile>();
+
+
+ public DashboardCategory() {
+ // Empty
+ }
+
+ public void addTile(DashboardTile tile) {
+ tiles.add(tile);
+ }
+
+ public void addTile(int n, DashboardTile tile) {
+ tiles.add(n, tile);
+ }
+
+ public void removeTile(DashboardTile tile) {
+ tiles.remove(tile);
+ }
+
+ public void removeTile(int n) {
+ tiles.remove(n);
+ }
+
+ public int getTilesCount() {
+ return tiles.size();
+ }
+
+ public DashboardTile getTile(int n) {
+ return tiles.get(n);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ TextUtils.writeToParcel(title, dest, flags);
+ dest.writeString(key);
+ dest.writeInt(priority);
+
+ final int count = tiles.size();
+ dest.writeInt(count);
+
+ for (int n = 0; n < count; n++) {
+ DashboardTile tile = tiles.get(n);
+ tile.writeToParcel(dest, flags);
+ }
+ }
+
+ public void readFromParcel(Parcel in) {
+ title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ key = in.readString();
+ priority = in.readInt();
+
+ final int count = in.readInt();
+
+ for (int n = 0; n < count; n++) {
+ DashboardTile tile = DashboardTile.CREATOR.createFromParcel(in);
+ tiles.add(tile);
+ }
+ }
+
+ DashboardCategory(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public static final Creator<DashboardCategory> CREATOR = new Creator<DashboardCategory>() {
+ public DashboardCategory createFromParcel(Parcel source) {
+ return new DashboardCategory(source);
+ }
+
+ public DashboardCategory[] newArray(int size) {
+ return new DashboardCategory[size];
+ }
+ };
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardTile.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardTile.java
new file mode 100644
index 0000000..63a40a7
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardTile.java
@@ -0,0 +1,159 @@
+/**
+ * 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.settingslib.drawer;
+
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+
+/**
+ * Description of a single dashboard tile that the user can select.
+ */
+public class DashboardTile implements Parcelable {
+
+ /**
+ * Title of the tile that is shown to the user.
+ * @attr ref android.R.styleable#PreferenceHeader_title
+ */
+ public CharSequence title;
+
+ /**
+ * Optional summary describing what this tile controls.
+ * @attr ref android.R.styleable#PreferenceHeader_summary
+ */
+ public CharSequence summary;
+
+ /**
+ * Optional icon to show for this tile.
+ * @attr ref android.R.styleable#PreferenceHeader_icon
+ */
+ public Icon icon;
+
+ /**
+ * Full class name of the fragment to display when this tile is
+ * selected.
+ * @attr ref android.R.styleable#PreferenceHeader_fragment
+ */
+ public String fragment;
+
+ /**
+ * Optional arguments to supply to the fragment when it is
+ * instantiated.
+ */
+ public Bundle fragmentArguments;
+
+ /**
+ * Intent to launch when the preference is selected.
+ */
+ public Intent intent;
+
+ /**
+ * Optional list of user handles which the intent should be launched on.
+ */
+ public ArrayList<UserHandle> userHandle = new ArrayList<>();
+
+ /**
+ * Optional additional data for use by subclasses of the activity
+ */
+ public Bundle extras;
+
+ /**
+ * Category in which the tile should be placed.
+ */
+ public String category;
+
+ /**
+ * Priority of the intent filter that created this tile, used for display ordering.
+ */
+ public int priority;
+
+ public DashboardTile() {
+ // Empty
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ TextUtils.writeToParcel(title, dest, flags);
+ TextUtils.writeToParcel(summary, dest, flags);
+ if (icon != null) {
+ dest.writeByte((byte) 1);
+ icon.writeToParcel(dest, flags);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ dest.writeString(fragment);
+ dest.writeBundle(fragmentArguments);
+ if (intent != null) {
+ dest.writeByte((byte) 1);
+ intent.writeToParcel(dest, flags);
+ } else {
+ dest.writeByte((byte) 0);
+ }
+ final int N = userHandle.size();
+ dest.writeInt(N);
+ for (int i = 0; i < N; i++) {
+ userHandle.get(i).writeToParcel(dest, flags);
+ }
+ dest.writeBundle(extras);
+ dest.writeString(category);
+ dest.writeInt(priority);
+ }
+
+ public void readFromParcel(Parcel in) {
+ title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ if (in.readByte() != 0) {
+ icon = Icon.CREATOR.createFromParcel(in);
+ }
+ fragment = in.readString();
+ fragmentArguments = in.readBundle();
+ if (in.readByte() != 0) {
+ intent = Intent.CREATOR.createFromParcel(in);
+ }
+ final int N = in.readInt();
+ for (int i = 0; i < N; i++) {
+ userHandle.add(UserHandle.CREATOR.createFromParcel(in));
+ }
+ extras = in.readBundle();
+ category = in.readString();
+ priority = in.readInt();
+ }
+
+ DashboardTile(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public static final Creator<DashboardTile> CREATOR = new Creator<DashboardTile>() {
+ public DashboardTile createFromParcel(Parcel source) {
+ return new DashboardTile(source);
+ }
+ public DashboardTile[] newArray(int size) {
+ return new DashboardTile[size];
+ }
+ };
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
new file mode 100644
index 0000000..4f97665
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -0,0 +1,135 @@
+/**
+ * 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.settingslib.drawer;
+
+import android.annotation.LayoutRes;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.widget.DrawerLayout;
+import android.util.Pair;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.ListView;
+import android.widget.Toolbar;
+
+import com.android.settingslib.R;
+
+import java.util.HashMap;
+import java.util.List;
+
+public class SettingsDrawerActivity extends Activity {
+
+ private SettingsDrawerAdapter mDrawerAdapter;
+ // Hold on to a cache of tiles to avoid loading the info multiple times.
+ private final HashMap<Pair<String, String>, DashboardTile> mTileCache = new HashMap<>();
+ private List<DashboardCategory> mDashboardCategories;
+ private DrawerLayout mDrawerLayout;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ super.setContentView(R.layout.settings_with_drawer);
+ mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ // Nope.
+ Toolbar toolbar = (Toolbar) findViewById(R.id.action_bar);
+ setActionBar(toolbar);
+ mDrawerAdapter = new SettingsDrawerAdapter(this);
+ ListView listView = (ListView) findViewById(R.id.left_drawer);
+ listView.setAdapter(mDrawerAdapter);
+ listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ public void onItemClick(android.widget.AdapterView<?> parent, View view, int position,
+ long id) {
+ onTileClicked(mDrawerAdapter.getTile(position));
+ };
+ });
+ getActionBar().setHomeAsUpIndicator(R.drawable.ic_menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ openDrawer();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ mDrawerAdapter.updateCategories();
+ }
+
+ public void openDrawer() {
+ mDrawerLayout.openDrawer(Gravity.START);
+ }
+
+ public void closeDrawer() {
+ mDrawerLayout.closeDrawers();
+ }
+
+ @Override
+ public void setContentView(@LayoutRes int layoutResID) {
+ LayoutInflater.from(this).inflate(layoutResID,
+ (ViewGroup) findViewById(R.id.content_frame));
+ }
+
+ @Override
+ public void setContentView(View view) {
+ ((ViewGroup) findViewById(R.id.content_frame)).addView(view);
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ ((ViewGroup) findViewById(R.id.content_frame)).addView(view, params);
+ }
+
+ public void updateDrawer() {
+ // TODO: Do this in the background with some loading.
+ mDrawerAdapter.updateCategories();
+ getActionBar().setDisplayHomeAsUpEnabled(mDrawerAdapter.getCount() != 0);
+ }
+
+ public List<DashboardCategory> getDashboardCategories(boolean force) {
+ if (force) {
+ mDashboardCategories = TileUtils.getCategories(this, mTileCache);
+ }
+ return mDashboardCategories;
+ }
+
+ protected void openTile(DashboardTile tile) {
+ closeDrawer();
+ startActivity(tile.intent);
+ }
+
+ protected void onTileClicked(DashboardTile tile) {
+ openTile(tile);
+ finish();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
new file mode 100644
index 0000000..fef716c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerAdapter.java
@@ -0,0 +1,106 @@
+/*
+ * 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.settingslib.drawer;
+
+import android.graphics.drawable.Icon;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.settingslib.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SettingsDrawerAdapter extends BaseAdapter {
+
+ private final ArrayList<Item> mItems = new ArrayList<>();
+ private final SettingsDrawerActivity mActivity;
+
+ public SettingsDrawerAdapter(SettingsDrawerActivity activity) {
+ mActivity = activity;
+ }
+
+ void updateCategories() {
+ List<DashboardCategory> categories = mActivity.getDashboardCategories(true);
+ mItems.clear();
+ for (int i = 0; i < categories.size(); i++) {
+ Item category = new Item();
+ category.icon = null;
+ DashboardCategory dashboardCategory = categories.get(i);
+ category.label = dashboardCategory.title;
+ mItems.add(category);
+ for (int j = 0; j < dashboardCategory.tiles.size(); j++) {
+ Item tile = new Item();
+ DashboardTile dashboardTile = dashboardCategory.tiles.get(j);
+ tile.label = dashboardTile.title;
+ tile.icon = dashboardTile.icon;
+ tile.tile = dashboardTile;
+ mItems.add(tile);
+ }
+ }
+ notifyDataSetChanged();
+ }
+
+ public DashboardTile getTile(int position) {
+ return mItems.get(position).tile;
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return mItems.get(position).icon != null;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Item item = mItems.get(position);
+ boolean isTile = item.icon != null;
+ if (convertView == null || (isTile != (convertView.getId() == R.id.tile_item))) {
+ convertView = LayoutInflater.from(mActivity).inflate(isTile ? R.layout.drawer_item
+ : R.layout.drawer_category,
+ parent, false);
+ }
+ if (isTile) {
+ ((ImageView) convertView.findViewById(android.R.id.icon)).setImageIcon(item.icon);
+ }
+ ((TextView) convertView.findViewById(android.R.id.title)).setText(item.label);
+ return convertView;
+ }
+
+ private static class Item {
+ public Icon icon;
+ public CharSequence label;
+ public DashboardTile tile;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
new file mode 100644
index 0000000..fae692b
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -0,0 +1,308 @@
+/*
+ * 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.settingslib.drawer;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TileUtils {
+
+ private static final boolean DEBUG = true;
+
+ private static final String LOG_TAG = "TileUtils";
+
+ /**
+ * Settings will search for system activities of this action and add them as a top level
+ * settings tile using the following parameters.
+ *
+ * <p>A category must be specified in the meta-data for the activity named
+ * {@link #EXTRA_CATEGORY_KEY}
+ *
+ * <p>The title may be defined by meta-data named {@link #META_DATA_PREFERENCE_TITLE}
+ * otherwise the label for the activity will be used.
+ *
+ * <p>The icon may be defined by meta-data named {@link #META_DATA_PREFERENCE_ICON}
+ * otherwise the icon for the activity will be used.
+ *
+ * <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
+ */
+ private static final String EXTRA_SETTINGS_ACTION =
+ "com.android.settings.action.EXTRA_SETTINGS";
+
+ /**
+ * Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
+ */
+ private static final String SETTINGS_ACTION =
+ "com.android.settings.action.SETTINGS";
+
+ private static final String OPERATOR_SETTINGS =
+ "com.android.settings.OPERATOR_APPLICATION_SETTING";
+
+ private static final String OPERATOR_DEFAULT_CATEGORY =
+ "com.android.settings.category.wireless";
+
+ private static final String MANUFACTURER_SETTINGS =
+ "com.android.settings.MANUFACTURER_APPLICATION_SETTING";
+
+ private static final String MANUFACTURER_DEFAULT_CATEGORY =
+ "com.android.settings.category.device";
+
+ /**
+ * The key used to get the category from metadata of activities of action
+ * {@link #EXTRA_SETTINGS_ACTION}
+ * The value must be one of:
+ * <li>com.android.settings.category.wireless</li>
+ * <li>com.android.settings.category.device</li>
+ * <li>com.android.settings.category.personal</li>
+ * <li>com.android.settings.category.system</li>
+ */
+ private static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
+
+ /**
+ * Name of the meta-data item that should be set in the AndroidManifest.xml
+ * to specify the icon that should be displayed for the preference.
+ */
+ public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
+
+ /**
+ * Name of the meta-data item that should be set in the AndroidManifest.xml
+ * to specify the title that should be displayed for the preference.
+ */
+ public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
+
+ /**
+ * Name of the meta-data item that should be set in the AndroidManifest.xml
+ * to specify the summary text that should be displayed for the preference.
+ */
+ public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
+
+ private static final String SETTING_PKG = "com.android.settings";
+
+ public static List<DashboardCategory> getCategories(Context context) {
+ return getCategories(context, new HashMap<Pair<String, String>, DashboardTile>());
+ }
+
+ public static List<DashboardCategory> getCategories(Context context,
+ HashMap<Pair<String, String>, DashboardTile> cache) {
+ ArrayList<DashboardTile> tiles = new ArrayList<>();
+ UserManager userManager = UserManager.get(context);
+ for (UserHandle user : userManager.getUserProfiles()) {
+ // TODO: Needs much optimization, too many PM queries going on here.
+ getTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
+ getTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
+ getTilesForAction(context, user, OPERATOR_SETTINGS, cache, OPERATOR_DEFAULT_CATEGORY,
+ tiles, false);
+ getTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
+ MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
+ }
+ HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
+ for (DashboardTile tile : tiles) {
+ DashboardCategory category = categoryMap.get(tile.category);
+ if (category == null) {
+ category = createCategory(context, tile.category);
+ if (category == null) {
+ Log.w(LOG_TAG, "Couldn't find category " + tile.category);
+ continue;
+ }
+ categoryMap.put(category.key, category);
+ }
+ category.addTile(tile);
+ }
+ ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
+ for (DashboardCategory category : categories) {
+ Collections.sort(category.tiles, TILE_COMPARATOR);
+ }
+ Collections.sort(categories, CATEGORY_COMPARATOR);
+ return categories;
+ }
+
+ private static DashboardCategory createCategory(Context context, String categoryKey) {
+ DashboardCategory category = new DashboardCategory();
+ category.key = categoryKey;
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> results = pm.queryIntentActivities(new Intent(categoryKey), 0);
+ if (results.size() == 0) {
+ return null;
+ }
+ for (ResolveInfo resolved : results) {
+ if (!resolved.system) {
+ // Do not allow any app to add to settings, only system ones.
+ continue;
+ }
+ category.title = resolved.activityInfo.loadLabel(pm);
+ category.priority = SETTING_PKG.equals(
+ resolved.activityInfo.applicationInfo.packageName) ? resolved.priority : 0;
+ if (DEBUG) Log.d(LOG_TAG, "Adding category " + category.title);
+ }
+
+ return category;
+ }
+
+ private static void getTilesForAction(Context context,
+ UserHandle user, String action, Map<Pair<String, String>, DashboardTile> addedCache,
+ String defaultCategory, ArrayList<DashboardTile> outTiles, boolean requireSettings) {
+ PackageManager pm = context.getPackageManager();
+ Intent intent = new Intent(action);
+ List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
+ PackageManager.GET_META_DATA, user.getIdentifier());
+ for (ResolveInfo resolved : results) {
+ if (requireSettings) {
+ if (!SETTING_PKG.equals(resolved.activityInfo.applicationInfo.packageName)) {
+ continue;
+ }
+ } else {
+ if (!resolved.system) {
+ // Do not allow any app to add to settings, only system ones.
+ continue;
+ }
+ }
+ ActivityInfo activityInfo = resolved.activityInfo;
+ Bundle metaData = activityInfo.metaData;
+ String categoryKey = defaultCategory;
+ if (((metaData == null) || !metaData.containsKey(EXTRA_CATEGORY_KEY))
+ && categoryKey == null) {
+ Log.w(LOG_TAG, "Found " + resolved.activityInfo.name + " for action "
+ + action + " missing metadata "
+ + (metaData == null ? "" : EXTRA_CATEGORY_KEY));
+ continue;
+ } else {
+ categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
+ }
+ Pair<String, String> key = new Pair<String, String>(activityInfo.packageName,
+ activityInfo.name);
+ DashboardTile tile = addedCache.get(key);
+ if (tile == null) {
+ tile = new DashboardTile();
+ tile.intent = new Intent().setClassName(
+ activityInfo.packageName, activityInfo.name);
+ tile.category = categoryKey;
+ tile.priority = requireSettings ? resolved.priority : 0;
+ updateTileData(context, tile);
+ if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
+
+ addedCache.put(key, tile);
+ }
+ if (!tile.userHandle.contains(user)) {
+ tile.userHandle.add(user);
+ }
+ if (!outTiles.contains(tile)) {
+ outTiles.add(tile);
+ }
+ }
+ }
+
+ private static DashboardCategory getCategory(List<DashboardCategory> target,
+ String categoryKey) {
+ for (DashboardCategory category : target) {
+ if (categoryKey.equals(category.key)) {
+ return category;
+ }
+ }
+ return null;
+ }
+
+ private static boolean updateTileData(Context context, DashboardTile tile) {
+ Intent intent = tile.intent;
+ if (intent != null) {
+ // Find the activity that is in the system image
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> list = tile.userHandle.size() != 0
+ ? pm.queryIntentActivitiesAsUser(intent, PackageManager.GET_META_DATA,
+ tile.userHandle.get(0).getIdentifier())
+ : pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
+ int listSize = list.size();
+ for (int i = 0; i < listSize; i++) {
+ ResolveInfo resolveInfo = list.get(i);
+ if (resolveInfo.activityInfo.applicationInfo.isSystemApp()) {
+ int icon = 0;
+ CharSequence title = null;
+ String summary = null;
+
+ // Get the activity's meta-data
+ try {
+ Resources res = pm.getResourcesForApplication(
+ resolveInfo.activityInfo.packageName);
+ Bundle metaData = resolveInfo.activityInfo.metaData;
+
+ if (res != null && metaData != null) {
+ if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
+ icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
+ }
+ if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+ title = metaData.getString(META_DATA_PREFERENCE_TITLE);
+ }
+ if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
+ summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
+ }
+ }
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
+ if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
+ }
+
+ // Set the preference title to the activity's label if no
+ // meta-data is found
+ if (TextUtils.isEmpty(title)) {
+ title = resolveInfo.loadLabel(pm).toString();
+ }
+ if (icon == 0) {
+ icon = resolveInfo.activityInfo.icon;
+ }
+
+ // Set icon, title and summary for the preference
+ tile.icon = Icon.createWithResource(resolveInfo.activityInfo.packageName, icon);
+ tile.title = title;
+ tile.summary = summary;
+ // Replace the intent with this specific activity
+ tile.intent = new Intent().setClassName(resolveInfo.activityInfo.packageName,
+ resolveInfo.activityInfo.name);
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static final Comparator<DashboardTile> TILE_COMPARATOR =
+ new Comparator<DashboardTile>() {
+ @Override
+ public int compare(DashboardTile lhs, DashboardTile rhs) {
+ return rhs.priority - lhs.priority;
+ }
+ };
+
+ private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
+ new Comparator<DashboardCategory>() {
+ @Override
+ public int compare(DashboardCategory lhs, DashboardCategory rhs) {
+ return rhs.priority - lhs.priority;
+ }
+ };
+}