diff --git a/packages/SystemUI/res/drawable/ic_qs_profiles_off.xml b/packages/SystemUI/res/drawable/ic_qs_profiles_off.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8c0a15c8394cb4913bbb166a068684b53fae45f1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_profiles_off.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/ic_qs_profiles_on.xml b/packages/SystemUI/res/drawable/ic_qs_profiles_on.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4b6646705a5b6e3d3984fc238c616bf70391ec94
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_profiles_on.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/layout/qs_detail_items_list.xml b/packages/SystemUI/res/layout/qs_detail_items_list.xml
new file mode 100644
index 0000000000000000000000000000000000000000..25ff3750f9630e3dc93b77ca79121b53e4b71f76
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_detail_items_list.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/values/cm_strings.xml b/packages/SystemUI/res/values/cm_strings.xml
index c84e725086d89051fcf847c2ad9c06052350f569..6fab9d162d6f30e85abc915493504139ccd7033d 100644
--- a/packages/SystemUI/res/values/cm_strings.xml
+++ b/packages/SystemUI/res/values/cm_strings.xml
@@ -56,6 +56,14 @@
Sync turned off.
Sync turned on.
+
+ Profiles off.
+ Profile: %s.
+ Profiles turned off.
+ Profile changed to %s.
+ Profiles off.
+ System profiles
+
USB tethering
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 25f8ffaa5d137fa09014c3b89cecb0b17453fea8..9da2919acbf89f750ec068f087dcd9a2b518dfa5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -112,7 +112,7 @@
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,nfc,location,hotspot,inversion,saver,work,cast,night,adb_network,ambient_display,caffeine,heads_up,livedisplay,reading_mode,sync,usb_tether,volume_panel
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,nfc,location,hotspot,inversion,saver,work,cast,night,adb_network,ambient_display,caffeine,heads_up,livedisplay,reading_mode,sync,usb_tether,volume_panel,profiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItemsList.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItemsList.java
new file mode 100644
index 0000000000000000000000000000000000000000..882fae30ae30b2564b9d3edc4997fdcbfba04ef3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItemsList.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2017-2018 The LineageOS 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.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import android.widget.TextView;
+import com.android.systemui.R;
+
+/**
+ * Quick settings common detail list view with line items.
+ */
+public class QSDetailItemsList extends FrameLayout {
+ private static final String TAG = "QSDetailItemsList";
+
+ private ListView mListView;
+ private View mEmpty;
+ private TextView mEmptyText;
+ private ImageView mEmptyIcon;
+
+ public QSDetailItemsList(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public static QSDetailItemsList convertOrInflate(Context context,
+ View convertView, ViewGroup parent) {
+ if (convertView instanceof QSDetailItemsList) {
+ return (QSDetailItemsList) convertView;
+ }
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return (QSDetailItemsList) inflater.inflate(R.layout.qs_detail_items_list, parent, false);
+ }
+
+ public void setAdapter(ListAdapter adapter) {
+ mListView.setAdapter(adapter);
+ }
+
+ public ListView getListView() {
+ return mListView;
+ }
+
+ public void setEmptyState(int icon, int text) {
+ mEmptyIcon.setImageResource(icon);
+ mEmptyText.setText(text);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mListView = (ListView) findViewById(android.R.id.list);
+ mListView.setOnTouchListener(new OnTouchListener() {
+ // Setting on Touch Listener for handling the touch inside ScrollView
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ // Disallow the touch request for parent scroll on touch of child view
+ v.getParent().requestDisallowInterceptTouchEvent(true);
+ return false;
+ }
+ });
+ mEmpty = findViewById(android.R.id.empty);
+ mEmpty.setVisibility(GONE);
+ mEmptyText = (TextView) mEmpty.findViewById(android.R.id.title);
+ mEmptyIcon = (ImageView) mEmpty.findViewById(android.R.id.icon);
+ mListView.setEmptyView(mEmpty);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 98453cb33f551726ac5a9a2261b34e5becc207ec..8eb4656ad53acf994ffbca76779dc8cae8c5d378 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -42,6 +42,7 @@ import com.android.systemui.qs.tiles.LiveDisplayTile;
import com.android.systemui.qs.tiles.LocationTile;
import com.android.systemui.qs.tiles.NfcTile;
import com.android.systemui.qs.tiles.NightDisplayTile;
+import com.android.systemui.qs.tiles.ProfilesTile;
import com.android.systemui.qs.tiles.ReadingModeTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.qs.tiles.SyncTile;
@@ -72,6 +73,7 @@ public class QSFactoryImpl implements QSFactory {
else if (tileSpec.equals("rotation")) return new RotationLockTile(mHost);
else if (tileSpec.equals("flashlight")) return new FlashlightTile(mHost);
else if (tileSpec.equals("location")) return new LocationTile(mHost);
+ else if (tileSpec.equals("profiles")) return new ProfilesTile(mHost);
else if (tileSpec.equals("cast")) return new CastTile(mHost);
else if (tileSpec.equals("hotspot")) return new HotspotTile(mHost);
else if (tileSpec.equals("user")) return new UserTile(mHost);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ProfilesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ProfilesTile.java
new file mode 100644
index 0000000000000000000000000000000000000000..357b00151e53b31827aa23e9e44c7367b2c8c578
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ProfilesTile.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod Project
+ * Copyright (C) 2017-2018 The LineageOS 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CheckedTextView;
+import android.widget.ListView;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.DetailAdapter;
+import com.android.systemui.plugins.qs.QSTile.State;
+import com.android.systemui.qs.QSDetailItemsList;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+import lineageos.app.Profile;
+import lineageos.app.ProfileManager;
+import lineageos.providers.LineageSettings;
+import org.lineageos.internal.logging.LineageMetricsLogger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class ProfilesTile extends QSTileImpl {
+
+ private static final Intent PROFILES_SETTINGS =
+ new Intent("org.lineageos.lineageparts.PROFILES_SETTINGS");
+
+ private boolean mListening;
+ private QSDetailItemsList mDetails;
+ private ProfileAdapter mAdapter;
+
+ private final ActivityStarter mActivityStarter;
+ private final KeyguardMonitor mKeyguardMonitor;
+ private final ProfileManager mProfileManager;
+ private final ProfilesObserver mObserver;
+ private final ProfileDetailAdapter mDetailAdapter;
+ private final KeyguardMonitorCallback mCallback = new KeyguardMonitorCallback();
+
+ public ProfilesTile(QSHost host) {
+ super(host);
+ mActivityStarter = Dependency.get(ActivityStarter.class);
+ mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
+ mProfileManager = ProfileManager.getInstance(mContext);
+ mObserver = new ProfilesObserver(mHandler);
+ mDetailAdapter = (ProfileDetailAdapter) createDetailAdapter();
+ }
+
+ @Override
+ public State newTileState() {
+ return new State();
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return mContext.getString(R.string.quick_settings_profiles);
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return PROFILES_SETTINGS;
+ }
+
+ @Override
+ protected void handleClick() {
+ if (mKeyguardMonitor.isSecure() && mKeyguardMonitor.isShowing()) {
+ mActivityStarter.postQSRunnableDismissingKeyguard(() ->
+ showDetail(true));
+ return;
+ }
+ showDetail(true);
+ }
+
+ @Override
+ protected void handleLongClick() {
+ mActivityStarter.postStartActivityDismissingKeyguard(PROFILES_SETTINGS, 0);
+ }
+
+ @Override
+ protected void handleUpdateState(State state, Object arg) {
+ if (profilesEnabled()) {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_profiles_on);
+ state.label = mProfileManager.getActiveProfile().getName();
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_profiles, state.label);
+ } else {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_profiles_off);
+ state.label = mContext.getString(R.string.quick_settings_profiles_off);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_profiles_off);
+ }
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (profilesEnabled()) {
+ return mContext.getString(R.string.accessibility_quick_settings_profiles_changed,
+ mState.label);
+ } else {
+ return mContext.getString(R.string.accessibility_quick_settings_profiles_changed_off);
+ }
+ }
+
+ private boolean profilesEnabled() {
+ return LineageSettings.System.getInt(mContext.getContentResolver(),
+ LineageSettings.System.SYSTEM_PROFILES_ENABLED, 1) == 1;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return LineageMetricsLogger.TILE_PROFILES;
+ }
+
+ @Override
+ public void handleSetListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (listening) {
+ mObserver.startObserving();
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ProfileManager.INTENT_ACTION_PROFILE_SELECTED);
+ filter.addAction(ProfileManager.INTENT_ACTION_PROFILE_UPDATED);
+ mContext.registerReceiver(mReceiver, filter);
+ mKeyguardMonitor.addCallback(mCallback);
+ refreshState();
+ } else {
+ mObserver.endObserving();
+ mContext.unregisterReceiver(mReceiver);
+ mKeyguardMonitor.removeCallback(mCallback);
+ }
+ }
+
+ @Override
+ public DetailAdapter getDetailAdapter() {
+ return mDetailAdapter;
+ }
+
+ @Override
+ protected DetailAdapter createDetailAdapter() {
+ return new ProfileDetailAdapter();
+ }
+
+ private class KeyguardMonitorCallback implements KeyguardMonitor.Callback {
+ @Override
+ public void onKeyguardShowingChanged() {
+ refreshState();
+ }
+ }
+
+ private class ProfileAdapter extends ArrayAdapter {
+ public ProfileAdapter(Context context, List profiles) {
+ super(context, android.R.layout.simple_list_item_single_choice, profiles);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ CheckedTextView label = (CheckedTextView) inflater.inflate(
+ android.R.layout.simple_list_item_single_choice, parent, false);
+
+ Profile p = getItem(position);
+ label.setText(p.getName());
+
+ return label;
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ProfileManager.INTENT_ACTION_PROFILE_SELECTED.equals(intent.getAction())
+ || ProfileManager.INTENT_ACTION_PROFILE_UPDATED.equals(intent.getAction())) {
+ refreshState();
+ }
+ }
+ };
+
+ public class ProfileDetailAdapter implements DetailAdapter, AdapterView.OnItemClickListener {
+
+ private List mProfilesList;
+
+ @Override
+ public CharSequence getTitle() {
+ return mContext.getString(R.string.quick_settings_profiles);
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ boolean enabled = profilesEnabled();
+ return enabled;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return LineageMetricsLogger.TILE_PROFILES_DETAIL;
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ mDetails = QSDetailItemsList.convertOrInflate(context, convertView, parent);
+ mProfilesList = new ArrayList<>();
+ mDetails.setAdapter(mAdapter = new ProfileAdapter(context, mProfilesList));
+
+ final ListView list = mDetails.getListView();
+ list.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
+ list.setOnItemClickListener(this);
+
+ mDetails.setEmptyState(R.drawable.ic_qs_profiles_off,
+ R.string.quick_settings_profiles_off);
+
+ rebuildProfilesList(profilesEnabled());
+
+ return mDetails;
+ }
+
+ private void rebuildProfilesList(boolean populate) {
+ mProfilesList.clear();
+ if (populate) {
+ int selected = -1;
+
+ final Profile[] profiles = mProfileManager.getProfiles();
+ final Profile activeProfile = mProfileManager.getActiveProfile();
+ final UUID activeUuid = activeProfile != null ? activeProfile.getUuid() : null;
+
+ for (int i = 0; i < profiles.length; i++) {
+ mProfilesList.add(profiles[i]);
+ if (activeUuid != null && activeUuid.equals(profiles[i].getUuid())) {
+ selected = i;
+ }
+ }
+ mDetails.getListView().setItemChecked(selected, true);
+ }
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return PROFILES_SETTINGS;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ LineageSettings.System.putInt(mContext.getContentResolver(),
+ LineageSettings.System.SYSTEM_PROFILES_ENABLED, state ? 1 : 0);
+
+ fireToggleStateChanged(state);
+ rebuildProfilesList(state);
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ Profile selected = (Profile) parent.getItemAtPosition(position);
+ mProfileManager.setActiveProfile(selected.getUuid());
+ }
+ }
+
+ private class ProfilesObserver extends ContentObserver {
+ public ProfilesObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ refreshState();
+ }
+
+ public void startObserving() {
+ mContext.getContentResolver().registerContentObserver(
+ LineageSettings.System.getUriFor(LineageSettings.System.SYSTEM_PROFILES_ENABLED),
+ false, this);
+ }
+
+ public void endObserving() {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+ }
+}