diff options
21 files changed, 781 insertions, 47 deletions
diff --git a/core/res/res/values/cm_strings.xml b/core/res/res/values/cm_strings.xml index ed573a8..6e2cabf 100644 --- a/core/res/res/values/cm_strings.xml +++ b/core/res/res/values/cm_strings.xml @@ -91,8 +91,18 @@ <!-- ADB notification message--> <string name="adb_active_generic_notification_message">Touch to disable debugging.</string> + <!-- ADB custom tile --> + <string name="adb_active_custom_tile">ADB - <xliff:g id="adb_type" example="Usb">%1$s</xliff:g></string> + <string name="adb_active_custom_tile_both">Both</string> + <string name="adb_active_custom_tile_usb">USB</string> + <string name="adb_active_custom_tile_net">Network</string> + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_accessThemeService">access theme service</string> + + <!-- Title of an application permission, listed so the user can choose whether they want the application to do this. --> + <string name="permlab_interceptPackageLaunch">intercept app launch</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_accessThemeService">Allows an app to access the theme service. Should never be needed for normal apps.</string> diff --git a/core/res/res/values/cm_symbols.xml b/core/res/res/values/cm_symbols.xml index 293b951..bd38ffb 100644 --- a/core/res/res/values/cm_symbols.xml +++ b/core/res/res/values/cm_symbols.xml @@ -39,6 +39,12 @@ <java-symbol type="array" name="notification_light_package_mapping" /> <java-symbol type="array" name="config_notificationNoAlertsVibePattern" /> + <!-- ADB custom tile --> + <java-symbol type="string" name="adb_active_custom_tile" /> + <java-symbol type="string" name="adb_active_custom_tile_both" /> + <java-symbol type="string" name="adb_active_custom_tile_usb" /> + <java-symbol type="string" name="adb_active_custom_tile_net" /> + <!-- Package Manager --> <java-symbol type="array" name="config_disabledComponents" /> <java-symbol type="array" name="config_forceEnabledComponents" /> diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk index 9ae27e9..d9c9a3a 100644 --- a/packages/SystemUI/Android.mk +++ b/packages/SystemUI/Android.mk @@ -7,7 +7,6 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) \ src/com/android/systemui/EventLogTags.logtags LOCAL_STATIC_JAVA_LIBRARIES := Keyguard \ - org.cyanogenmod.platform.sdk \ org.cyanogenmod.platform.internal \ android-support-v7-palette \ android-support-v4 diff --git a/packages/SystemUI/AndroidManifest_cm.xml b/packages/SystemUI/AndroidManifest_cm.xml index b869a0b..30dac81 100644 --- a/packages/SystemUI/AndroidManifest_cm.xml +++ b/packages/SystemUI/AndroidManifest_cm.xml @@ -32,6 +32,9 @@ <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" /> + <!-- Publish dynamic tiles --> + <uses-permission android:name="cyanogenmod.permission.PUBLISH_CUSTOM_TILE" /> + <application> <provider android:name=".cm.SpamMessageProvider" android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" diff --git a/packages/SystemUI/res/drawable/ic_dynamic_qs_adb.xml b/packages/SystemUI/res/drawable/ic_dynamic_qs_adb.xml new file mode 100644 index 0000000..6b33a66 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_dynamic_qs_adb.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2015 The CyanogenMod 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="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + + <path + android:fillColor="#FFFFFF" + android:pathData="M 37.727707,9.975208 L 40.285946,3.7258741 C 40.541238,3.1009409 +40.243401,2.3882503 39.618464,2.1329586 38.993529,1.8776669 38.28084,2.1755027 +38.025548,2.8004407 L 35.374234,9.275815 C 32.449014,8.605674 28.718028,8.350381 +24.258396,8.350381 l 0,0.01596 0,0 0,-0.01862 c -0.582384,0 -1.148814,0.0053 +-1.707266,0.01596 l -0.0027,0 c -3.712371,0.06116 -6.863631,0.329751 +-9.405913,0.912136 L 10.491243,2.8004407 C 10.235951,2.1755075 +9.5232609,1.8776669 8.8983273,2.1329586 8.2733938,2.3882503 7.9755532,3.1009409 +8.2308452,3.7258741 L 10.789084,9.975208 c -3.3959157,1.284439 +-5.1989153,3.45708 -5.1989153,6.94607 l 0,7.31837 0,1.898735 0,2.084884 c +0,6.411552 6.0738233,8.379428 16.9583033,8.557599 l 0,0.01064 0.67546,0 c +0.340389,0.0027 0.686097,0.0053 1.034464,0.0053 l 0,-0.0053 0,0 0,0.0053 c +0.348368,0 0.694075,-0.0027 1.034465,-0.0053 l 0.04786,0 c 11.280713,-0.11169 +17.585893,-2.031699 17.585893,-8.565578 l 0,-2.087543 0,-1.898735 0,-7.31837 C +42.929314,13.429631 41.123615,11.259649 37.7277,9.975211 Z M 14.661013,27.973293 +c -3.084777,0 -5.5845131,-2.499733 -5.5845131,-5.584512 0,-3.084778 +2.4997361,-5.584511 5.5845131,-5.584511 3.084778,0 5.584512,2.499733 +5.584512,5.584511 0,3.084779 -2.499734,5.584512 -5.584512,5.584512 z m +10.185086,4.050102 -1.178065,0 c -0.86693,0 -1.571642,-0.702054 +-1.571642,-1.571642 0,-0.03192 0.008,-0.06382 0.01064,-0.09573 l 4.300073,0 c +0.0027,0.03192 0.01064,0.06116 0.01064,0.09573 0,0.869588 -0.704712,1.571642 +-1.571641,1.571642 z m 9.00968,-4.050102 c -3.084778,0 -5.584512,-2.499733 +-5.584512,-5.584512 0,-3.084778 2.499734,-5.584511 5.584512,-5.584511 3.084777,0 +5.584512,2.499733 5.584512,5.584511 -0.0027,3.084779 -2.502394,5.584512 +-5.584512,5.584512 z" /> + <path + android:fillColor="#FFFFFF" + android:strokeWidth="16" + android:strokeLineJoin="round" + android:strokeLineCap="round" + android:pathData="M 23.2448199 33.152538 L 24.8907709 33.152538 Q 25.9999988 33.152538 +25.9999988 34.2617659 L 25.9999988 44.6534791 Q 25.9999988 45.762707 24.8907709 45.762707 +L 23.2448199 45.762707 Q 22.135592 45.762707 22.135592 44.6534791 L 22.135592 34.2617659 +Q 22.135592 33.152538 23.2448199 33.152538 Z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_dynamic_qs_ime_selector.xml b/packages/SystemUI/res/drawable/ic_dynamic_qs_ime_selector.xml new file mode 100644 index 0000000..07ee10a --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_dynamic_qs_ime_selector.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2015 The CyanogenMod 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="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + + <path + android:fillColor="#FFFFFF" + android:pathData="M40 10H8c-2.21 0-3.98 1.79-3.98 4L4 34c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 +4-4V14c0-2.21-1.79-4-4-4zm-18 6h4v4h-4v-4zm0 6h4v4h-4v-4zm-6-6h4v4h-4v-4zm0 +6h4v4h-4v-4zm-2 4h-4v-4h4v4zm0-6h-4v-4h4v4zm18 +14H16v-4h16v4zm0-8h-4v-4h4v4zm0-6h-4v-4h4v4zm6 6h-4v-4h4v4zm0-6h-4v-4h4v4z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_dynamic_qs_next_alarm.xml b/packages/SystemUI/res/drawable/ic_dynamic_qs_next_alarm.xml new file mode 100644 index 0000000..184d407 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_dynamic_qs_next_alarm.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2015 The CyanogenMod 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="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + + <path + android:fillColor="#FFFFFF" + android:pathData="M44 11.44l-9.19-7.71-2.57 3.06 9.19 7.71L44 11.44zM15.76 6.78l-2.57-3.06L4 +11.43l2.57 3.06 9.19-7.71zM25 16h-3v12l9.49 5.71L33 +31.24l-8-4.74V16zm-1.01-8C14.04 8 6 16.06 6 26s8.04 18 17.99 18S42 35.94 42 26 +33.94 8 23.99 8zM24 40c-7.73 0-14-6.27-14-14s6.27-14 14-14 14 6.27 14 14-6.26 +14-14 14z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_dynamic_qs_su.xml b/packages/SystemUI/res/drawable/ic_dynamic_qs_su.xml new file mode 100644 index 0000000..5d582d2 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_dynamic_qs_su.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2015 The CyanogenMod 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="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + + <path + android:fillColor="#FFFFFF" + android:strokeWidth="1" + android:pathData="M 28.79,43.78 L 22.26,43.78 31.33,3.52 37.88,3.52 Z" /> + <path + android:fillColor="#FFFFFF" + android:strokeWidth="1" + android:pathData="M 15.46,43.78 L 8.54,43.78 17.69,3.52 24.41,3.52 Z" /> + <path + android:fillColor="#FFFFFF" + android:strokeWidth="1" + android:pathData="M 7.53,15.86 L 43.53,15.86 43.53,20.34 7.53,20.34 Z" /> + <path + android:fillColor="#FFFFFF" + android:strokeWidth="1" + android:pathData="M 4.68,28.68 L 41.69,28.68 41.69,33.15 4.68,33.15 Z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/cm_arrays.xml b/packages/SystemUI/res/values/cm_arrays.xml index 9bd31f0..687ab30 100644 --- a/packages/SystemUI/res/values/cm_arrays.xml +++ b/packages/SystemUI/res/values/cm_arrays.xml @@ -54,4 +54,24 @@ <item>@string/accessibility_quick_settings_perf_profile_changed_bias_perf</item> <item>@string/accessibility_quick_settings_perf_profile_changed_perf</item> </string-array> + + <!-- Dynamic tiles --> + <string-array name="dynamic_qs_tiles_labels" translatable="false"> + <item>@string/dynamic_qs_tile_next_alarm_label</item> + <item>@string/dynamic_qs_tile_ime_selector_label</item> + <item>@string/dynamic_qs_tile_su_label</item> + <item>@string/dynamic_qs_tile_adb_label</item> + </string-array> + <string-array name="dynamic_qs_tiles_icons_resources_ids" translatable="false"> + <item>ic_dynamic_qs_next_alarm</item> + <item>ic_dynamic_qs_ime_selector</item> + <item>ic_dynamic_qs_su</item> + <item>ic_dynamic_qs_adb</item> + </string-array> + <string-array name="dynamic_qs_tiles_values" translatable="false"> + <item>next_alarm</item> + <item>ime_selector</item> + <item>su</item> + <item>adb</item> + </string-array> </resources> diff --git a/packages/SystemUI/res/values/cm_strings.xml b/packages/SystemUI/res/values/cm_strings.xml index 03b9a47..03fc080 100644 --- a/packages/SystemUI/res/values/cm_strings.xml +++ b/packages/SystemUI/res/values/cm_strings.xml @@ -153,4 +153,11 @@ <!-- Announcement made when the battery mode tile changes to quick (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_perf_profile_changed_bias_perf">Battery mode changed to quick mode.</string> <string name="quick_settings_performance_profile_detail_title">Battery mode</string> + + <!-- Dynamic tiles --> + <string name="quick_settings_dynamic_tile_detail_title">Dynamic tile</string> + <string name="dynamic_qs_tile_next_alarm_label">Next alarm</string> + <string name="dynamic_qs_tile_ime_selector_label">IME selector</string> + <string name="dynamic_qs_tile_su_label">Root</string> + <string name="dynamic_qs_tile_adb_label" translatable="false">ADB</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java index f016dc0..9d76e4d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java @@ -1477,7 +1477,10 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On super.handleShowDetailImpl(r, show, x, y); if (show) { final StatusBarPanelCustomTile customTile = r.detailAdapter.getCustomTile(); - mDetailRemoveButton.setVisibility(customTile != null ? VISIBLE : GONE); + mDetailRemoveButton.setVisibility(customTile != null && + !(customTile.getPackage().equals(mContext.getPackageName()) + || customTile.getUid() == android.os.Process.SYSTEM_UID) + ? VISIBLE : GONE); mDetailRemoveButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomQSTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomQSTile.java index 4bc30a4..96cc51a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomQSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomQSTile.java @@ -22,6 +22,7 @@ import android.content.Intent; import android.content.res.Configuration; import android.content.res.ThemeConfig; import android.net.Uri; +import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; @@ -61,7 +62,7 @@ public class CustomQSTile extends QSTile<QSTile.State> { public CustomQSTile(Host host, StatusBarPanelCustomTile tile) { super(host); - refreshState(tile); + mTile = tile; } @Override @@ -109,6 +110,7 @@ public class CustomQSTile extends QSTile<QSTile.State> { if (mOnClick != null) { mOnClick.send(); } else if (mOnClickUri != null) { + mHost.collapsePanels(); final Intent intent = new Intent().setData(mOnClickUri); mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId)); } @@ -123,9 +125,9 @@ public class CustomQSTile extends QSTile<QSTile.State> { mTile = (StatusBarPanelCustomTile) arg; } final CustomTile customTile = mTile.getCustomTile(); - state.visible = true; state.contentDescription = customTile.contentDescription; state.label = customTile.label; + state.visible = true; final int iconId = customTile.icon; if (iconId != 0 && (customTile.remoteIcon == null)) { final String iconPackage = mTile.getResPkg(); @@ -147,12 +149,22 @@ public class CustomQSTile extends QSTile<QSTile.State> { return MetricsLogger.DONT_TRACK_ME_BRO; } + private boolean isDynamicTile() { + return mTile.getPackage().equals(mContext.getPackageName()) + || mTile.getUid() == Process.SYSTEM_UID; + } + private class CustomQSDetailAdapter implements DetailAdapter, AdapterView.OnItemClickListener, QSDetailItemsGrid.QSDetailItemsGridAdapter.OnPseudoGriditemClickListener { private QSDetailItemsList.QSCustomDetailListAdapter mListAdapter; private QSDetailItemsGrid.QSDetailItemsGridAdapter mGridAdapter; public int getTitle() { + if (isDynamicTile()) { + return mContext.getResources().getIdentifier( + String.format("dynamic_qs_tile_%s_label", mTile.getTag()), + "string", mContext.getPackageName()); + } return R.string.quick_settings_custom_tile_detail_title; } @@ -199,8 +211,12 @@ public class CustomQSTile extends QSTile<QSTile.State> { // icon is cached in state, fetch it imageView.setImageDrawable(getState().icon.getDrawable(mContext)); customTileTitle.setText(mTile.getCustomTile().label); - customTilePkg.setText(mTile.getPackage()); - customTileContentDesc.setText(mTile.getCustomTile().contentDescription); + if (isDynamicTile()) { + customTilePkg.setText(R.string.quick_settings_dynamic_tile_detail_title); + } else { + customTilePkg.setText(mTile.getPackage()); + customTileContentDesc.setText(mTile.getCustomTile().contentDescription); + } } else { switch (mExpandedStyle.getStyle()) { case CustomTile.ExpandedStyle.GRID_STYLE: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CustomTileData.java b/packages/SystemUI/src/com/android/systemui/statusbar/CustomTileData.java index 5db1f98..4be7292 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CustomTileData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CustomTileData.java @@ -25,11 +25,12 @@ import android.util.ArrayMap; */ public class CustomTileData { public static final class Entry { - public String key; - public StatusBarPanelCustomTile statusBarPanelCustomTile; + public final String key; + public final StatusBarPanelCustomTile sbc; public Entry(StatusBarPanelCustomTile sbc) { this.key = sbc.getKey(); + this.sbc = sbc; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index bcf1c2f..3987a35 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -424,6 +424,7 @@ public class NotificationPanelView extends PanelView implements @Override public void onAnimationEnd(Animator animation) { mQsSizeChangeAnimator = null; + mQsContainer.setHeightOverride(-1); } }); mQsSizeChangeAnimator.start(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index ee291f5..9c1ae95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -20,15 +20,21 @@ import android.app.ActivityManagerNative; import android.app.AlarmManager; import android.app.AlarmManager.AlarmClockInfo; import android.app.IUserSwitchObserver; +import android.app.PendingIntent; import android.app.StatusBarManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.UserInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.database.ContentObserver; +import android.graphics.Bitmap; import android.media.AudioManager; import android.net.Uri; +import android.os.Binder; import android.os.Handler; import android.os.IRemoteCallback; import android.os.RemoteException; @@ -51,8 +57,16 @@ import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.SuController; +import cyanogenmod.app.CMStatusBarManager; +import cyanogenmod.app.CustomTile; import cyanogenmod.providers.CMSettings; +import org.cyanogenmod.internal.util.QSUtils; +import org.cyanogenmod.internal.util.QSUtils.OnQSChanged; +import org.cyanogenmod.internal.util.QSConstants; + +import java.util.ArrayList; + /** * This class contains all of the policy about which icons are installed in the status * bar at boot time. It goes through the normal API for icons, even though it probably @@ -126,6 +140,13 @@ public class PhoneStatusBarPolicy implements Callback { } }; + private final OnQSChanged mQSListener = new OnQSChanged() { + @Override + public void onQSChanged() { + processQSChangedLocked(); + } + }; + public PhoneStatusBarPolicy(Context context, CastController cast, HotspotController hotspot, UserInfoController userInfoController, BluetoothController bluetooth, SuController su) { mContext = context; @@ -198,6 +219,8 @@ public class PhoneStatusBarPolicy implements Callback { mService.setIcon(SLOT_MANAGED_PROFILE, R.drawable.stat_sys_managed_profile_status, 0, mContext.getString(R.string.accessibility_managed_profile)); mService.setIconVisibility(SLOT_MANAGED_PROFILE, false); + + QSUtils.registerObserverForQSChanges(mContext, mQSListener); } private ContentObserver mAlarmIconObserver = new ContentObserver(null) { @@ -448,6 +471,12 @@ public class PhoneStatusBarPolicy implements Callback { private void updateSu() { mService.setIconVisibility(SLOT_SU, mSuController.hasActiveSessions()); + final int userId = UserHandle.myUserId(); + if (isSuEnabledForUser(userId)) { + publishSuCustomTile(); + } else { + unpublishSuCustomTile(); + } } private final CastController.Callback mCastCallback = new CastController.Callback() { @@ -479,4 +508,117 @@ public class PhoneStatusBarPolicy implements Callback { } }; + private void publishSuCustomTile() { + // This action should be performed as system + final int userId = UserHandle.myUserId(); + long token = Binder.clearCallingIdentity(); + try { + if (!QSUtils.isQSTileEnabledForUser( + mContext, QSConstants.DYNAMIC_TILE_SU, userId)) { + return; + } + + final UserHandle user = new UserHandle(userId); + final int icon = QSUtils.getDynamicQSTileResIconId(mContext, userId, + QSConstants.DYNAMIC_TILE_SU); + final String contentDesc = QSUtils.getDynamicQSTileLabel(mContext, userId, + QSConstants.DYNAMIC_TILE_SU); + final Context resourceContext = QSUtils.getQSTileContext(mContext, userId); + + CustomTile.ListExpandedStyle style = new CustomTile.ListExpandedStyle(); + ArrayList<CustomTile.ExpandedListItem> items = new ArrayList<>(); + for (String pkg : mSuController.getPackageNamesWithActiveSuSessions()) { + CustomTile.ExpandedListItem item = new CustomTile.ExpandedListItem(); + int appIconIdentifier = getActiveSuApkDrawableId(pkg); + if (appIconIdentifier != -1) { + item.setExpandedListItemDrawable(appIconIdentifier); + } else { + item.setExpandedListItemDrawable(icon); + } + item.setExpandedListItemTitle(getActiveSuApkLabel(pkg)); + item.setExpandedListItemSummary(pkg); + item.setExpandedListItemOnClickIntent(getCustomTilePendingIntent(pkg)); + items.add(item); + } + style.setListItems(items); + + CMStatusBarManager statusBarManager = CMStatusBarManager.getInstance(mContext); + CustomTile tile = new CustomTile.Builder(resourceContext) + .setLabel(contentDesc) + .setContentDescription(contentDesc) + .setIcon(icon) + .setOnSettingsClickIntent(getCustomTileSettingsIntent()) + .setExpandedStyle(style) + .build(); + statusBarManager.publishTileAsUser(QSConstants.DYNAMIC_TILE_SU, + PhoneStatusBarPolicy.class.hashCode(), tile, user); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private void unpublishSuCustomTile() { + // This action should be performed as system + final int userId = UserHandle.myUserId(); + long token = Binder.clearCallingIdentity(); + try { + CMStatusBarManager statusBarManager = CMStatusBarManager.getInstance(mContext); + statusBarManager.removeTileAsUser(QSConstants.DYNAMIC_TILE_SU, + PhoneStatusBarPolicy.class.hashCode(), new UserHandle(userId)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private PendingIntent getCustomTilePendingIntent(String pkg) { + Intent i = new Intent(Intent.ACTION_MAIN); + i.setPackage(pkg); + i.addCategory(Intent.CATEGORY_LAUNCHER); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return PendingIntent.getActivity(mContext, 0, i, PendingIntent.FLAG_UPDATE_CURRENT); + } + + private Intent getCustomTileSettingsIntent() { + Intent i = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return i; + } + + private String getActiveSuApkLabel(String pkg) { + final PackageManager pm = mContext.getPackageManager(); + ApplicationInfo ai = null; + try { + ai = pm.getApplicationInfo(pkg, 0); + } catch (final NameNotFoundException e) { + // Ignore + } + return (String) (ai != null ? pm.getApplicationLabel(ai) : pkg); + } + + private int getActiveSuApkDrawableId(String pkg) { + final PackageManager pm = mContext.getPackageManager(); + ApplicationInfo ai; + try { + ai = pm.getApplicationInfo(pkg, 0); + } catch (final NameNotFoundException e) { + return -1; + } + return ai.icon; + } + + private boolean isSuEnabledForUser(int userId) { + final boolean hasSuAccess = mSuController.hasActiveSessions(); + final boolean isEnabledForUser = QSUtils.isQSTileEnabledForUser(mContext, + QSConstants.DYNAMIC_TILE_SU, userId); + return (userId == UserHandle.USER_OWNER) && isEnabledForUser && hasSuAccess; + } + + private void processQSChangedLocked() { + final int userId = UserHandle.myUserId(); + if (isSuEnabledForUser(userId)) { + publishSuCustomTile(); + } else { + unpublishSuCustomTile(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index eff9b0a..be6f143 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -289,6 +289,7 @@ public class QSTileHost implements QSTile.Host, Tunable { } if (DEBUG) Log.d(TAG, "Recreating tiles"); final List<String> tileSpecs = loadTileSpecs(newValue); + removeUnusedDynamicTiles(tileSpecs); if (tileSpecs.equals(mTileSpecs)) return; for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) { if (!tileSpecs.contains(tile.getKey())) { @@ -318,6 +319,23 @@ public class QSTileHost implements QSTile.Host, Tunable { } } + private void removeUnusedDynamicTiles(List<String> tileSpecs) { + List<CustomTileData.Entry> tilesToRemove = new ArrayList<>(); + for (CustomTileData.Entry entry : mCustomTileData.getEntries().values()) { + if (entry.sbc.getPackage().equals(mContext.getPackageName()) + || entry.sbc.getUid() == Process.SYSTEM_UID) { + if (!tileSpecs.contains(entry.sbc.getTag())) { + tilesToRemove.add(entry); + } + } + } + + for (CustomTileData.Entry entry : tilesToRemove) { + mCustomTileData.remove(entry.key); + removeCustomTile(entry.sbc); + } + } + @Override public void goToSettingsPage() { if (mCallback != null) { @@ -429,29 +447,35 @@ public class QSTileHost implements QSTile.Host, Tunable { } void updateCustomTile(StatusBarPanelCustomTile sbc) { - if (mTiles.containsKey(sbc.getKey())) { - QSTile<?> tile = mTiles.get(sbc.getKey()); - if (tile instanceof CustomQSTile) { - CustomQSTile qsTile = (CustomQSTile) tile; - qsTile.update(sbc); + synchronized (mTiles) { + if (mTiles.containsKey(sbc.getKey())) { + QSTile<?> tile = mTiles.get(sbc.getKey()); + if (tile instanceof CustomQSTile) { + CustomQSTile qsTile = (CustomQSTile) tile; + qsTile.update(sbc); + } } } } void addCustomTile(StatusBarPanelCustomTile sbc) { - mCustomTileData.add(new CustomTileData.Entry(sbc)); - mTiles.put(sbc.getKey(), new CustomQSTile(this, sbc)); - if (mCallback != null) { - mCallback.onTilesChanged(); + synchronized (mTiles) { + mCustomTileData.add(new CustomTileData.Entry(sbc)); + mTiles.put(sbc.getKey(), new CustomQSTile(this, sbc)); + if (mCallback != null) { + mCallback.onTilesChanged(); + } } } void removeCustomTileSysUi(String key) { - if (mTiles.containsKey(key)) { - mTiles.remove(key); - mCustomTileData.remove(key); - if (mCallback != null) { - mCallback.onTilesChanged(); + synchronized (mTiles) { + if (mTiles.containsKey(key)) { + mTiles.remove(key); + mCustomTileData.remove(key); + if (mCallback != null) { + mCallback.onTilesChanged(); + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuController.java index 5f1e52e..de67261 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuController.java @@ -16,10 +16,13 @@ package com.android.systemui.statusbar.policy; +import java.util.List; + public interface SuController { void addCallback(Callback callback); void removeCallback(Callback callback); boolean hasActiveSessions(); + List<String> getPackageNamesWithActiveSuSessions(); public interface Callback { void onSuSessionsChanged(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuControllerImpl.java index 1ba334a..c663bab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuControllerImpl.java @@ -16,22 +16,15 @@ package com.android.systemui.statusbar.policy; -import android.app.ActivityManager; import android.app.AppOpsManager; -import android.app.StatusBarManager; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.os.UserHandle; -import android.os.UserManager; -import android.provider.Settings; import android.util.Log; -import com.android.systemui.R; - import java.util.ArrayList; import java.util.List; @@ -45,15 +38,11 @@ public class SuControllerImpl implements SuController { private ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); - private Context mContext; - private AppOpsManager mAppOpsManager; - private boolean mHasActiveSuSessions; + private List<String> mActiveSuSessions = new ArrayList<>(); public SuControllerImpl(Context context) { - mContext = context; - mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); IntentFilter intentFilter = new IntentFilter(); @@ -85,7 +74,9 @@ public class SuControllerImpl implements SuController { @Override public boolean hasActiveSessions() { - return mHasActiveSuSessions; + synchronized (mActiveSuSessions) { + return mActiveSuSessions.size() > 0; + } } private void fireCallback(Callback callback) { @@ -98,10 +89,10 @@ public class SuControllerImpl implements SuController { } } - /** - * Returns true if a su session is active - */ - private boolean hasActiveSuSessions() { + // Return the list of package names that currently have an active su session + @Override + public List<String> getPackageNamesWithActiveSuSessions() { + List<String> packageNames = new ArrayList<>(); List<AppOpsManager.PackageOps> packages = mAppOpsManager.getPackagesForOps(mSuOpArray); // AppOpsManager can return null when there is no requested data. @@ -116,7 +107,8 @@ public class SuControllerImpl implements SuController { AppOpsManager.OpEntry opEntry = opEntries.get(opInd); if (opEntry.getOp() == AppOpsManager.OP_SU) { if (opEntry.isRunning()) { - return true; + packageNames.add(packageOp.getPackageName()); + break; } } } @@ -124,14 +116,17 @@ public class SuControllerImpl implements SuController { } } - return false; + return packageNames; } - private void updateActiveSuSessions() { - boolean hadActiveSuSessions = mHasActiveSuSessions; - mHasActiveSuSessions = hasActiveSuSessions(); - if (mHasActiveSuSessions != hadActiveSuSessions) { - fireCallbacks(); + private synchronized void updateActiveSuSessions() { + List<String> newList = getPackageNamesWithActiveSuSessions(); + synchronized (mActiveSuSessions) { + if (!newList.equals(mActiveSuSessions)) { + mActiveSuSessions.clear(); + mActiveSuSessions.addAll(newList); + fireCallbacks(); + } } } } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 27858cb..cf1340b 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -28,7 +28,9 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.database.ContentObserver; import android.net.Uri; import android.os.Binder; @@ -42,6 +44,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.WorkSource; +import android.provider.AlarmClock; import android.provider.Settings; import android.text.TextUtils; import android.text.format.DateFormat; @@ -66,6 +69,7 @@ import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; +import java.util.List; import java.util.Locale; import java.util.Random; import java.util.TimeZone; @@ -79,6 +83,13 @@ import static android.app.AlarmManager.RTC_POWEROFF_WAKEUP; import com.android.internal.util.LocalLog; +import cyanogenmod.app.CMStatusBarManager; +import cyanogenmod.app.CustomTile; + +import org.cyanogenmod.internal.util.QSUtils; +import org.cyanogenmod.internal.util.QSUtils.OnQSChanged; +import org.cyanogenmod.internal.util.QSConstants; + class AlarmManagerService extends SystemService { private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP; private static final int RTC_MASK = 1 << RTC; @@ -293,6 +304,14 @@ class AlarmManagerService extends SystemService { final Constants mConstants; + private final OnQSChanged mQSListener = new OnQSChanged() { + @Override + public void onQSChanged() { + processQSChangedLocked(); + } + }; + private ContentObserver mQSObserver; + // Alarm delivery ordering bookkeeping static final int PRIO_TICK = 0; static final int PRIO_WAKEUP = 1; @@ -857,6 +876,8 @@ class AlarmManagerService extends SystemService { } publishBinderService(Context.ALARM_SERVICE, mService); + + mQSObserver = QSUtils.registerObserverForQSChanges(getContext(), mQSListener); } @Override @@ -869,6 +890,12 @@ class AlarmManagerService extends SystemService { @Override protected void finalize() throws Throwable { try { + QSUtils.unregisterObserverForQSChanges(getContext(), mQSObserver); + } catch (Exception ex) { + // Ignore + } + + try { close(mNativeData); } finally { super.finalize(); @@ -1052,6 +1079,8 @@ class AlarmManagerService extends SystemService { if (a.alarmClock != null) { mNextAlarmClockMayChange = true; + //Publish as system user + publishNextAlarmCustomTile(Process.SYSTEM_UID); } boolean needRebatch = false; @@ -1611,6 +1640,9 @@ class AlarmManagerService extends SystemService { updateNextAlarmInfoForUserLocked(userId, null); } } + + // Process dynamic custom tile + processQSChangedLocked(); } private void updateNextAlarmInfoForUserLocked(int userId, @@ -2654,6 +2686,121 @@ class AlarmManagerService extends SystemService { return bs; } + private void publishNextAlarmCustomTile(int userId) { + // This action should be performed as system + long token = Binder.clearCallingIdentity(); + try { + final UserHandle user = new UserHandle(userId); + if (!QSUtils.isQSTileEnabledForUser( + getContext(), QSConstants.DYNAMIC_TILE_NEXT_ALARM, user.getUserId(userId))) { + return; + } + + final int icon = QSUtils.getDynamicQSTileResIconId(getContext(), userId, + QSConstants.DYNAMIC_TILE_NEXT_ALARM); + final String contentDesc = QSUtils.getDynamicQSTileLabel(getContext(), userId, + QSConstants.DYNAMIC_TILE_NEXT_ALARM); + final Context resourceContext = QSUtils.getQSTileContext(getContext(), userId); + + // Create the expanded view with all the user alarms + AlarmManager.AlarmClockInfo nextAlarm = null; + CustomTile.ListExpandedStyle style = new CustomTile.ListExpandedStyle(); + ArrayList<CustomTile.ExpandedListItem> items = new ArrayList<>(); + for (Alarm alarm : getAllUserAlarmsLocked(userId)) { + if (nextAlarm == null) { + nextAlarm = alarm.alarmClock; + } + + final String pkg = alarm.operation.getCreatorPackage(); + CustomTile.ExpandedListItem item = new CustomTile.ExpandedListItem(); + item.setExpandedListItemDrawable(icon); + item.setExpandedListItemTitle(formatNextAlarm(getContext(), alarm.alarmClock, userId)); + item.setExpandedListItemSummary(getAlarmApkLabel(pkg)); + item.setExpandedListItemOnClickIntent(getCustomTilePendingIntent(user, pkg)); + items.add(item); + } + style.setListItems(items); + + // Build the custom tile + CMStatusBarManager statusBarManager = CMStatusBarManager.getInstance(getContext()); + CustomTile tile = new CustomTile.Builder(resourceContext) + .setLabel(formatNextAlarm(getContext(), nextAlarm, userId)) + .setContentDescription(contentDesc) + .setIcon(icon) + .setExpandedStyle(style) + .build(); + statusBarManager.publishTileAsUser(QSConstants.DYNAMIC_TILE_NEXT_ALARM, + AlarmManagerService.class.hashCode(), tile, user); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private void unpublishNextAlarmCustomTile(int userId) { + // This action should be performed as system + long token = Binder.clearCallingIdentity(); + try { + CMStatusBarManager statusBarManager = CMStatusBarManager.getInstance(getContext()); + statusBarManager.removeTileAsUser(QSConstants.DYNAMIC_TILE_NEXT_ALARM, + AlarmManagerService.class.hashCode(), new UserHandle(userId)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private List<Alarm> getAllUserAlarmsLocked(int userId) { + List<Alarm> userAlarms = new ArrayList<>(); + synchronized (mLock) { + final int N = mAlarmBatches.size(); + for (int i = 0; i < N; i++) { + ArrayList<Alarm> alarms = mAlarmBatches.get(i).alarms; + final int M = alarms.size(); + for (int j = 0; j < M; j++) { + Alarm a = alarms.get(j); + if (a.alarmClock != null && userId == a.uid) { + userAlarms.add(a); + } + } + } + } + return userAlarms; + } + + private String getAlarmApkLabel(String pkg) { + final PackageManager pm = getContext().getPackageManager(); + ApplicationInfo ai = null; + try { + ai = pm.getApplicationInfo(pkg, 0); + } catch (final NameNotFoundException e) { + // Ignore + } + return (String) (ai != null ? pm.getApplicationLabel(ai) : pkg); + } + + private PendingIntent getCustomTilePendingIntent(UserHandle user, String pkg) { + Intent i = new Intent(AlarmClock.ACTION_SHOW_ALARMS); + i.setPackage(pkg); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return PendingIntent.getActivityAsUser(getContext(), 0, i, + PendingIntent.FLAG_UPDATE_CURRENT, null, user); + } + + private void processQSChangedLocked() { + synchronized (mLock) { + int count = mNextAlarmClockForUser.size(); + for (int i = 0; i < count; i++) { + int userId = mNextAlarmClockForUser.keyAt(i); + boolean enabled = QSUtils.isQSTileEnabledForUser( + getContext(), QSConstants.DYNAMIC_TILE_NEXT_ALARM, userId); + if (enabled) { + publishNextAlarmCustomTile(userId); + } else { + unpublishNextAlarmCustomTile(userId); + } + } + } + } + class ResultReceiver implements PendingIntent.OnFinished { public void onSendFinished(PendingIntent pi, Intent intent, int resultCode, String resultData, Bundle resultExtras) { diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index 6d6ca3c..e36d118 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -36,6 +36,9 @@ import com.android.server.pm.UserManagerService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.wm.WindowManagerService; +import cyanogenmod.app.CMStatusBarManager; +import cyanogenmod.app.CustomTile; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -142,6 +145,9 @@ import java.util.Locale; import cyanogenmod.providers.CMSettings; +import org.cyanogenmod.internal.util.QSUtils; +import org.cyanogenmod.internal.util.QSUtils.OnQSChanged; +import org.cyanogenmod.internal.util.QSConstants; /** * This class provides a system service that manages input methods. */ @@ -199,6 +205,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1, -1); + private final OnQSChanged mQSListener = new OnQSChanged() { + @Override + public void onQSChanged() { + processQSChangedLocked(); + } + }; + // All known input methods. mMethodMap also serves as the global // lock for this class. final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>(); @@ -949,6 +962,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } }, filter); LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl(mHandler)); + QSUtils.registerObserverForQSChanges(mContext, mQSListener); } private void resetDefaultImeLocked(Context context) { @@ -1822,6 +1836,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub com.android.internal.R.string.select_input_method, mImeSwitcherNotification.build(), UserHandle.ALL); mNotificationShown = true; + publishImeSelectorCustomTile(imi); } } else { if (mNotificationShown && mNotificationManager != null) { @@ -1831,6 +1846,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mNotificationManager.cancelAsUser(null, com.android.internal.R.string.select_input_method, UserHandle.ALL); mNotificationShown = false; + unpublishImeSelectorCustomTile(); } } } finally { @@ -3561,6 +3577,78 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + private void publishImeSelectorCustomTile(InputMethodInfo imi) { + // This action should be performed as system + final int userId = UserHandle.myUserId(); + long token = Binder.clearCallingIdentity(); + try { + if (!QSUtils.isQSTileEnabledForUser( + mContext, QSConstants.DYNAMIC_TILE_IME_SELECTOR, userId)) { + return; + } + + final UserHandle user = new UserHandle(userId); + final int icon = QSUtils.getDynamicQSTileResIconId(mContext, userId, + QSConstants.DYNAMIC_TILE_IME_SELECTOR); + final String contentDesc = QSUtils.getDynamicQSTileLabel(mContext, userId, + QSConstants.DYNAMIC_TILE_IME_SELECTOR); + final Context resourceContext = QSUtils.getQSTileContext(mContext, userId); + CharSequence inputMethodName = null; + if (mCurrentSubtype != null) { + inputMethodName = mCurrentSubtype.getDisplayName(mContext, + imi.getPackageName(), imi.getServiceInfo().applicationInfo); + } + final CharSequence label = inputMethodName == null ? contentDesc : inputMethodName; + + CMStatusBarManager statusBarManager = CMStatusBarManager.getInstance(mContext); + CustomTile tile = new CustomTile.Builder(resourceContext) + .setLabel(label.toString()) + .setContentDescription(contentDesc) + .setIcon(icon) + .setOnClickIntent(mImeSwitchPendingIntent) + .build(); + statusBarManager.publishTileAsUser(QSConstants.DYNAMIC_TILE_IME_SELECTOR, + InputMethodManagerService.class.hashCode(), tile, user); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private void unpublishImeSelectorCustomTile() { + // This action should be performed as system + final int userId = UserHandle.myUserId(); + long token = Binder.clearCallingIdentity(); + try { + CMStatusBarManager statusBarManager = CMStatusBarManager.getInstance(mContext); + statusBarManager.removeTileAsUser(QSConstants.DYNAMIC_TILE_IME_SELECTOR, + InputMethodManagerService.class.hashCode(), new UserHandle(userId)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private void processQSChangedLocked() { + final int userId = UserHandle.myUserId(); + final boolean isIMEVisible = ((mImeWindowVis & (InputMethodService.IME_ACTIVE)) != 0) + && (mWindowManagerService.isHardKeyboardAvailable() + || (mImeWindowVis & (InputMethodService.IME_VISIBLE)) != 0); + InputMethodInfo imi = null; + synchronized (mMethodMap) { + if (mCurMethodId != null) { + imi = mMethodMap.get(mCurMethodId); + } + } + final boolean hasInputMethod = isIMEVisible && imi != null && mCurrentSubtype != null; + final boolean isEnabledForUser = QSUtils.isQSTileEnabledForUser(mContext, + QSConstants.DYNAMIC_TILE_NEXT_ALARM, userId); + boolean enabled = isEnabledForUser && hasInputMethod; + if (enabled) { + publishImeSelectorCustomTile(imi); + } else { + unpublishImeSelectorCustomTile(); + } + } + // TODO: Cache the state for each user and reset when the cached user is removed. private static class InputMethodFileManager { private static final String SYSTEM_PATH = "system"; diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 34a17a2..19e39e7 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -32,6 +32,7 @@ import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; +import android.os.Binder; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; @@ -51,8 +52,16 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import com.android.server.FgThread; + import cyanogenmod.providers.CMSettings; +import cyanogenmod.app.CMStatusBarManager; +import cyanogenmod.app.CustomTile; + +import org.cyanogenmod.internal.util.QSUtils; +import org.cyanogenmod.internal.util.QSUtils.OnQSChanged; +import org.cyanogenmod.internal.util.QSConstants; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -162,6 +171,13 @@ public class UsbDeviceManager { } } + private final OnQSChanged mQSListener = new OnQSChanged() { + @Override + public void onQSChanged() { + mHandler.processQSChangedLocked(); + } + }; + /* * Listens for uevent messages from the kernel to monitor the USB state */ @@ -361,11 +377,13 @@ public class UsbDeviceManager { false, adbNotificationObserver); mContentResolver.registerContentObserver( CMSettings.Secure.getUriFor(CMSettings.Secure.ADB_NOTIFY), - false, adbNotificationObserver); + false, adbNotificationObserver); // Watch for USB configuration changes mUEventObserver.startObserving(USB_STATE_MATCH); mUEventObserver.startObserving(ACCESSORY_START_MATCH); + + QSUtils.registerObserverForQSChanges(mContext, mQSListener); } catch (Exception e) { Slog.e(TAG, "Error initializing UsbHandler", e); } @@ -865,6 +883,12 @@ public class UsbDeviceManager { } mAdbNotificationId = id; } + + if (id > 0) { + publishAdbCustomTile(); + } else { + unpublishAdbCustomTile(); + } } private String getDefaultFunctions() { @@ -893,6 +917,95 @@ public class UsbDeviceManager { pw.println("IOException: " + e); } } + + private void publishAdbCustomTile() { + // This action should be performed as system + final int userId = UserHandle.myUserId(); + long token = Binder.clearCallingIdentity(); + try { + if (!QSUtils.isQSTileEnabledForUser( + mContext, QSConstants.DYNAMIC_TILE_ADB, userId)) { + return; + } + + final UserHandle user = new UserHandle(userId); + final int icon = QSUtils.getDynamicQSTileResIconId(mContext, userId, + QSConstants.DYNAMIC_TILE_ADB); + final String contentDesc = QSUtils.getDynamicQSTileLabel(mContext, userId, + QSConstants.DYNAMIC_TILE_ADB); + final Context resourceContext = QSUtils.getQSTileContext(mContext, userId); + + CMStatusBarManager statusBarManager = CMStatusBarManager.getInstance(mContext); + CustomTile tile = new CustomTile.Builder(resourceContext) + .setLabel(getAdbCustomTileLabel()) + .setContentDescription(contentDesc) + .setIcon(icon) + .setOnClickIntent(getCustomTilePendingIntent()) + .build(); + statusBarManager.publishTileAsUser(QSConstants.DYNAMIC_TILE_ADB, + UsbDeviceManager.class.hashCode(), tile, user); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private void unpublishAdbCustomTile() { + // This action should be performed as system + final int userId = UserHandle.myUserId(); + long token = Binder.clearCallingIdentity(); + try { + CMStatusBarManager statusBarManager = CMStatusBarManager.getInstance(mContext); + statusBarManager.removeTileAsUser(QSConstants.DYNAMIC_TILE_ADB, + UsbDeviceManager.class.hashCode(), new UserHandle(userId)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private PendingIntent getCustomTilePendingIntent() { + Intent i = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return PendingIntent.getActivity(mContext, 0, i, PendingIntent.FLAG_UPDATE_CURRENT, null); + } + + private String getAdbCustomTileLabel() { + boolean usbAdbActive = mAdbEnabled && mConnected; + boolean netAdbActive = mAdbEnabled && + CMSettings.Secure.getInt(mContentResolver, CMSettings.Secure.ADB_PORT, -1) > 0; + + int id = 0; + if (usbAdbActive && netAdbActive) { + id = com.android.internal.R.string.adb_active_custom_tile_both; + } else if (usbAdbActive) { + id = com.android.internal.R.string.adb_active_custom_tile_usb; + } else if (netAdbActive) { + id = com.android.internal.R.string.adb_active_custom_tile_net; + } + + Resources res = mContext.getResources(); + return res.getString( + com.android.internal.R.string.adb_active_custom_tile, + res.getString(id)); + } + + private void processQSChangedLocked() { + final int userId = UserHandle.myUserId(); + boolean usbAdbActive = mAdbEnabled && mConnected; + boolean netAdbActive = mAdbEnabled && + CMSettings.Secure.getInt(mContentResolver, CMSettings.Secure.ADB_PORT, -1) > 0; + boolean notifEnabled = "1".equals(SystemProperties.get("persist.adb.notify")) + || CMSettings.Secure.getInt(mContext.getContentResolver(), + CMSettings.Secure.ADB_NOTIFY, 1) == 1; + boolean isActive = notifEnabled && (usbAdbActive || netAdbActive); + final boolean isEnabledForUser = QSUtils.isQSTileEnabledForUser(mContext, + QSConstants.DYNAMIC_TILE_ADB, userId); + boolean enabled = (userId == UserHandle.USER_OWNER) && isEnabledForUser && isActive; + if (enabled) { + publishAdbCustomTile(); + } else { + unpublishAdbCustomTile(); + } + } } /* returns the currently attached USB accessory */ |