Merge "Implement new multi-user affordance."

This commit is contained in:
Jorim Jaggi
2014-05-11 13:40:14 +00:00
committed by Android (Google) Code Review
7 changed files with 344 additions and 4 deletions

View File

@@ -25,7 +25,7 @@
android:orientation="horizontal"
android:gravity="center_vertical"
android:baselineAligned="false"
android:elevation="14dp"
android:elevation="10dp"
>
<View
@@ -74,12 +74,22 @@
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium" />
<com.android.systemui.statusbar.phone.MultiUserSwitch android:id="@+id/multi_user_switch"
android:layout_width="40dp"
android:layout_height="@dimen/status_bar_header_height"
android:layout_alignParentEnd="true"
android:background="@null"
android:scaleType="centerInside"
android:padding="6dp"
/>
<FrameLayout android:id="@+id/system_icons_container"
android:layout_width="wrap_content"
android:layout_height="@dimen/status_bar_header_height"
android:layout_alignParentEnd="true"
android:layout_marginEnd="16dp"
android:layout_toStartOf="@id/multi_user_switch"
android:layout_marginEnd="4dp"
/>
<TextView
android:id="@+id/header_debug_info"
android:visibility="invisible"

View File

@@ -22,7 +22,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#dd000000">
android:background="#dd000000"
android:elevation="12dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -0,0 +1,90 @@
/*
* 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.statusbar.phone;
import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import com.android.systemui.R;
import com.android.systemui.settings.UserSwitcherHostView;
import com.android.systemui.statusbar.policy.UserInfoController;
/**
* Image button for the multi user switcher.
*/
public class MultiUserSwitch extends ImageButton implements View.OnClickListener,
UserInfoController.OnUserInfoChangedListener {
private ViewGroup mOverlayParent;
public MultiUserSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
setOnClickListener(this);
}
public void setOverlayParent(ViewGroup parent) {
mOverlayParent = parent;
}
@Override
public void onClick(View v) {
final UserManager um = UserManager.get(getContext());
if (um.isUserSwitcherEnabled()) {
final UserSwitcherHostView switcher =
(UserSwitcherHostView) LayoutInflater.from(getContext()).inflate(
R.layout.user_switcher_host, mOverlayParent, false);
switcher.setFinishRunnable(new Runnable() {
@Override
public void run() {
mOverlayParent.removeView(switcher);
}
});
switcher.refreshUsers();
mOverlayParent.addView(switcher);
} else {
Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
getContext(), v, ContactsContract.Profile.CONTENT_URI,
ContactsContract.QuickContact.MODE_LARGE, null);
getContext().startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
}
}
public void setUserInfoController(UserInfoController userInfoController) {
userInfoController.addListener(this);
}
@Override
public void onUserInfoChanged(String name, Drawable picture) {
setImageDrawable(picture);
}
}

View File

@@ -95,6 +95,7 @@ public class NotificationPanelView extends PanelView implements
super.onFinishInflate();
mHeader = (StatusBarHeaderView) findViewById(R.id.header);
mHeader.getBackgroundView().setOnClickListener(this);
mHeader.setOverlayParent(this);
mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
mStackScrollerContainer = findViewById(R.id.notification_container_parent);
mQsContainer = (QuickSettingsContainerView) findViewById(R.id.quick_settings_container);

View File

@@ -111,6 +111,7 @@ import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
@@ -184,6 +185,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
LocationController mLocationController;
NetworkController mNetworkController;
RotationLockController mRotationLockController;
UserInfoController mUserInfoController;
int mNaturalBarHeight = -1;
int mIconSize = -1;
@@ -670,6 +672,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
|| QuickSettings.DEBUG_GONE_TILES) {
mRotationLockController = new RotationLockController(mContext);
}
mUserInfoController = new UserInfoController(mContext);
final SignalClusterView signalCluster =
(SignalClusterView)mStatusBarView.findViewById(R.id.signal_cluster);
@@ -737,6 +740,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mQS = null; // fly away, be free
}
// User info. Trigger first load.
mHeader.setUserInfoController(mUserInfoController);
mUserInfoController.reloadUserInfo();
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mBroadcastReceiver.onReceive(mContext,
new Intent(pm.isScreenOn() ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF));

View File

