Commit cc0c2d39 authored by Roman Birg's avatar Roman Birg Committed by Michael W

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
parent 32a5b165
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M19,5v14H5V5H19
M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3L19,3z
M17,8 c0-0.6-0.4-1-1-1c-0.6,0-1,0.4-1,1s0.4,1,1,1C16.6,9,17,8.6,17,8z
M13,8c0-0.6-0.4-1-1-1c-0.6,0-1,0.4-1,1s0.4,1,1,1 C12.6,9,13,8.6,13,8z
M9,8c0-0.6-0.4-1-1-1S7,7.4,7,8s0.4,1,1,1S9,8.6,9,8z
M17,13h-6v-2H9v2H7v2h2v2h2v-2h6V13z" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M19,5v14H5V5H19
M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3L19,3z
M17,8 c0-0.6-0.4-1-1-1c-0.6,0-1,0.4-1,1s0.4,1,1,1C16.6,9,17,8.6,17,8z
M13,8c0-0.6-0.4-1-1-1c-0.6,0-1,0.4-1,1s0.4,1,1,1 C12.6,9,13,8.6,13,8z
M9,8c0-0.6-0.4-1-1-1S7,7.4,7,8s0.4,1,1,1S9,8.6,9,8z
M17,13h-6v-2H9v2H7v2h2v2h2v-2h6V13z" />
</vector>
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015 The CyanogenMod 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.
-->
<!-- extends LinearLayout -->
<com.android.systemui.qs.QSDetailItemsList xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="100dp"
android:paddingTop="16dp"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<ListView
android:id="@android:id/list"
android:divider="@null"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<LinearLayout
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical" >
<ImageView
android:id="@android:id/icon"
android:layout_width="56dp"
android:layout_height="56dp" />
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textAppearance="@style/TextAppearance.QS.DetailEmpty" />
</LinearLayout>
</com.android.systemui.qs.QSDetailItemsList>
......@@ -56,6 +56,14 @@
<string name="accessibility_quick_settings_sync_changed_off">Sync turned off.</string>
<string name="accessibility_quick_settings_sync_changed_on">Sync turned on.</string>
<!-- System Profiles QS tile -->
<string name="accessibility_quick_settings_profiles_off">Profiles off.</string>
<string name="accessibility_quick_settings_profiles">Profile: <xliff:g id="profile" example="Default">%s</xliff:g>.</string>
<string name="accessibility_quick_settings_profiles_changed_off">Profiles turned off.</string>
<string name="accessibility_quick_settings_profiles_changed">Profile changed to <xliff:g id="profile" example="Default">%s</xliff:g>.</string>
<string name="quick_settings_profiles_off">Profiles off.</string>
<string name="quick_settings_profiles">System profiles</string>
<!-- USB tethering QS tile -->
<string name="quick_settings_usb_tether_label">USB tethering</string>
......
......@@ -112,7 +112,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
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
</string>
<!-- The tiles to display in QuickSettings -->
......
/*
* 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);
}
}
......@@ -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);
......
/*
* 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<State> {
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<Profile> {
public ProfileAdapter(Context context, List<Profile> 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<Profile> 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);
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment