am e895d43e: am 632fc874: Merge "QS: Fix some QS layout issues." into lmp-dev

* commit 'e895d43e9640b1e38c86af4e1556c5f88fd07241':
  QS: Fix some QS layout issues.
This commit is contained in:
John Spurlock
2014-08-11 14:40:50 +00:00
committed by Android Git Automerger
12 changed files with 389 additions and 47 deletions

View File

@@ -90,6 +90,14 @@
<!-- The number of columns that the top level tiles span in the QuickSettings -->
<integer name="quick_settings_user_time_settings_tile_span">1</integer>
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
wifi,bt,inversion,cell,airplane,rotation,flashlight,location,cast
</string>
<!-- The tiles to display in QuickSettings -->
<string name="quick_settings_tiles" translatable="false">default</string>
<!-- Whether or not the RSSI tile is capitalized or not. -->
<bool name="quick_settings_rssi_tile_capitalization">true</bool>

View File

@@ -37,6 +37,7 @@ import com.android.systemui.settings.ToggleSlider;
import com.android.systemui.statusbar.phone.QSTileHost;
import java.util.ArrayList;
import java.util.Collection;
/** View that represents the quick settings tile panel. **/
public class QSPanel extends ViewGroup {
@@ -186,12 +187,25 @@ public class QSPanel extends ViewGroup {
v.setVisibility(visible ? VISIBLE : GONE);
}
public void addTile(final QSTile<?> tile) {
public void setTiles(Collection<QSTile<?>> tiles) {
for (TileRecord record : mRecords) {
removeView(record.tileView);
}
mRecords.clear();
for (QSTile<?> tile : tiles) {
addTile(tile);
}
if (isShowingDetail()) {
mDetail.bringToFront();
}
}
private void addTile(final QSTile<?> tile) {
final TileRecord r = new TileRecord();
r.tile = tile;
r.tileView = tile.createTileView(mContext);
r.tileView.setVisibility(View.GONE);
r.tile.setCallback(new QSTile.Callback() {
final QSTile.Callback callback = new QSTile.Callback() {
@Override
public void onStateChanged(QSTile.State state) {
setTileVisibility(r.tileView, state.visible);
@@ -213,7 +227,8 @@ public class QSPanel extends ViewGroup {
fireScanStateChanged(state);
}
}
});
};
r.tile.setCallback(callback);
final View.OnClickListener click = new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -227,6 +242,8 @@ public class QSPanel extends ViewGroup {
}
};
r.tileView.init(click, clickSecondary);
r.tile.setListening(mListening);
callback.onStateChanged(r.tile.getState());
r.tile.refreshState();
mRecords.add(r);

View File

@@ -37,9 +37,8 @@ import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.volume.VolumeComponent;
import java.util.List;
import java.util.Collection;
import java.util.Objects;
/**
@@ -134,6 +133,14 @@ public abstract class QSTile<TState extends State> implements Listenable {
mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
}
public void destroy() {
mHandler.sendEmptyMessage(H.DESTROY);
}
public TState getState() {
return mState;
}
// call only on tile worker looper
private void handleSetCallback(Callback callback) {
@@ -181,6 +188,11 @@ public abstract class QSTile<TState extends State> implements Listenable {
handleRefreshState(null);
}
protected void handleDestroy() {
setListening(false);
mCallback = null;
}
protected final class H extends Handler {
private static final int SET_CALLBACK = 1;
private static final int CLICK = 2;
@@ -190,6 +202,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
private static final int USER_SWITCH = 6;
private static final int TOGGLE_STATE_CHANGED = 7;
private static final int SCAN_STATE_CHANGED = 8;
private static final int DESTROY = 9;
private H(Looper looper) {
super(looper);
@@ -223,6 +236,11 @@ public abstract class QSTile<TState extends State> implements Listenable {
} else if (msg.what == SCAN_STATE_CHANGED) {
name = "handleScanStateChanged";
handleScanStateChanged(msg.arg1 != 0);
} else if (msg.what == DESTROY) {
name = "handleDestroy";
handleDestroy();
} else {
throw new IllegalArgumentException("Unknown msg: " + msg.what);
}
} catch (Throwable t) {
final String error = "Error in " + name;
@@ -245,7 +263,8 @@ public abstract class QSTile<TState extends State> implements Listenable {
void collapsePanels();
Looper getLooper();
Context getContext();
List<QSTile<?>> getTiles();
Collection<QSTile<?>> getTiles();
void setCallback(Callback callback);
BluetoothController getBluetoothController();
LocationController getLocationController();
RotationLockController getRotationLockController();
@@ -255,6 +274,10 @@ public abstract class QSTile<TState extends State> implements Listenable {
CastController getCastController();
FlashlightController getFlashlightController();
KeyguardMonitor getKeyguardMonitor();
public interface Callback {
void onTilesChanged();
}
}
public static class State {

View File

@@ -23,8 +23,9 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.Listenable;
public class UsageTracker {
public class UsageTracker implements Listenable {
private static final long MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
private final Context mContext;
@@ -32,7 +33,7 @@ public class UsageTracker {
private final String mPrefKey;
private final String mResetAction;
private BroadcastReceiver mReceiver;
private boolean mRegistered;
public UsageTracker(Context context, Class<?> tile) {
mContext = context;
@@ -42,17 +43,14 @@ public class UsageTracker {
mResetAction = "com.android.systemui.qs." + tile.getSimpleName() + ".usage_reset";
}
public void listenForReset() {
if (mReceiver != null) {
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mResetAction.equals(intent.getAction())) {
reset();
}
}
};
mContext.registerReceiver(mReceiver, new IntentFilter(mResetAction));
@Override
public void setListening(boolean listen) {
if (listen && !mRegistered) {
mContext.registerReceiver(mReceiver, new IntentFilter(mResetAction));
mRegistered = true;
} else if (!listen && mRegistered) {
mContext.unregisterReceiver(mReceiver);
mRegistered = false;
}
}
@@ -72,4 +70,13 @@ public class UsageTracker {
private SharedPreferences getSharedPrefs() {
return mContext.getSharedPreferences(mContext.getPackageName(), 0);
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mResetAction.equals(intent.getAction())) {
reset();
}
}
};
}

View File

@@ -31,6 +31,8 @@ import com.android.systemui.qs.QSTile;
public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
private final GlobalSetting mSetting;
private boolean mListening;
public AirplaneModeTile(Host host) {
super(host);
@@ -79,6 +81,8 @@ public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
}
public void setListening(boolean listening) {
if (mListening == listening) return;
mListening = listening;
if (listening) {
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);

View File

@@ -41,7 +41,13 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
}
};
mUsageTracker = new UsageTracker(host.getContext(), ColorInversionTile.class);
mUsageTracker.listenForReset();
mUsageTracker.setListening(true);
}
@Override
protected void handleDestroy() {
super.handleDestroy();
mUsageTracker.setListening(false);
}
@Override

View File

@@ -16,18 +16,13 @@
package com.android.systemui.qs.tiles;
import android.app.ActivityManager;
import android.os.SystemClock;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.SecureSetting;
import com.android.systemui.statusbar.policy.FlashlightController;
import android.app.ActivityManager;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.provider.Settings.Secure;
import android.util.Log;
/** Quick settings tile: Control flashlight **/
public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
FlashlightController.FlashlightListener {
@@ -45,6 +40,12 @@ public class FlashlightTile extends QSTile<QSTile.BooleanState> implements
mFlashlightController.addListener(this);
}
@Override
protected void handleDestroy() {
super.handleDestroy();
mFlashlightController.removeListener(this);
}
@Override
protected BooleanState newTileState() {
return new BooleanState();

View File

@@ -35,7 +35,13 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
super(host);
mController = host.getHotspotController();
mUsageTracker = new UsageTracker(host.getContext(), HotspotTile.class);
mUsageTracker.listenForReset();
mUsageTracker.setListening(true);
}
@Override
protected void handleDestroy() {
super.handleDestroy();
mUsageTracker.setListening(false);
}
@Override

View File

@@ -0,0 +1,139 @@
/*
* Copyright (C) 2014 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.systemui.qs.tiles;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import com.android.systemui.qs.QSTile;
public class IntentTile extends QSTile<QSTile.State> {
public static final String PREFIX = "intent(";
private PendingIntent mOnClick;
private String mOnClickUri;
private int mCurrentUserId;
private IntentTile(Host host, String action) {
super(host);
mContext.registerReceiver(mReceiver, new IntentFilter(action));
}
@Override
protected void handleDestroy() {
super.handleDestroy();
mContext.unregisterReceiver(mReceiver);
}
public static QSTile<?> create(Host host, String spec) {
if (spec == null || !spec.startsWith(PREFIX) || !spec.endsWith(")")) {
throw new IllegalArgumentException("Bad intent tile spec: " + spec);
}
final String action = spec.substring(PREFIX.length(), spec.length() - 1);
if (action.isEmpty()) {
throw new IllegalArgumentException("Empty intent tile spec action");
}
return new IntentTile(host, action);
}
@Override
public void setListening(boolean listening) {
}
@Override
protected State newTileState() {
return new State();
}
@Override
protected void handleUserSwitch(int newUserId) {
super.handleUserSwitch(newUserId);
mCurrentUserId = newUserId;
}
@Override
protected void handleClick() {
try {
if (mOnClick != null) {
mOnClick.send();
} else if (mOnClickUri != null) {
final Intent intent = Intent.parseUri(mOnClickUri, Intent.URI_INTENT_SCHEME);
mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
}
} catch (Throwable t) {
Log.w(TAG, "Error sending click intent", t);
}
}
@Override
protected void handleUpdateState(State state, Object arg) {
if (!(arg instanceof Intent)) return;
final Intent intent = (Intent) arg;
state.visible = intent.getBooleanExtra("visible", true);
state.contentDescription = intent.getStringExtra("contentDescription");
state.label = intent.getStringExtra("label");
state.iconId = 0;
state.icon = null;
final byte[] iconBitmap = intent.getByteArrayExtra("iconBitmap");
if (iconBitmap != null) {
try {
final Bitmap b = BitmapFactory.decodeByteArray(iconBitmap, 0, iconBitmap.length);
state.icon = new BitmapDrawable(mContext.getResources(), b);
} catch (Throwable t) {
Log.w(TAG, "Error loading icon bitmap, length " + iconBitmap.length, t);
}
} else {
final int iconId = intent.getIntExtra("iconId", 0);
if (iconId != 0) {
final String iconPackage = intent.getStringExtra("iconPackage");
if (!TextUtils.isEmpty(iconPackage)) {
state.icon = getPackageDrawable(iconPackage, iconId);
} else {
state.iconId = iconId;
}
}
}
mOnClick = intent.getParcelableExtra("onClick");
mOnClickUri = intent.getStringExtra("onClickUri");
}
private Drawable getPackageDrawable(String pkg, int id) {
try {
return mContext.createPackageContext(pkg, 0).getDrawable(id);
} catch (Throwable t) {
Log.w(TAG, "Error loading package drawable pkg=" + pkg + " id=" + id, t);
return null;
}
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
refreshState(intent);
}
};
}

View File

@@ -197,6 +197,20 @@ public class NotificationPanelView extends PanelView implements
mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
mSecureCameraLaunchManager =
new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea);
// recompute internal state when qspanel height changes
mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight,
int oldBottom) {
final int height = bottom - top;
final int oldHeight = oldBottom - oldTop;
if (height != oldHeight) {
onScrollChanged();
}
}
});
}
@Override

View File

@@ -818,10 +818,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mUserSwitcherController, mKeyguardMonitor,
mSecurityController);
mQSPanel.setHost(qsh);
for (QSTile<?> tile : qsh.getTiles()) {
mQSPanel.addTile(tile);
}
mQSPanel.setTiles(qsh.getTiles());
mHeader.setQSPanel(mQSPanel);
qsh.setCallback(new QSTileHost.Callback() {
@Override
public void onTilesChanged() {
mQSPanel.setTiles(qsh.getTiles());
}
});
}
mBackdrop = (FrameLayout) mStatusBarWindow.findViewById(R.id.backdrop);

View File

@@ -18,9 +18,16 @@ package com.android.systemui.statusbar.phone;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.provider.Settings.Secure;
import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.tiles.AirplaneModeTile;
import com.android.systemui.qs.tiles.BluetoothTile;
@@ -29,6 +36,7 @@ import com.android.systemui.qs.tiles.CellularTile;
import com.android.systemui.qs.tiles.ColorInversionTile;
import com.android.systemui.qs.tiles.FlashlightTile;
import com.android.systemui.qs.tiles.HotspotTile;
import com.android.systemui.qs.tiles.IntentTile;
import com.android.systemui.qs.tiles.LocationTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.qs.tiles.WifiTile;
@@ -46,13 +54,23 @@ import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/** Platform implementation of the quick settings tile host **/
public class QSTileHost implements QSTile.Host {
private static final String TAG = "QSTileHost";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final String TILES_SETTING = "sysui_qs_tiles";
private final Context mContext;
private final PhoneStatusBar mStatusBar;
private final LinkedHashMap<String, QSTile<?>> mTiles = new LinkedHashMap<>();
private final Observer mObserver = new Observer();
private final BluetoothController mBluetooth;
private final LocationController mLocation;
private final RotationLockController mRotation;
@@ -62,12 +80,13 @@ public class QSTileHost implements QSTile.Host {
private final CastController mCast;
private final Looper mLooper;
private final CurrentUserTracker mUserTracker;
private final ArrayList<QSTile<?>> mTiles = new ArrayList<QSTile<?>>();
private final FlashlightController mFlashlight;
private final UserSwitcherController mUserSwitcherController;
private final KeyguardMonitor mKeyguard;
private final SecurityController mSecurity;
private Callback mCallback;
public QSTileHost(Context context, PhoneStatusBar statusBar,
BluetoothController bluetooth, LocationController location,
RotationLockController rotation, NetworkController network,
@@ -93,31 +112,30 @@ public class QSTileHost implements QSTile.Host {
ht.start();
mLooper = ht.getLooper();
mTiles.add(new WifiTile(this));
mTiles.add(new BluetoothTile(this));
mTiles.add(new ColorInversionTile(this));
mTiles.add(new CellularTile(this));
mTiles.add(new AirplaneModeTile(this));
mTiles.add(new RotationLockTile(this));
mTiles.add(new FlashlightTile(this));
mTiles.add(new LocationTile(this));
mTiles.add(new CastTile(this));
mTiles.add(new HotspotTile(this));
mUserTracker = new CurrentUserTracker(mContext) {
@Override
public void onUserSwitched(int newUserId) {
for (QSTile<?> tile : mTiles) {
recreateTiles();
for (QSTile<?> tile : mTiles.values()) {
tile.userSwitch(newUserId);
}
mObserver.register();
}
};
recreateTiles();
mUserTracker.startTracking();
mObserver.register();
}
@Override
public List<QSTile<?>> getTiles() {
return mTiles;
public void setCallback(Callback callback) {
mCallback = callback;
}
@Override
public Collection<QSTile<?>> getTiles() {
return mTiles.values();
}
@Override
@@ -197,4 +215,99 @@ public class QSTileHost implements QSTile.Host {
public SecurityController getSecurityController() {
return mSecurity;
}
private void recreateTiles() {
if (DEBUG) Log.d(TAG, "Recreating tiles");
final List<String> tileSpecs = loadTileSpecs();
for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
if (!tileSpecs.contains(tile.getKey())) {
if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
tile.getValue().destroy();
}
}
final LinkedHashMap<String, QSTile<?>> newTiles = new LinkedHashMap<>();
for (String tileSpec : tileSpecs) {
if (mTiles.containsKey(tileSpec)) {
newTiles.put(tileSpec, mTiles.get(tileSpec));
} else {
if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
try {
newTiles.put(tileSpec, createTile(tileSpec));
} catch (Throwable t) {
Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
}
}
}
if (mTiles.equals(newTiles)) return;
mTiles.clear();
mTiles.putAll(newTiles);
if (mCallback != null) {
mCallback.onTilesChanged();
}
}
private QSTile<?> createTile(String tileSpec) {
if (tileSpec.equals("wifi")) return new WifiTile(this);
else if (tileSpec.equals("bt")) return new BluetoothTile(this);
else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
else if (tileSpec.equals("cell")) return new CellularTile(this);
else if (tileSpec.equals("airplane")) return new AirplaneModeTile(this);
else if (tileSpec.equals("rotation")) return new RotationLockTile(this);
else if (tileSpec.equals("flashlight")) return new FlashlightTile(this);
else if (tileSpec.equals("location")) return new LocationTile(this);
else if (tileSpec.equals("cast")) return new CastTile(this);
else if (tileSpec.equals("hotspot")) return new HotspotTile(this);
else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
}
private List<String> loadTileSpecs() {
final Resources res = mContext.getResources();
final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
String tileList = Secure.getStringForUser(mContext.getContentResolver(), TILES_SETTING,
mUserTracker.getCurrentUserId());
if (tileList == null) {
tileList = res.getString(R.string.quick_settings_tiles);
if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
} else {
if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
}
final ArrayList<String> tiles = new ArrayList<String>();
boolean addedDefault = false;
for (String tile : tileList.split(",")) {
tile = tile.trim();
if (tile.isEmpty()) continue;
if (tile.equals("default")) {
if (!addedDefault) {
tiles.addAll(Arrays.asList(defaultTileList.split(",")));
addedDefault = true;
}
} else {
tiles.add(tile);
}
}
return tiles;
}
private class Observer extends ContentObserver {
private boolean mRegistered;
public Observer() {
super(new Handler(Looper.getMainLooper()));
}
public void register() {
if (mRegistered) {
mContext.getContentResolver().unregisterContentObserver(this);
}
mContext.getContentResolver().registerContentObserver(Secure.getUriFor(TILES_SETTING),
false, this, mUserTracker.getCurrentUserId());
mRegistered = true;
}
@Override
public void onChange(boolean selfChange, Uri uri) {
recreateTiles();
}
}
}