@@ -24,6 +24,7 @@ import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.UserInfoController;
/**
* The view to manage the header area in the expanded status bar.
@@ -35,6 +36,7 @@ public class StatusBarHeaderView extends RelativeLayout {
private ViewGroup mSystemIconsContainer;
private View mDateTime;
private View mKeyguardCarrierText;
private MultiUserSwitch mMultiUserSwitch;
private int mCollapsedHeight;
private int mExpandedHeight;
@@ -53,6 +55,7 @@ public class StatusBarHeaderView extends RelativeLayout {
mSystemIconsContainer = (ViewGroup) findViewById(R.id.system_icons_container);
mDateTime = findViewById(R.id.datetime);
mKeyguardCarrierText = findViewById(R.id.keyguard_carrier_text);
mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch);
loadDimens();
}
@@ -97,6 +100,11 @@ public class StatusBarHeaderView extends RelativeLayout {
lp.height = systemIconsContainerHeight;
mSystemIconsContainer.setLayoutParams(lp);
}
lp = mMultiUserSwitch.getLayoutParams();
if (lp.height != systemIconsContainerHeight) {
lp.height = systemIconsContainerHeight;
mMultiUserSwitch.setLayoutParams(lp);
}
}
public void setExpansion(float height) {
@@ -133,4 +141,12 @@ public class StatusBarHeaderView extends RelativeLayout {
}
updateHeights();
}
public void setUserInfoController(UserInfoController userInfoController) {
mMultiUserSwitch.setUserInfoController(userInfoController);
}
public void setOverlayParent(ViewGroup parent) {
mMultiUserSwitch.setOverlayParent(parent);
}
}

View File

@@ -0,0 +1,215 @@
/*
* 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.statusbar.policy;
import android.app.ActivityManagerNative;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.os.AsyncTask;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.ContactsContract;
import android.security.KeyChain;
import android.util.Log;
import android.util.Pair;
import com.android.internal.view.RotationPolicy;
import com.android.systemui.R;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
public final class UserInfoController {
private static final String TAG = "UserInfoController";
private final Context mContext;
private final ArrayList<OnUserInfoChangedListener> mCallbacks =
new ArrayList<OnUserInfoChangedListener>();
private AsyncTask<Void, Void, Pair<String, Drawable>> mUserInfoTask;
private boolean mUseDefaultAvatar;
private String mUserName;
private Drawable mUserDrawable;
public UserInfoController(Context context) {
mContext = context;
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
mContext.registerReceiver(mReceiver, filter);
IntentFilter profileFilter = new IntentFilter();
profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
profileFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
mContext.registerReceiverAsUser(mProfileReceiver, UserHandle.ALL, profileFilter,
null, null);
}
public void addListener(OnUserInfoChangedListener callback) {
mCallbacks.add(callback);
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_USER_SWITCHED.equals(action)) {
reloadUserInfo();
} else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
if (mUseDefaultAvatar) {
reloadUserInfo();
}
}
}
};
private final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (ContactsContract.Intents.ACTION_PROFILE_CHANGED.equals(action) ||
Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
try {
final int currentUser = ActivityManagerNative.getDefault().getCurrentUser().id;
final int changedUser =
intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
if (changedUser == currentUser) {
reloadUserInfo();
}
} catch (RemoteException e) {
Log.e(TAG, "Couldn't get current user id for profile change", e);
}
}
}
};
public void reloadUserInfo() {
if (mUserInfoTask != null) {
mUserInfoTask.cancel(false);
mUserInfoTask = null;
}
queryForUserInformation();
}
private Bitmap circularClip(Bitmap input) {
Bitmap output = Bitmap.createBitmap(input.getWidth(),
input.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final Paint paint = new Paint();
paint.setShader(new BitmapShader(input, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
paint.setAntiAlias(true);
canvas.drawCircle(input.getWidth() / 2, input.getHeight() / 2, input.getWidth() / 2, paint);
return output;
}
private void queryForUserInformation() {
Context currentUserContext;
UserInfo userInfo;
try {
userInfo = ActivityManagerNative.getDefault().getCurrentUser();
currentUserContext = mContext.createPackageContextAsUser("android", 0,
new UserHandle(userInfo.id));
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Couldn't create user context", e);
throw new RuntimeException(e);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't get user info", e);
throw new RuntimeException(e);
}
final int userId = userInfo.id;
final String userName = userInfo.name;
final Context context = currentUserContext;
mUserInfoTask = new AsyncTask<Void, Void, Pair<String, Drawable>>() {
@Override
protected Pair<String, Drawable> doInBackground(Void... params) {
final UserManager um = UserManager.get(mContext);
// Fall back to the UserManager nickname if we can't read the name from the local
// profile below.
String name = userName;
Drawable avatar = null;
Bitmap rawAvatar = um.getUserIcon(userId);
if (rawAvatar != null) {
avatar = new BitmapDrawable(mContext.getResources(), circularClip(rawAvatar));
} else {
avatar = mContext.getResources().getDrawable(R.drawable.ic_qs_default_user);
mUseDefaultAvatar = true;
}
// If it's a single-user device, get the profile name, since the nickname is not
// usually valid
if (um.getUsers().size() <= 1) {
// Try and read the display name from the local profile
final Cursor cursor = context.getContentResolver().query(
ContactsContract.Profile.CONTENT_URI, new String[] {
ContactsContract.CommonDataKinds.Phone._ID, ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME},
null, null, null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
name = cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
}
} finally {
cursor.close();
}
}
}
return new Pair<String, Drawable>(name, avatar);
}
@Override
protected void onPostExecute(Pair<String, Drawable> result) {
mUserName = result.first;
mUserDrawable = result.second;
mUserInfoTask = null;
notifyChanged();
}
};
mUserInfoTask.execute();
}
private void notifyChanged() {
for (OnUserInfoChangedListener listener : mCallbacks) {
listener.onUserInfoChanged(mUserName, mUserDrawable);
}
}
public interface OnUserInfoChangedListener {
public void onUserInfoChanged(String name, Drawable picture);
}
}