From cc0c2d39907d98d7ef921efda78a2fd25070095f Mon Sep 17 00:00:00 2001 From: Roman Birg Date: Thu, 22 Jan 2015 11:21:56 -0800 Subject: [PATCH] System Profiles in QS Tiles Contains also commit by Joey Rizzoli: SystemUI: Profiles tile should require authentication A profile with the option to disable secure lockscreen could be used to bypass the lockscreen security. Require the user to unlock their device when tapping on the profile quick tile. BUGBASH-1095 Change-Id: I2a438af301212241533b969bf2c6c8390ef09cbc --- .../res/drawable/ic_qs_profiles_off.xml | 32 ++ .../res/drawable/ic_qs_profiles_on.xml | 32 ++ .../res/layout/qs_detail_items_list.xml | 55 ++++ packages/SystemUI/res/values/cm_strings.xml | 8 + packages/SystemUI/res/values/config.xml | 2 +- .../systemui/qs/QSDetailItemsList.java | 90 +++++ .../systemui/qs/tileimpl/QSFactoryImpl.java | 2 + .../systemui/qs/tiles/ProfilesTile.java | 307 ++++++++++++++++++ 8 files changed, 527 insertions(+), 1 deletion(-) create mode 100644 packages/SystemUI/res/drawable/ic_qs_profiles_off.xml create mode 100644 packages/SystemUI/res/drawable/ic_qs_profiles_on.xml create mode 100644 packages/SystemUI/res/layout/qs_detail_items_list.xml create mode 100644 packages/SystemUI/src/com/android/systemui/qs/QSDetailItemsList.java create mode 100644 packages/SystemUI/src/com/android/systemui/qs/tiles/ProfilesTile.java 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 000000000000..8c0a15c8394c --- /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 000000000000..4b6646705a5b --- /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 000000000000..25ff3750f963 --- /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 c84e725086d8..6fab9d162d6f 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 25f8ffaa5d13..9da2919acbf8 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 000000000000..882fae30ae30 --- /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 98453cb33f55..8eb4656ad53a 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 000000000000..357b00151e53 --- /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); + } + } +} -- GitLab