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); + } + } +}