diff options
Diffstat (limited to 'packages/SettingsLib/src')
64 files changed, 3093 insertions, 838 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java b/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java index 253ca11bc44e..8e29e50fc848 100644 --- a/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreference.java @@ -20,6 +20,7 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; +import android.support.annotation.CallSuper; import android.support.v14.preference.EditTextPreferenceDialogFragment; import android.support.v7.preference.EditTextPreference; import android.util.AttributeSet; @@ -30,7 +31,8 @@ public class CustomEditTextPreference extends EditTextPreference { private CustomPreferenceDialogFragment mFragment; - public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + public CustomEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @@ -47,8 +49,13 @@ public class CustomEditTextPreference extends EditTextPreference { } public EditText getEditText() { - return mFragment != null ? (EditText) mFragment.getDialog().findViewById(android.R.id.edit) - : null; + if (mFragment != null) { + final Dialog dialog = mFragment.getDialog(); + if (dialog != null) { + return (EditText) dialog.findViewById(android.R.id.edit); + } + } + return null; } public boolean isDialogOpen() { @@ -69,7 +76,12 @@ public class CustomEditTextPreference extends EditTextPreference { protected void onClick(DialogInterface dialog, int which) { } + @CallSuper protected void onBindDialogView(View view) { + final EditText editText = view.findViewById(android.R.id.edit); + if (editText != null) { + editText.requestFocus(); + } } private void setFragment(CustomPreferenceDialogFragment fragment) { diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java index 78a9064f1400..f2cd1033cd38 100644 --- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java @@ -22,12 +22,15 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Build; +import android.system.Os; +import android.system.StructUtsname; import android.telephony.PhoneNumberUtils; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.Log; +import android.support.annotation.VisibleForTesting; import java.io.BufferedReader; import java.io.FileReader; @@ -45,7 +48,6 @@ import static android.content.Context.TELEPHONY_SERVICE; public class DeviceInfoUtils { private static final String TAG = "DeviceInfoUtils"; - private static final String FILENAME_PROC_VERSION = "/proc/version"; private static final String FILENAME_MSV = "/sys/board_properties/soc/msv"; /** @@ -63,43 +65,36 @@ public class DeviceInfoUtils { } } - public static String getFormattedKernelVersion() { - try { - return formatKernelVersion(readLine(FILENAME_PROC_VERSION)); - } catch (IOException e) { - Log.e(TAG, "IO Exception when getting kernel version for Device Info screen", - e); - - return "Unavailable"; - } + public static String getFormattedKernelVersion(Context context) { + return formatKernelVersion(context, Os.uname()); } - public static String formatKernelVersion(String rawKernelVersion) { - // Example (see tests for more): - // Linux version 4.9.29-g958411d (android-build@xyz) (Android clang version 3.8.256229 \ - // (based on LLVM 3.8.256229)) #1 SMP PREEMPT Wed Jun 7 00:06:03 CST 2017 - // Linux version 4.9.29-geb63318482a7 (android-build@xyz) (gcc version 4.9.x 20150123 \ - // (prerelease) (GCC) ) #1 SMP PREEMPT Thu Jun 1 03:41:57 UTC 2017 - final String PROC_VERSION_REGEX = - "Linux version (\\S+) " + /* group 1: "3.0.31-g6fb96c9" */ - "\\((\\S+?)\\) " + /* group 2: "(x@y.com) " */ - "\\((.+?)\\) " + /* group 3: kernel toolchain version information */ - "(#\\d+) " + /* group 4: "#1" */ + @VisibleForTesting + static String formatKernelVersion(Context context, StructUtsname uname) { + if (uname == null) { + return context.getString(R.string.status_unavailable); + } + // Example: + // 4.9.29-g958411d + // #1 SMP PREEMPT Wed Jun 7 00:06:03 CST 2017 + final String VERSION_REGEX = + "(#\\d+) " + /* group 1: "#1" */ "(?:.*?)?" + /* ignore: optional SMP, PREEMPT, and any CONFIG_FLAGS */ - "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 5: "Thu Jun 28 11:02:39 PDT 2012" */ - - Matcher m = Pattern.compile(PROC_VERSION_REGEX).matcher(rawKernelVersion); + "((Sun|Mon|Tue|Wed|Thu|Fri|Sat).+)"; /* group 2: "Thu Jun 28 11:02:39 PDT 2012" */ + Matcher m = Pattern.compile(VERSION_REGEX).matcher(uname.version); if (!m.matches()) { - Log.e(TAG, "Regex did not match on /proc/version: " + rawKernelVersion); - return "Unavailable"; - } else if (m.groupCount() < 4) { - Log.e(TAG, "Regex match on /proc/version only returned " + m.groupCount() - + " groups"); - return "Unavailable"; + Log.e(TAG, "Regex did not match on uname version " + uname.version); + return context.getString(R.string.status_unavailable); } - return m.group(1) + " ("+ m.group(3) + ")\n" + // 3.0.31-g6fb96c9 (toolchain version) - m.group(2) + " " + m.group(4) + "\n" + // x@y.com #1 - m.group(5); // Thu Jun 28 11:02:39 PDT 2012 + + // Example output: + // 4.9.29-g958411d + // #1 Wed Jun 7 00:06:03 CST 2017 + return new StringBuilder().append(uname.release) + .append("\n") + .append(m.group(1)) + .append(" ") + .append(m.group(2)).toString(); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java index 2c2641079953..8055caaad536 100644 --- a/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/HelpUtils.java @@ -227,7 +227,7 @@ public class HelpUtils { // cache the version code PackageInfo info = context.getPackageManager().getPackageInfo( context.getPackageName(), 0); - sCachedVersionCode = Integer.toString(info.versionCode); + sCachedVersionCode = Long.toString(info.getLongVersionCode()); // append the version code to the uri builder.appendQueryParameter(PARAM_VERSION, sCachedVersionCode); diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java index 32e6389dc34f..c3a36e97be3a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java @@ -28,6 +28,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.graphics.drawable.Drawable; import android.os.RemoteException; @@ -343,7 +344,8 @@ public class RestrictedLockUtils { } DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); - if (dpm == null) { + PackageManager pm = context.getPackageManager(); + if (!pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) || dpm == null) { return null; } boolean isAccountTypeDisabled = false; diff --git a/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java b/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java index 1c161dffced9..8b39f60aa9ca 100644 --- a/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/TwoTargetPreference.java @@ -21,41 +21,56 @@ import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceViewHolder; import android.util.AttributeSet; import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; public class TwoTargetPreference extends Preference { + private boolean mUseSmallIcon; + private int mSmallIconSize; + public TwoTargetPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - init(); + init(context); } public TwoTargetPreference(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - init(); + init(context); } public TwoTargetPreference(Context context, AttributeSet attrs) { super(context, attrs); - init(); + init(context); } public TwoTargetPreference(Context context) { super(context); - init(); + init(context); } - private void init() { + private void init(Context context) { setLayoutResource(R.layout.preference_two_target); + mSmallIconSize = context.getResources().getDimensionPixelSize( + R.dimen.two_target_pref_small_icon_size); final int secondTargetResId = getSecondTargetResId(); if (secondTargetResId != 0) { setWidgetLayoutResource(secondTargetResId); } } + public void setUseSmallIcon(boolean useSmallIcon) { + mUseSmallIcon = useSmallIcon; + } + @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); + if (mUseSmallIcon) { + ImageView icon = holder.itemView.findViewById(android.R.id.icon); + icon.setLayoutParams(new LinearLayout.LayoutParams(mSmallIconSize, mSmallIconSize)); + } final View divider = holder.findViewById(R.id.two_target_divider); final View widgetFrame = holder.findViewById(android.R.id.widget_frame); final boolean shouldHideSecondTarget = shouldHideSecondTarget(); diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java index 64ec16a23b46..3c46d99906c7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java @@ -12,18 +12,15 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Color; -import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; +import android.location.LocationManager; import android.net.ConnectivityManager; -import android.net.NetworkBadging; import android.os.BatteryManager; +import android.os.UserHandle; import android.os.UserManager; import android.print.PrintManager; import android.provider.Settings; -import android.view.View; import com.android.internal.util.UserIcons; import com.android.settingslib.drawable.UserIconDrawable; @@ -31,19 +28,33 @@ import com.android.settingslib.drawable.UserIconDrawable; import java.text.NumberFormat; public class Utils { + + private static final String CURRENT_MODE_KEY = "CURRENT_MODE"; + private static final String NEW_MODE_KEY = "NEW_MODE"; + private static Signature[] sSystemSignature; private static String sPermissionControllerPackageName; private static String sServicesSystemSharedLibPackageName; private static String sSharedSystemSharedLibPackageName; - static final int[] WIFI_PIE_FOR_BADGING = { - com.android.internal.R.drawable.ic_signal_wifi_badged_0_bars, - com.android.internal.R.drawable.ic_signal_wifi_badged_1_bar, - com.android.internal.R.drawable.ic_signal_wifi_badged_2_bars, - com.android.internal.R.drawable.ic_signal_wifi_badged_3_bars, - com.android.internal.R.drawable.ic_signal_wifi_badged_4_bars + static final int[] WIFI_PIE = { + com.android.internal.R.drawable.ic_wifi_signal_0, + com.android.internal.R.drawable.ic_wifi_signal_1, + com.android.internal.R.drawable.ic_wifi_signal_2, + com.android.internal.R.drawable.ic_wifi_signal_3, + com.android.internal.R.drawable.ic_wifi_signal_4 }; + public static boolean updateLocationMode(Context context, int oldMode, int newMode, int userId) { + Intent intent = new Intent(LocationManager.MODE_CHANGING_ACTION); + intent.putExtra(CURRENT_MODE_KEY, oldMode); + intent.putExtra(NEW_MODE_KEY, newMode); + context.sendBroadcastAsUser( + intent, UserHandle.of(userId), android.Manifest.permission.WRITE_SECURE_SETTINGS); + return Settings.Secure.putIntForUser( + context.getContentResolver(), Settings.Secure.LOCATION_MODE, newMode, userId); + } + /** * Return string resource that best describes combination of tethering * options available on this device. @@ -99,7 +110,7 @@ public class Utils { public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) { final int iconSize = UserIconDrawable.getSizeForList(context); if (user.isManagedProfile()) { - Drawable drawable = context.getDrawable(com.android.internal.R.drawable.ic_corp_icon); + Drawable drawable = context.getDrawable(com.android.internal.R.drawable.ic_corp_badge); drawable.setBounds(0, 0, iconSize, iconSize); return drawable; } @@ -110,7 +121,8 @@ public class Utils { } } return new UserIconDrawable(iconSize).setIconDrawable( - UserIcons.getDefaultUserIcon(user.id, /* light= */ false)).bake(); + UserIcons.getDefaultUserIcon(context.getResources(), user.id, /* light= */ false)) + .bake(); } /** Formats a double from 0.0..100.0 with an option to round **/ @@ -272,42 +284,17 @@ public class Utils { } /** - * Returns a badged Wifi icon drawable. - * - * <p>The first layer contains the Wifi pie and the second layer contains the badge. Callers - * should set the drawable to the appropriate size and tint color. + * Returns the Wifi icon resource for a given RSSI level. * - * @param context The caller's context (must have access to internal resources) * @param level The number of bars to show (0-4) - * @param badge The badge enum {@see android.net.ScoredNetwork} * - * @throws IllegalArgumentException if an invalid badge enum is given - * - * @deprecated TODO(sghuman): Finalize the form of this method and then move it to a new - * location. + * @throws IllegalArgumentException if an invalid RSSI level is given. */ - public static LayerDrawable getBadgedWifiIcon(Context context, int level, int badge) { - return new LayerDrawable( - new Drawable[] { - context.getDrawable(WIFI_PIE_FOR_BADGING[level]), - context.getDrawable(getWifiBadgeResource(badge)) - }); - } - - private static int getWifiBadgeResource(int badge) { - switch (badge) { - case NetworkBadging.BADGING_NONE: - return View.NO_ID; - case NetworkBadging.BADGING_SD: - return com.android.internal.R.drawable.ic_signal_wifi_badged_sd; - case NetworkBadging.BADGING_HD: - return com.android.internal.R.drawable.ic_signal_wifi_badged_hd; - case NetworkBadging.BADGING_4K: - return com.android.internal.R.drawable.ic_signal_wifi_badged_4k; - default: - throw new IllegalArgumentException( - "No badge resource found for badge value: " + badge); + public static int getWifiIconResource(int level) { + if (level < 0 || level >= WIFI_PIE.length) { + throw new IllegalArgumentException("No Wifi icon found for level: " + level); } + return WIFI_PIE[level]; } public static int getDefaultStorageManagerDaysToRetain(Resources resources) { @@ -325,4 +312,9 @@ public class Utils { } return defaultDays; } + + public static boolean isWifiOnly(Context context) { + return !context.getSystemService(ConnectivityManager.class) + .isNetworkSupported(ConnectivityManager.TYPE_MOBILE); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index 40c2b1f3b771..fa2499f6c144 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -52,6 +52,11 @@ import android.util.SparseArray; import com.android.internal.R; import com.android.internal.util.ArrayUtils; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnDestroy; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; import java.io.File; import java.io.IOException; @@ -180,7 +185,11 @@ public class ApplicationsState { } public Session newSession(Callbacks callbacks) { - Session s = new Session(callbacks); + return newSession(callbacks, null); + } + + public Session newSession(Callbacks callbacks, Lifecycle lifecycle) { + Session s = new Session(callbacks, lifecycle); synchronized (mEntriesMap) { mSessions.add(s); } @@ -586,7 +595,7 @@ public class ApplicationsState { .replaceAll("").toLowerCase(); } - public class Session { + public class Session implements LifecycleObserver, OnPause, OnResume, OnDestroy { final Callbacks mCallbacks; boolean mResumed; @@ -600,11 +609,19 @@ public class ApplicationsState { ArrayList<AppEntry> mLastAppList; boolean mRebuildForeground; - Session(Callbacks callbacks) { + private final boolean mHasLifecycle; + + Session(Callbacks callbacks, Lifecycle lifecycle) { mCallbacks = callbacks; + if (lifecycle != null) { + lifecycle.addObserver(this); + mHasLifecycle = true; + } else { + mHasLifecycle = false; + } } - public void resume() { + public void onResume() { if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock..."); synchronized (mEntriesMap) { if (!mResumed) { @@ -616,7 +633,7 @@ public class ApplicationsState { if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock"); } - public void pause() { + public void onPause() { if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock..."); synchronized (mEntriesMap) { if (mResumed) { @@ -735,8 +752,11 @@ public class ApplicationsState { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); } - public void release() { - pause(); + public void onDestroy() { + if (!mHasLifecycle) { + // TODO: Legacy, remove this later once all usages are switched to Lifecycle + onPause(); + } synchronized (mEntriesMap) { mSessions.remove(this); } diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/PackageManagerWrapper.java b/packages/SettingsLib/src/com/android/settingslib/applications/PackageManagerWrapper.java deleted file mode 100644 index 6c79a6124ca2..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/applications/PackageManagerWrapper.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2017 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.settingslib.applications; - -import android.content.ComponentName; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.os.UserHandle; - -import java.util.List; - -/** - * This interface replicates a subset of the android.content.pm.PackageManager (PM). The interface - * exists so that we can use a thin wrapper around the PM in production code and a mock in tests. - * We cannot directly mock or shadow the PM, because some of the methods we rely on are newer than - * the API version supported by Robolectric. - */ -public interface PackageManagerWrapper { - - /** - * Returns the real {@code PackageManager} object. - */ - PackageManager getPackageManager(); - - /** - * Calls {@code PackageManager.getInstalledApplicationsAsUser()}. - * - * @see android.content.pm.PackageManager#getInstalledApplicationsAsUser - */ - List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId); - - /** - * Calls {@code PackageManager.hasSystemFeature()}. - * - * @see android.content.pm.PackageManager#hasSystemFeature - */ - boolean hasSystemFeature(String name); - - /** - * Calls {@code PackageManager.queryIntentActivitiesAsUser()}. - * - * @see android.content.pm.PackageManager#queryIntentActivitiesAsUser - */ - List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId); - - /** - * Calls {@code PackageManager.getInstallReason()}. - * - * @see android.content.pm.PackageManager#getInstallReason - */ - int getInstallReason(String packageName, UserHandle user); - - /** - * Calls {@code PackageManager.getApplicationInfoAsUser} - */ - ApplicationInfo getApplicationInfoAsUser(String packageName, int i, int userId) - throws PackageManager.NameNotFoundException; - - /** - * Calls {@code PackageManager.setDefaultBrowserPackageNameAsUser} - */ - boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId); - - /** - * Calls {@code PackageManager.getDefaultBrowserPackageNameAsUser} - */ - String getDefaultBrowserPackageNameAsUser(int userId); - - /** - * Calls {@code PackageManager.getHomeActivities} - */ - ComponentName getHomeActivities(List<ResolveInfo> homeActivities); - - /** - * Calls {@code PackageManager.queryIntentServicesAsUser} - */ - List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int i, int user); - - /** - * Calls {@code PackageManager.replacePreferredActivity} - */ - void replacePreferredActivity(IntentFilter homeFilter, int matchCategoryEmpty, - ComponentName[] componentNames, ComponentName component); - - /** - * Gets information about a particular package from the package manager. - * @param packageName The name of the package we would like information about. - * @param i additional options flags. see javadoc for {@link PackageManager#getPackageInfo(String, int)} - * @return The PackageInfo for the requested package - * @throws NameNotFoundException - */ - PackageInfo getPackageInfo(String packageName, int i) throws NameNotFoundException; - - /** - * Retrieves the icon associated with this particular set of ApplicationInfo - * @param info The ApplicationInfo to retrieve the icon for - * @return The icon as a drawable. - */ - Drawable getUserBadgedIcon(ApplicationInfo info); - - /** - * Retrieves the label associated with the particular set of ApplicationInfo - * @param app The ApplicationInfo to retrieve the label for - * @return the label as a CharSequence - */ - CharSequence loadLabel(ApplicationInfo app); - - /** - * Retrieve all activities that can be performed for the given intent. - */ - List<ResolveInfo> queryIntentActivities(Intent intent, int flags); -} diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/PackageManagerWrapperImpl.java b/packages/SettingsLib/src/com/android/settingslib/applications/PackageManagerWrapperImpl.java deleted file mode 100644 index dcb40b20365e..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/applications/PackageManagerWrapperImpl.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (C) 2017 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.settingslib.applications; - -import android.content.ComponentName; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.os.UserHandle; - -import java.util.List; - -/** - * A thin wrapper class that simplifies testing by putting a mockable layer between the application - * and the PackageManager. This class only provides access to the minimum number of functions from - * the PackageManager needed for DeletionHelper to work. - */ -public class PackageManagerWrapperImpl implements PackageManagerWrapper { - - private final PackageManager mPm; - - public PackageManagerWrapperImpl(PackageManager pm) { - mPm = pm; - } - - @Override - public PackageManager getPackageManager() { - return mPm; - } - - @Override - public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) { - return mPm.getInstalledApplicationsAsUser(flags, userId); - } - - @Override - public boolean hasSystemFeature(String name) { - return mPm.hasSystemFeature(name); - } - - @Override - public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) { - return mPm.queryIntentActivitiesAsUser(intent, flags, userId); - } - - @Override - public int getInstallReason(String packageName, UserHandle user) { - return mPm.getInstallReason(packageName, user); - } - - @Override - public ApplicationInfo getApplicationInfoAsUser(String packageName, int i, int userId) - throws PackageManager.NameNotFoundException { - return mPm.getApplicationInfoAsUser(packageName, i, userId); - } - - @Override - public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) { - return mPm.setDefaultBrowserPackageNameAsUser(packageName, userId); - } - - @Override - public String getDefaultBrowserPackageNameAsUser(int userId) { - return mPm.getDefaultBrowserPackageNameAsUser(userId); - } - - @Override - public ComponentName getHomeActivities(List<ResolveInfo> homeActivities) { - return mPm.getHomeActivities(homeActivities); - } - - @Override - public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int i, int user) { - return mPm.queryIntentServicesAsUser(intent, i, user); - } - - @Override - public void replacePreferredActivity(IntentFilter homeFilter, int matchCategoryEmpty, - ComponentName[] componentNames, ComponentName component) { - mPm.replacePreferredActivity(homeFilter, matchCategoryEmpty, componentNames, component); - } - - @Override - public PackageInfo getPackageInfo(String packageName, int i) throws NameNotFoundException { - return mPm.getPackageInfo(packageName, i); - } - - @Override - public Drawable getUserBadgedIcon(ApplicationInfo info) { - return mPm.getUserBadgedIcon(mPm.loadUnbadgedItemIcon(info, info), - new UserHandle(UserHandle.getUserId(info.uid))); - } - - @Override - public CharSequence loadLabel(ApplicationInfo app) { - return app.loadLabel(mPm); - } - - @Override - public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) { - return mPm.queryIntentActivities(intent, flags); - } -} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java index 0946181ab710..764c5922cc64 100755 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java @@ -30,6 +30,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.R; +import com.android.settingslib.wrapper.BluetoothA2dpWrapper; import java.util.ArrayList; import java.util.Arrays; @@ -42,7 +43,6 @@ public class A2dpProfile implements LocalBluetoothProfile { private Context mContext; private BluetoothA2dp mService; - BluetoothA2dpWrapper.Factory mWrapperFactory; private BluetoothA2dpWrapper mServiceWrapper; private boolean mIsProfileReady; @@ -67,7 +67,7 @@ public class A2dpProfile implements LocalBluetoothProfile { public void onServiceConnected(int profile, BluetoothProfile proxy) { if (V) Log.d(TAG,"Bluetooth service connected"); mService = (BluetoothA2dp) proxy; - mServiceWrapper = mWrapperFactory.getInstance(mService); + mServiceWrapper = new BluetoothA2dpWrapper(mService); // We just bound to the service, so refresh the UI for any connected A2DP devices. List<BluetoothDevice> deviceList = mService.getConnectedDevices(); while (!deviceList.isEmpty()) { @@ -101,14 +101,13 @@ public class A2dpProfile implements LocalBluetoothProfile { mLocalAdapter = adapter; mDeviceManager = deviceManager; mProfileManager = profileManager; - mWrapperFactory = new BluetoothA2dpWrapperImpl.Factory(); mLocalAdapter.getProfileProxy(context, new A2dpServiceListener(), BluetoothProfile.A2DP); } @VisibleForTesting - void setWrapperFactory(BluetoothA2dpWrapper.Factory factory) { - mWrapperFactory = factory; + void setBluetoothA2dpWrapper(BluetoothA2dpWrapper wrapper) { + mServiceWrapper = wrapper; } public boolean isConnectable() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java deleted file mode 100644 index aa3e8356739a..000000000000 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapper.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2017 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.settingslib.bluetooth; - -import android.bluetooth.BluetoothA2dp; -import android.bluetooth.BluetoothCodecStatus; -import android.bluetooth.BluetoothDevice; - -/** - * This interface replicates some methods of android.bluetooth.BluetoothA2dp that are new and not - * yet available in our current version of Robolectric. It provides a thin wrapper to call the real - * methods in production and a mock in tests. - */ -public interface BluetoothA2dpWrapper { - - static interface Factory { - BluetoothA2dpWrapper getInstance(BluetoothA2dp service); - } - - /** - * @return the real {@code BluetoothA2dp} object - */ - BluetoothA2dp getService(); - - /** - * Wraps {@code BluetoothA2dp.getCodecStatus} - */ - public BluetoothCodecStatus getCodecStatus(); - - /** - * Wraps {@code BluetoothA2dp.supportsOptionalCodecs} - */ - int supportsOptionalCodecs(BluetoothDevice device); - - /** - * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled} - */ - int getOptionalCodecsEnabled(BluetoothDevice device); - - /** - * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled} - */ - void setOptionalCodecsEnabled(BluetoothDevice device, int value); -} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS new file mode 100644 index 000000000000..7162121330ef --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS @@ -0,0 +1,8 @@ +# Default reviewers for this and subdirectories. +asapperstein@google.com +asargent@google.com +eisenbach@google.com +jackqdyulei@google.com +siyuanh@google.com + +# Emergency approvers in case the above are not available
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java index c9194268543a..0ee1dad9d744 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/Utils.java @@ -1,9 +1,17 @@ package com.android.settingslib.bluetooth; +import android.bluetooth.BluetoothClass; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.annotation.DrawableRes; +import android.util.Pair; import com.android.settingslib.R; +import com.android.settingslib.graph.BluetoothDeviceLayerDrawable; + +import java.util.List; public class Utils { public static final boolean V = false; // verbose logging @@ -40,4 +48,78 @@ public class Utils { void onShowError(Context context, String name, int messageResId); } + public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context, + CachedBluetoothDevice cachedDevice) { + return getBtClassDrawableWithDescription(context, cachedDevice, 1 /* iconScale */); + } + + public static Pair<Drawable, String> getBtClassDrawableWithDescription(Context context, + CachedBluetoothDevice cachedDevice, float iconScale) { + BluetoothClass btClass = cachedDevice.getBtClass(); + final int level = cachedDevice.getBatteryLevel(); + if (btClass != null) { + switch (btClass.getMajorDeviceClass()) { + case BluetoothClass.Device.Major.COMPUTER: + return new Pair<>(getBluetoothDrawable(context, R.drawable.ic_bt_laptop, level, + iconScale), + context.getString(R.string.bluetooth_talkback_computer)); + + case BluetoothClass.Device.Major.PHONE: + return new Pair<>( + getBluetoothDrawable(context, R.drawable.ic_bt_cellphone, level, + iconScale), + context.getString(R.string.bluetooth_talkback_phone)); + + case BluetoothClass.Device.Major.PERIPHERAL: + return new Pair<>( + getBluetoothDrawable(context, HidProfile.getHidClassDrawable(btClass), + level, iconScale), + context.getString(R.string.bluetooth_talkback_input_peripheral)); + + case BluetoothClass.Device.Major.IMAGING: + return new Pair<>( + getBluetoothDrawable(context, R.drawable.ic_settings_print, level, + iconScale), + context.getString(R.string.bluetooth_talkback_imaging)); + + default: + // unrecognized device class; continue + } + } + + List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles(); + for (LocalBluetoothProfile profile : profiles) { + int resId = profile.getDrawableResource(btClass); + if (resId != 0) { + return new Pair<>(getBluetoothDrawable(context, resId, level, iconScale), null); + } + } + if (btClass != null) { + if (btClass.doesClassMatch(BluetoothClass.PROFILE_HEADSET)) { + return new Pair<>( + getBluetoothDrawable(context, R.drawable.ic_bt_headset_hfp, level, + iconScale), + context.getString(R.string.bluetooth_talkback_headset)); + } + if (btClass.doesClassMatch(BluetoothClass.PROFILE_A2DP)) { + return new Pair<>( + getBluetoothDrawable(context, R.drawable.ic_bt_headphones_a2dp, level, + iconScale), + context.getString(R.string.bluetooth_talkback_headphone)); + } + } + return new Pair<>( + getBluetoothDrawable(context, R.drawable.ic_settings_bluetooth, level, iconScale), + context.getString(R.string.bluetooth_talkback_bluetooth)); + } + + public static Drawable getBluetoothDrawable(Context context, @DrawableRes int resId, + int batteryLevel, float iconScale) { + if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) { + return BluetoothDeviceLayerDrawable.createLayerDrawable(context, resId, batteryLevel, + iconScale); + } else { + return context.getDrawable(resId); + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java index 38fe8790e4d0..88f0d2be311f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java @@ -10,79 +10,63 @@ import android.support.v7.preference.PreferenceScreen; */ public abstract class AbstractPreferenceController { - protected final Context mContext; + protected final Context mContext; - public AbstractPreferenceController(Context context) { - mContext = context; - } + public AbstractPreferenceController(Context context) { + mContext = context; + } - /** - * Displays preference in this controller. - */ - public void displayPreference(PreferenceScreen screen) { - if (isAvailable()) { - if (this instanceof Preference.OnPreferenceChangeListener) { - final Preference preference = screen.findPreference(getPreferenceKey()); - preference.setOnPreferenceChangeListener( - (Preference.OnPreferenceChangeListener) this); - } - } else { - removePreference(screen, getPreferenceKey()); - } - } + /** + * Displays preference in this controller. + */ + public void displayPreference(PreferenceScreen screen) { + final String prefKey = getPreferenceKey(); + if (isAvailable()) { + setVisible(screen, prefKey, true /* visible */); + if (this instanceof Preference.OnPreferenceChangeListener) { + final Preference preference = screen.findPreference(prefKey); + preference.setOnPreferenceChangeListener( + (Preference.OnPreferenceChangeListener) this); + } + } else { + setVisible(screen, prefKey, false /* visible */); + } + } - /** - * Updates the current status of preference (summary, switch state, etc) - */ - public void updateState(Preference preference) { + /** + * Updates the current status of preference (summary, switch state, etc) + */ + public void updateState(Preference preference) { - } + } - /** - * Returns true if preference is available (should be displayed) - */ - public abstract boolean isAvailable(); + /** + * Returns true if preference is available (should be displayed) + */ + public abstract boolean isAvailable(); - /** - * Handles preference tree click - * - * @param preference the preference being clicked - * @return true if click is handled - */ - public boolean handlePreferenceTreeClick(Preference preference) { - return false; - } + /** + * Handles preference tree click + * + * @param preference the preference being clicked + * @return true if click is handled + */ + public boolean handlePreferenceTreeClick(Preference preference) { + return false; + } - /** - * Returns the key for this preference. - */ - public abstract String getPreferenceKey(); - - /** - * Removes preference from screen. - */ - protected final void removePreference(PreferenceScreen screen, String key) { - findAndRemovePreference(screen, key); - } - - // finds the preference recursively and removes it from its parent - private boolean findAndRemovePreference(PreferenceGroup prefGroup, String key) { - final int preferenceCount = prefGroup.getPreferenceCount(); - for (int i = 0; i < preferenceCount; i++) { - final Preference preference = prefGroup.getPreference(i); - final String curKey = preference.getKey(); - - if (curKey != null && curKey.equals(key)) { - return prefGroup.removePreference(preference); - } - - if (preference instanceof PreferenceGroup) { - if (findAndRemovePreference((PreferenceGroup) preference, key)) { - return true; - } - } - } - return false; - } + /** + * Returns the key for this preference. + */ + public abstract String getPreferenceKey(); + /** + * Show/hide a preference. + */ + protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) { + final Preference pref = group.findPreference(key); + if (pref != null) { + pref.setVisible(isVisible); + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/ConfirmationDialogController.java b/packages/SettingsLib/src/com/android/settingslib/core/ConfirmationDialogController.java new file mode 100644 index 000000000000..72ab8c3848c5 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/core/ConfirmationDialogController.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 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.settingslib.core; + +import android.support.annotation.Nullable; +import android.support.v7.preference.Preference; + +/** + * Interface for {@link AbstractPreferenceController} objects which manage confirmation dialogs + */ +public interface ConfirmationDialogController { + /** + * Returns the key for this preference. + */ + String getPreferenceKey(); + + /** + * Shows the dialog + * @param preference Preference object relevant to the dialog being shown + */ + void showConfirmationDialog(@Nullable Preference preference); + + /** + * Dismiss the dialog managed by this object + */ + void dismissConfirmationDialog(); + + /** + * @return {@code true} if the dialog is showing + */ + boolean isConfirmationDialogShowing(); +} diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java index b2351a9ee0e4..451e5611979a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/Lifecycle.java @@ -15,11 +15,18 @@ */ package com.android.settingslib.core.lifecycle; +import static android.arch.lifecycle.Lifecycle.Event.ON_ANY; + import android.annotation.UiThread; +import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.LifecycleRegistry; +import android.arch.lifecycle.OnLifecycleEvent; import android.content.Context; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.preference.PreferenceScreen; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -44,18 +51,46 @@ import java.util.List; /** * Dispatcher for lifecycle events. */ -public class Lifecycle { +public class Lifecycle extends LifecycleRegistry { + private static final String TAG = "LifecycleObserver"; + + private final List<LifecycleObserver> mObservers = new ArrayList<>(); + private final LifecycleProxy mProxy = new LifecycleProxy(); - protected final List<LifecycleObserver> mObservers = new ArrayList<>(); + /** + * Creates a new LifecycleRegistry for the given provider. + * <p> + * You should usually create this inside your LifecycleOwner class's constructor and hold + * onto the same instance. + * + * @param provider The owner LifecycleOwner + */ + public Lifecycle(@NonNull LifecycleOwner provider) { + super(provider); + addObserver(mProxy); + } /** * Registers a new observer of lifecycle events. */ @UiThread - public <T extends LifecycleObserver> T addObserver(T observer) { + @Override + public void addObserver(android.arch.lifecycle.LifecycleObserver observer) { ThreadUtils.ensureMainThread(); - mObservers.add(observer); - return observer; + super.addObserver(observer); + if (observer instanceof LifecycleObserver) { + mObservers.add((LifecycleObserver) observer); + } + } + + @UiThread + @Override + public void removeObserver(android.arch.lifecycle.LifecycleObserver observer) { + ThreadUtils.ensureMainThread(); + super.removeObserver(observer); + if (observer instanceof LifecycleObserver) { + mObservers.remove(observer); + } } public void onAttach(Context context) { @@ -67,6 +102,8 @@ public class Lifecycle { } } + // This method is not called from the proxy because it does not have access to the + // savedInstanceState public void onCreate(Bundle savedInstanceState) { for (int i = 0, size = mObservers.size(); i < size; i++) { final LifecycleObserver observer = mObservers.get(i); @@ -76,7 +113,7 @@ public class Lifecycle { } } - public void onStart() { + private void onStart() { for (int i = 0, size = mObservers.size(); i < size; i++) { final LifecycleObserver observer = mObservers.get(i); if (observer instanceof OnStart) { @@ -94,7 +131,7 @@ public class Lifecycle { } } - public void onResume() { + private void onResume() { for (int i = 0, size = mObservers.size(); i < size; i++) { final LifecycleObserver observer = mObservers.get(i); if (observer instanceof OnResume) { @@ -103,7 +140,7 @@ public class Lifecycle { } } - public void onPause() { + private void onPause() { for (int i = 0, size = mObservers.size(); i < size; i++) { final LifecycleObserver observer = mObservers.get(i); if (observer instanceof OnPause) { @@ -121,7 +158,7 @@ public class Lifecycle { } } - public void onStop() { + private void onStop() { for (int i = 0, size = mObservers.size(); i < size; i++) { final LifecycleObserver observer = mObservers.get(i); if (observer instanceof OnStop) { @@ -130,7 +167,7 @@ public class Lifecycle { } } - public void onDestroy() { + private void onDestroy() { for (int i = 0, size = mObservers.size(); i < size; i++) { final LifecycleObserver observer = mObservers.get(i); if (observer instanceof OnDestroy) { @@ -168,4 +205,34 @@ public class Lifecycle { } return false; } + + private class LifecycleProxy + implements android.arch.lifecycle.LifecycleObserver { + @OnLifecycleEvent(ON_ANY) + public void onLifecycleEvent(LifecycleOwner owner, Event event) { + switch (event) { + case ON_CREATE: + // onCreate is called directly since we don't have savedInstanceState here + break; + case ON_START: + onStart(); + break; + case ON_RESUME: + onResume(); + break; + case ON_PAUSE: + onPause(); + break; + case ON_STOP: + onStop(); + break; + case ON_DESTROY: + onDestroy(); + break; + case ON_ANY: + Log.wtf(TAG, "Should not receive an 'ANY' event!"); + break; + } + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/LifecycleObserver.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/LifecycleObserver.java index 6c4107290274..ec8a8b537f48 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/LifecycleObserver.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/LifecycleObserver.java @@ -17,6 +17,9 @@ package com.android.settingslib.core.lifecycle; /** * Observer of lifecycle events. + * @deprecated use {@link android.arch.lifecycle.LifecycleObserver} instead */ -public interface LifecycleObserver { +@Deprecated +public interface LifecycleObserver extends + android.arch.lifecycle.LifecycleObserver { } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java index 727bec75c204..8b062f8447b0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java @@ -15,8 +15,16 @@ */ package com.android.settingslib.core.lifecycle; +import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE; +import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY; +import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE; +import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME; +import static android.arch.lifecycle.Lifecycle.Event.ON_START; +import static android.arch.lifecycle.Lifecycle.Event.ON_STOP; + import android.annotation.Nullable; import android.app.Activity; +import android.arch.lifecycle.LifecycleOwner; import android.os.Bundle; import android.os.PersistableBundle; import android.view.Menu; @@ -25,17 +33,19 @@ import android.view.MenuItem; /** * {@link Activity} that has hooks to observe activity lifecycle events. */ -public class ObservableActivity extends Activity { +public class ObservableActivity extends Activity implements LifecycleOwner { - private final Lifecycle mLifecycle = new Lifecycle(); + private final Lifecycle mLifecycle = new Lifecycle(this); - protected Lifecycle getLifecycle() { + public Lifecycle getLifecycle() { return mLifecycle; } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { mLifecycle.onAttach(this); + mLifecycle.onCreate(savedInstanceState); + mLifecycle.handleLifecycleEvent(ON_CREATE); super.onCreate(savedInstanceState); } @@ -43,36 +53,38 @@ public class ObservableActivity extends Activity { public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) { mLifecycle.onAttach(this); + mLifecycle.onCreate(savedInstanceState); + mLifecycle.handleLifecycleEvent(ON_CREATE); super.onCreate(savedInstanceState, persistentState); } @Override protected void onStart() { - mLifecycle.onStart(); + mLifecycle.handleLifecycleEvent(ON_START); super.onStart(); } @Override protected void onResume() { - mLifecycle.onResume(); + mLifecycle.handleLifecycleEvent(ON_RESUME); super.onResume(); } @Override protected void onPause() { - mLifecycle.onPause(); + mLifecycle.handleLifecycleEvent(ON_PAUSE); super.onPause(); } @Override protected void onStop() { - mLifecycle.onStop(); + mLifecycle.handleLifecycleEvent(ON_STOP); super.onStop(); } @Override protected void onDestroy() { - mLifecycle.onDestroy(); + mLifecycle.handleLifecycleEvent(ON_DESTROY); super.onDestroy(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java index 315bedc168bb..dc95384b26a5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java @@ -15,9 +15,17 @@ */ package com.android.settingslib.core.lifecycle; +import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE; +import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY; +import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE; +import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME; +import static android.arch.lifecycle.Lifecycle.Event.ON_START; +import static android.arch.lifecycle.Lifecycle.Event.ON_STOP; + import android.app.DialogFragment; +import android.arch.lifecycle.LifecycleOwner; import android.content.Context; -import android.support.annotation.VisibleForTesting; +import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -25,9 +33,9 @@ import android.view.MenuItem; /** * {@link DialogFragment} that has hooks to observe fragment lifecycle events. */ -public class ObservableDialogFragment extends DialogFragment { +public class ObservableDialogFragment extends DialogFragment implements LifecycleOwner { - protected final Lifecycle mLifecycle = createLifecycle(); + protected final Lifecycle mLifecycle = new Lifecycle(this); @Override public void onAttach(Context context) { @@ -36,32 +44,39 @@ public class ObservableDialogFragment extends DialogFragment { } @Override + public void onCreate(Bundle savedInstanceState) { + mLifecycle.onCreate(savedInstanceState); + mLifecycle.handleLifecycleEvent(ON_CREATE); + super.onCreate(savedInstanceState); + } + + @Override public void onStart() { - mLifecycle.onStart(); + mLifecycle.handleLifecycleEvent(ON_START); super.onStart(); } @Override public void onResume() { - mLifecycle.onResume(); + mLifecycle.handleLifecycleEvent(ON_RESUME); super.onResume(); } @Override public void onPause() { - mLifecycle.onPause(); + mLifecycle.handleLifecycleEvent(ON_PAUSE); super.onPause(); } @Override public void onStop() { - mLifecycle.onStop(); + mLifecycle.handleLifecycleEvent(ON_STOP); super.onStop(); } @Override public void onDestroy() { - mLifecycle.onDestroy(); + mLifecycle.handleLifecycleEvent(ON_DESTROY); super.onDestroy(); } @@ -86,9 +101,8 @@ public class ObservableDialogFragment extends DialogFragment { return lifecycleHandled; } - @VisibleForTesting(otherwise = VisibleForTesting.NONE) - /** @return a new lifecycle. */ - public static Lifecycle createLifecycle() { - return new Lifecycle(); + @Override + public Lifecycle getLifecycle() { + return mLifecycle; } } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java index 3a00eba664c4..925eda6ef9d5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java @@ -16,19 +16,27 @@ package com.android.settingslib.core.lifecycle; +import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE; +import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY; +import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE; +import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME; +import static android.arch.lifecycle.Lifecycle.Event.ON_START; +import static android.arch.lifecycle.Lifecycle.Event.ON_STOP; + import android.annotation.CallSuper; import android.app.Fragment; +import android.arch.lifecycle.LifecycleOwner; import android.content.Context; import android.os.Bundle; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; -public class ObservableFragment extends Fragment { +public class ObservableFragment extends Fragment implements LifecycleOwner { - private final Lifecycle mLifecycle = new Lifecycle(); + private final Lifecycle mLifecycle = new Lifecycle(this); - protected Lifecycle getLifecycle() { + public Lifecycle getLifecycle() { return mLifecycle; } @@ -43,6 +51,7 @@ public class ObservableFragment extends Fragment { @Override public void onCreate(Bundle savedInstanceState) { mLifecycle.onCreate(savedInstanceState); + mLifecycle.handleLifecycleEvent(ON_CREATE); super.onCreate(savedInstanceState); } @@ -56,35 +65,35 @@ public class ObservableFragment extends Fragment { @CallSuper @Override public void onStart() { - mLifecycle.onStart(); + mLifecycle.handleLifecycleEvent(ON_START); super.onStart(); } @CallSuper @Override - public void onStop() { - mLifecycle.onStop(); - super.onStop(); - } - - @CallSuper - @Override public void onResume() { - mLifecycle.onResume(); + mLifecycle.handleLifecycleEvent(ON_RESUME); super.onResume(); } @CallSuper @Override public void onPause() { - mLifecycle.onPause(); + mLifecycle.handleLifecycleEvent(ON_PAUSE); super.onPause(); } @CallSuper @Override + public void onStop() { + mLifecycle.handleLifecycleEvent(ON_STOP); + super.onStop(); + } + + @CallSuper + @Override public void onDestroy() { - mLifecycle.onDestroy(); + mLifecycle.handleLifecycleEvent(ON_DESTROY); super.onDestroy(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java index 76e5c8579f05..abd77559612e 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java @@ -16,7 +16,15 @@ package com.android.settingslib.core.lifecycle; +import static android.arch.lifecycle.Lifecycle.Event.ON_CREATE; +import static android.arch.lifecycle.Lifecycle.Event.ON_DESTROY; +import static android.arch.lifecycle.Lifecycle.Event.ON_PAUSE; +import static android.arch.lifecycle.Lifecycle.Event.ON_RESUME; +import static android.arch.lifecycle.Lifecycle.Event.ON_START; +import static android.arch.lifecycle.Lifecycle.Event.ON_STOP; + import android.annotation.CallSuper; +import android.arch.lifecycle.LifecycleOwner; import android.content.Context; import android.os.Bundle; import android.support.v14.preference.PreferenceFragment; @@ -28,11 +36,12 @@ import android.view.MenuItem; /** * {@link PreferenceFragment} that has hooks to observe fragment lifecycle events. */ -public abstract class ObservablePreferenceFragment extends PreferenceFragment { +public abstract class ObservablePreferenceFragment extends PreferenceFragment + implements LifecycleOwner { - private final Lifecycle mLifecycle = new Lifecycle(); + private final Lifecycle mLifecycle = new Lifecycle(this); - protected Lifecycle getLifecycle() { + public Lifecycle getLifecycle() { return mLifecycle; } @@ -47,6 +56,7 @@ public abstract class ObservablePreferenceFragment extends PreferenceFragment { @Override public void onCreate(Bundle savedInstanceState) { mLifecycle.onCreate(savedInstanceState); + mLifecycle.handleLifecycleEvent(ON_CREATE); super.onCreate(savedInstanceState); } @@ -66,35 +76,35 @@ public abstract class ObservablePreferenceFragment extends PreferenceFragment { @CallSuper @Override public void onStart() { - mLifecycle.onStart(); + mLifecycle.handleLifecycleEvent(ON_START); super.onStart(); } @CallSuper @Override - public void onStop() { - mLifecycle.onStop(); - super.onStop(); - } - - @CallSuper - @Override public void onResume() { - mLifecycle.onResume(); + mLifecycle.handleLifecycleEvent(ON_RESUME); super.onResume(); } @CallSuper @Override public void onPause() { - mLifecycle.onPause(); + mLifecycle.handleLifecycleEvent(ON_PAUSE); super.onPause(); } @CallSuper @Override + public void onStop() { + mLifecycle.handleLifecycleEvent(ON_STOP); + super.onStop(); + } + + @CallSuper + @Override public void onDestroy() { - mLifecycle.onDestroy(); + mLifecycle.handleLifecycleEvent(ON_DESTROY); super.onDestroy(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnAttach.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnAttach.java index 152cbac3ad3d..e28c38736639 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnAttach.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnAttach.java @@ -17,6 +17,10 @@ package com.android.settingslib.core.lifecycle.events; import android.content.Context; +/** + * @deprecated pass {@link Context} in constructor instead + */ +@Deprecated public interface OnAttach { void onAttach(Context context); } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreate.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreate.java index 44cbf8d26757..ad7068e2f9fb 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreate.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnCreate.java @@ -16,8 +16,14 @@ package com.android.settingslib.core.lifecycle.events; +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.OnLifecycleEvent; import android.os.Bundle; +/** + * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) } + */ +@Deprecated public interface OnCreate { void onCreate(Bundle savedInstanceState); } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnDestroy.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnDestroy.java index ffa3d168b904..c37286e1efaa 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnDestroy.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnDestroy.java @@ -15,6 +15,13 @@ */ package com.android.settingslib.core.lifecycle.events; +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.OnLifecycleEvent; + +/** + * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) } + */ +@Deprecated public interface OnDestroy { void onDestroy(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPause.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPause.java index 4a711058df21..a5ab39c46ab1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPause.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnPause.java @@ -15,6 +15,13 @@ */ package com.android.settingslib.core.lifecycle.events; +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.OnLifecycleEvent; + +/** + * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) } + */ +@Deprecated public interface OnPause { void onPause(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnResume.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnResume.java index 8dd24e98401d..1effba4a750d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnResume.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnResume.java @@ -15,6 +15,13 @@ */ package com.android.settingslib.core.lifecycle.events; +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.OnLifecycleEvent; + +/** + * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event)} + */ +@Deprecated public interface OnResume { void onResume(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStart.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStart.java index c88ddaa1d8de..07b8460367c1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStart.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStart.java @@ -15,7 +15,13 @@ */ package com.android.settingslib.core.lifecycle.events; -public interface OnStart { +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.OnLifecycleEvent; +/** + * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) } + */ +@Deprecated +public interface OnStart { void onStart(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStop.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStop.java index 32f61d98e4ad..d6a5967116be 100644 --- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStop.java +++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/events/OnStop.java @@ -15,7 +15,13 @@ */ package com.android.settingslib.core.lifecycle.events; -public interface OnStop { +import android.arch.lifecycle.Lifecycle; +import android.arch.lifecycle.OnLifecycleEvent; +/** + * @deprecated use {@link OnLifecycleEvent(Lifecycle.Event) } + */ +@Deprecated +public interface OnStop { void onStop(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java index 1771208398fd..a8262c8cc4c8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java +++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.XmlResourceParser; import android.icu.text.TimeZoneFormat; import android.icu.text.TimeZoneNames; +import android.support.annotation.VisibleForTesting; import android.support.v4.text.BidiFormatter; import android.support.v4.text.TextDirectionHeuristicsCompat; import android.text.SpannableString; @@ -32,6 +33,8 @@ import android.view.View; import com.android.settingslib.R; +import libcore.util.TimeZoneFinder; + import org.xmlpull.v1.XmlPullParserException; import java.util.ArrayList; @@ -349,7 +352,8 @@ public class ZoneGetter { return gmtText; } - private static final class ZoneGetterData { + @VisibleForTesting + public static final class ZoneGetterData { public final String[] olsonIdsToDisplay; public final CharSequence[] gmtOffsetTexts; public final TimeZone[] timeZones; @@ -376,10 +380,13 @@ public class ZoneGetter { } // Create a lookup of local zone IDs. - localZoneIds = new HashSet<String>(); - for (String olsonId : libcore.icu.TimeZoneNames.forLocale(locale)) { - localZoneIds.add(olsonId); - } + final List<String> zoneIds = lookupTimeZoneIdsByCountry(locale.getCountry()); + localZoneIds = new HashSet<>(zoneIds); + } + + @VisibleForTesting + public List<String> lookupTimeZoneIdsByCountry(String country) { + return TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(country); } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java index 75b6696d923c..3c02f6a234f4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java +++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java @@ -16,11 +16,13 @@ package com.android.settingslib.development; +import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.os.UserManager; import android.provider.Settings; +import android.support.annotation.VisibleForTesting; import android.support.v14.preference.SwitchPreference; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.preference.Preference; @@ -28,15 +30,20 @@ import android.support.v7.preference.PreferenceScreen; import android.support.v7.preference.TwoStatePreference; import android.text.TextUtils; -import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.ConfirmationDialogController; -public abstract class AbstractEnableAdbPreferenceController extends AbstractPreferenceController { +public abstract class AbstractEnableAdbPreferenceController extends + DeveloperOptionsPreferenceController implements ConfirmationDialogController { private static final String KEY_ENABLE_ADB = "enable_adb"; public static final String ACTION_ENABLE_ADB_STATE_CHANGED = "com.android.settingslib.development.AbstractEnableAdbController." + "ENABLE_ADB_STATE_CHANGED"; - private SwitchPreference mPreference; + public static final int ADB_SETTING_ON = 1; + public static final int ADB_SETTING_OFF = 0; + + + protected SwitchPreference mPreference; public AbstractEnableAdbPreferenceController(Context context) { super(context); @@ -62,12 +69,13 @@ public abstract class AbstractEnableAdbPreferenceController extends AbstractPref private boolean isAdbEnabled() { final ContentResolver cr = mContext.getContentResolver(); - return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) != 0; + return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, ADB_SETTING_OFF) + != ADB_SETTING_OFF; } @Override public void updateState(Preference preference) { - ((TwoStatePreference)preference).setChecked(isAdbEnabled()); + ((TwoStatePreference) preference).setChecked(isAdbEnabled()); } public void enablePreference(boolean enabled) { @@ -89,9 +97,13 @@ public abstract class AbstractEnableAdbPreferenceController extends AbstractPref @Override public boolean handlePreferenceTreeClick(Preference preference) { + if (isUserAMonkey()) { + return false; + } + if (TextUtils.equals(KEY_ENABLE_ADB, preference.getKey())) { if (!isAdbEnabled()) { - showConfirmationDialog((SwitchPreference) preference); + showConfirmationDialog(preference); } else { writeAdbSetting(false); } @@ -103,14 +115,17 @@ public abstract class AbstractEnableAdbPreferenceController extends AbstractPref protected void writeAdbSetting(boolean enabled) { Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.ADB_ENABLED, enabled ? 1 : 0); + Settings.Global.ADB_ENABLED, enabled ? ADB_SETTING_ON : ADB_SETTING_OFF); notifyStateChanged(); } - protected void notifyStateChanged() { + private void notifyStateChanged() { LocalBroadcastManager.getInstance(mContext) .sendBroadcast(new Intent(ACTION_ENABLE_ADB_STATE_CHANGED)); } - public abstract void showConfirmationDialog(SwitchPreference preference); + @VisibleForTesting + boolean isUserAMonkey() { + return ActivityManager.isUserAMonkey(); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogdSizePreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogdSizePreferenceController.java new file mode 100644 index 000000000000..f79be7eaddb1 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogdSizePreferenceController.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2017 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.settingslib.development; + +import android.content.Context; +import android.content.Intent; +import android.os.SystemProperties; +import android.support.annotation.VisibleForTesting; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.preference.ListPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settingslib.R; + +public abstract class AbstractLogdSizePreferenceController extends + DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener { + public static final String ACTION_LOGD_SIZE_UPDATED = "com.android.settingslib.development." + + "AbstractLogdSizePreferenceController.LOGD_SIZE_UPDATED"; + public static final String EXTRA_CURRENT_LOGD_VALUE = "CURRENT_LOGD_VALUE"; + + @VisibleForTesting + static final String LOW_RAM_CONFIG_PROPERTY_KEY = "ro.config.low_ram"; + private static final String SELECT_LOGD_SIZE_KEY = "select_logd_size"; + @VisibleForTesting + static final String SELECT_LOGD_SIZE_PROPERTY = "persist.logd.size"; + static final String SELECT_LOGD_TAG_PROPERTY = "persist.log.tag"; + // Tricky, isLoggable only checks for first character, assumes silence + static final String SELECT_LOGD_TAG_SILENCE = "Settings"; + @VisibleForTesting + static final String SELECT_LOGD_SNET_TAG_PROPERTY = "persist.log.tag.snet_event_log"; + private static final String SELECT_LOGD_RUNTIME_SNET_TAG_PROPERTY = "log.tag.snet_event_log"; + private static final String SELECT_LOGD_DEFAULT_SIZE_PROPERTY = "ro.logd.size"; + @VisibleForTesting + static final String SELECT_LOGD_DEFAULT_SIZE_VALUE = "262144"; + private static final String SELECT_LOGD_SVELTE_DEFAULT_SIZE_VALUE = "65536"; + // 32768 is merely a menu marker, 64K is our lowest log buffer size we replace it with. + @VisibleForTesting + static final String SELECT_LOGD_MINIMUM_SIZE_VALUE = "65536"; + static final String SELECT_LOGD_OFF_SIZE_MARKER_VALUE = "32768"; + @VisibleForTesting + static final String DEFAULT_SNET_TAG = "I"; + + private ListPreference mLogdSize; + + public AbstractLogdSizePreferenceController(Context context) { + super(context); + } + + @Override + public String getPreferenceKey() { + return SELECT_LOGD_SIZE_KEY; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + if (isAvailable()) { + mLogdSize = (ListPreference) screen.findPreference(SELECT_LOGD_SIZE_KEY); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mLogdSize) { + writeLogdSizeOption(newValue); + return true; + } else { + return false; + } + } + + public void enablePreference(boolean enabled) { + if (isAvailable()) { + mLogdSize.setEnabled(enabled); + } + } + + private String defaultLogdSizeValue() { + String defaultValue = SystemProperties.get(SELECT_LOGD_DEFAULT_SIZE_PROPERTY); + if ((defaultValue == null) || (defaultValue.length() == 0)) { + if (SystemProperties.get("ro.config.low_ram").equals("true")) { + defaultValue = SELECT_LOGD_SVELTE_DEFAULT_SIZE_VALUE; + } else { + defaultValue = SELECT_LOGD_DEFAULT_SIZE_VALUE; + } + } + return defaultValue; + } + + public void updateLogdSizeValues() { + if (mLogdSize != null) { + String currentTag = SystemProperties.get(SELECT_LOGD_TAG_PROPERTY); + String currentValue = SystemProperties.get(SELECT_LOGD_SIZE_PROPERTY); + if ((currentTag != null) && currentTag.startsWith(SELECT_LOGD_TAG_SILENCE)) { + currentValue = SELECT_LOGD_OFF_SIZE_MARKER_VALUE; + } + LocalBroadcastManager.getInstance(mContext).sendBroadcastSync( + new Intent(ACTION_LOGD_SIZE_UPDATED) + .putExtra(EXTRA_CURRENT_LOGD_VALUE, currentValue)); + if ((currentValue == null) || (currentValue.length() == 0)) { + currentValue = defaultLogdSizeValue(); + } + String[] values = mContext.getResources() + .getStringArray(R.array.select_logd_size_values); + String[] titles = mContext.getResources() + .getStringArray(R.array.select_logd_size_titles); + int index = 2; // punt to second entry if not found + if (SystemProperties.get("ro.config.low_ram").equals("true")) { + mLogdSize.setEntries(R.array.select_logd_size_lowram_titles); + titles = mContext.getResources() + .getStringArray(R.array.select_logd_size_lowram_titles); + index = 1; + } + String[] summaries = mContext.getResources() + .getStringArray(R.array.select_logd_size_summaries); + for (int i = 0; i < titles.length; i++) { + if (currentValue.equals(values[i]) + || currentValue.equals(titles[i])) { + index = i; + break; + } + } + mLogdSize.setValue(values[index]); + mLogdSize.setSummary(summaries[index]); + } + } + + public void writeLogdSizeOption(Object newValue) { + boolean disable = (newValue != null) && + (newValue.toString().equals(SELECT_LOGD_OFF_SIZE_MARKER_VALUE)); + String currentTag = SystemProperties.get(SELECT_LOGD_TAG_PROPERTY); + if (currentTag == null) { + currentTag = ""; + } + // filter clean and unstack all references to our setting + String newTag = currentTag.replaceAll( + ",+" + SELECT_LOGD_TAG_SILENCE, "").replaceFirst( + "^" + SELECT_LOGD_TAG_SILENCE + ",*", "").replaceAll( + ",+", ",").replaceFirst( + ",+$", ""); + if (disable) { + newValue = SELECT_LOGD_MINIMUM_SIZE_VALUE; + // Make sure snet_event_log get through first, but do not override + String snetValue = SystemProperties.get(SELECT_LOGD_SNET_TAG_PROPERTY); + if ((snetValue == null) || (snetValue.length() == 0)) { + snetValue = SystemProperties.get(SELECT_LOGD_RUNTIME_SNET_TAG_PROPERTY); + if ((snetValue == null) || (snetValue.length() == 0)) { + SystemProperties.set(SELECT_LOGD_SNET_TAG_PROPERTY, DEFAULT_SNET_TAG); + } + } + // Silence all log sources, security logs notwithstanding + if (newTag.length() != 0) { + newTag = "," + newTag; + } + // Stack settings, stack to help preserve original value + newTag = SELECT_LOGD_TAG_SILENCE + newTag; + } + if (!newTag.equals(currentTag)) { + SystemProperties.set(SELECT_LOGD_TAG_PROPERTY, newTag); + } + String defaultValue = defaultLogdSizeValue(); + final String size = ((newValue != null) && (newValue.toString().length() != 0)) ? + newValue.toString() : defaultValue; + SystemProperties.set(SELECT_LOGD_SIZE_PROPERTY, defaultValue.equals(size) ? "" : size); + SystemProperties.set("ctl.start", "logd-reinit"); + SystemPropPoker.getInstance().poke(); + updateLogdSizeValues(); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogpersistPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogpersistPreferenceController.java new file mode 100644 index 000000000000..77b2d86c445f --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractLogpersistPreferenceController.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2017 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.settingslib.development; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.os.SystemProperties; +import android.support.annotation.VisibleForTesting; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v7.preference.ListPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; + +import com.android.settingslib.R; +import com.android.settingslib.core.ConfirmationDialogController; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnCreate; +import com.android.settingslib.core.lifecycle.events.OnDestroy; + +public abstract class AbstractLogpersistPreferenceController extends + DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener, + LifecycleObserver, OnCreate, OnDestroy, ConfirmationDialogController { + + private static final String SELECT_LOGPERSIST_KEY = "select_logpersist"; + private static final String SELECT_LOGPERSIST_PROPERTY = "persist.logd.logpersistd"; + @VisibleForTesting + static final String ACTUAL_LOGPERSIST_PROPERTY = "logd.logpersistd"; + @VisibleForTesting + static final String SELECT_LOGPERSIST_PROPERTY_SERVICE = "logcatd"; + private static final String SELECT_LOGPERSIST_PROPERTY_CLEAR = "clear"; + private static final String SELECT_LOGPERSIST_PROPERTY_STOP = "stop"; + private static final String SELECT_LOGPERSIST_PROPERTY_BUFFER = + "persist.logd.logpersistd.buffer"; + @VisibleForTesting + static final String ACTUAL_LOGPERSIST_PROPERTY_BUFFER = "logd.logpersistd.buffer"; + private static final String ACTUAL_LOGPERSIST_PROPERTY_ENABLE = "logd.logpersistd.enable"; + + private ListPreference mLogpersist; + private boolean mLogpersistCleared; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String currentValue = intent.getStringExtra( + AbstractLogdSizePreferenceController.EXTRA_CURRENT_LOGD_VALUE); + onLogdSizeSettingUpdate(currentValue); + } + }; + + public AbstractLogpersistPreferenceController(Context context, Lifecycle lifecycle) { + super(context); + if (isAvailable() && lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public boolean isAvailable() { + return TextUtils.equals(SystemProperties.get("ro.debuggable", "0"), "1"); + } + + @Override + public String getPreferenceKey() { + return SELECT_LOGPERSIST_KEY; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + if (isAvailable()) { + mLogpersist = (ListPreference) screen.findPreference(SELECT_LOGPERSIST_KEY); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == mLogpersist) { + writeLogpersistOption(newValue, false); + return true; + } else { + return false; + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + LocalBroadcastManager.getInstance(mContext).registerReceiver(mReceiver, + new IntentFilter(AbstractLogdSizePreferenceController.ACTION_LOGD_SIZE_UPDATED)); + } + + @Override + public void onDestroy() { + LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mReceiver); + } + + public void enablePreference(boolean enabled) { + if (isAvailable()) { + mLogpersist.setEnabled(enabled); + } + } + + private void onLogdSizeSettingUpdate(String currentValue) { + if (mLogpersist != null) { + String currentLogpersistEnable + = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY_ENABLE); + if ((currentLogpersistEnable == null) + || !currentLogpersistEnable.equals("true") + || currentValue.equals( + AbstractLogdSizePreferenceController.SELECT_LOGD_OFF_SIZE_MARKER_VALUE)) { + writeLogpersistOption(null, true); + mLogpersist.setEnabled(false); + } else if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) { + mLogpersist.setEnabled(true); + } + } + } + + public void updateLogpersistValues() { + if (mLogpersist == null) { + return; + } + String currentValue = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY); + if (currentValue == null) { + currentValue = ""; + } + String currentBuffers = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY_BUFFER); + if ((currentBuffers == null) || (currentBuffers.length() == 0)) { + currentBuffers = "all"; + } + int index = 0; + if (currentValue.equals(SELECT_LOGPERSIST_PROPERTY_SERVICE)) { + index = 1; + if (currentBuffers.equals("kernel")) { + index = 3; + } else if (!currentBuffers.equals("all") && + !currentBuffers.contains("radio") && + currentBuffers.contains("security") && + currentBuffers.contains("kernel")) { + index = 2; + if (!currentBuffers.contains("default")) { + String[] contains = {"main", "events", "system", "crash"}; + for (String type : contains) { + if (!currentBuffers.contains(type)) { + index = 1; + break; + } + } + } + } + } + mLogpersist.setValue( + mContext.getResources().getStringArray(R.array.select_logpersist_values)[index]); + mLogpersist.setSummary( + mContext.getResources().getStringArray(R.array.select_logpersist_summaries)[index]); + if (index != 0) { + mLogpersistCleared = false; + } else if (!mLogpersistCleared) { + // would File.delete() directly but need to switch uid/gid to access + SystemProperties.set(ACTUAL_LOGPERSIST_PROPERTY, SELECT_LOGPERSIST_PROPERTY_CLEAR); + SystemPropPoker.getInstance().poke(); + mLogpersistCleared = true; + } + } + + protected void setLogpersistOff(boolean update) { + SystemProperties.set(SELECT_LOGPERSIST_PROPERTY_BUFFER, ""); + // deal with trampoline of empty properties + SystemProperties.set(ACTUAL_LOGPERSIST_PROPERTY_BUFFER, ""); + SystemProperties.set(SELECT_LOGPERSIST_PROPERTY, ""); + SystemProperties.set(ACTUAL_LOGPERSIST_PROPERTY, + update ? "" : SELECT_LOGPERSIST_PROPERTY_STOP); + SystemPropPoker.getInstance().poke(); + if (update) { + updateLogpersistValues(); + } else { + for (int i = 0; i < 3; i++) { + String currentValue = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY); + if ((currentValue == null) || currentValue.equals("")) { + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // Ignore + } + } + } + } + + public void writeLogpersistOption(Object newValue, boolean skipWarning) { + if (mLogpersist == null) { + return; + } + String currentTag = SystemProperties.get( + AbstractLogdSizePreferenceController.SELECT_LOGD_TAG_PROPERTY); + if ((currentTag != null) && currentTag.startsWith( + AbstractLogdSizePreferenceController.SELECT_LOGD_TAG_SILENCE)) { + newValue = null; + skipWarning = true; + } + + if ((newValue == null) || newValue.toString().equals("")) { + if (skipWarning) { + mLogpersistCleared = false; + } else if (!mLogpersistCleared) { + // if transitioning from on to off, pop up an are you sure? + String currentValue = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY); + if ((currentValue != null) && + currentValue.equals(SELECT_LOGPERSIST_PROPERTY_SERVICE)) { + showConfirmationDialog(mLogpersist); + return; + } + } + setLogpersistOff(true); + return; + } + + String currentBuffer = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY_BUFFER); + if ((currentBuffer != null) && !currentBuffer.equals(newValue.toString())) { + setLogpersistOff(false); + } + SystemProperties.set(SELECT_LOGPERSIST_PROPERTY_BUFFER, newValue.toString()); + SystemProperties.set(SELECT_LOGPERSIST_PROPERTY, SELECT_LOGPERSIST_PROPERTY_SERVICE); + SystemPropPoker.getInstance().poke(); + for (int i = 0; i < 3; i++) { + String currentValue = SystemProperties.get(ACTUAL_LOGPERSIST_PROPERTY); + if ((currentValue != null) + && currentValue.equals(SELECT_LOGPERSIST_PROPERTY_SERVICE)) { + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // Ignore + } + } + updateLogpersistValues(); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java new file mode 100644 index 000000000000..f68c04f91dbf --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/development/DeveloperOptionsPreferenceController.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 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.settingslib.development; + +import android.content.Context; + +import com.android.settingslib.core.AbstractPreferenceController; + +/** + * This controller is used handle changes for the master switch in the developer options page. + * + * All Preference Controllers that are a part of the developer options page should inherit this + * class. + */ +public abstract class DeveloperOptionsPreferenceController extends + AbstractPreferenceController { + + public DeveloperOptionsPreferenceController(Context context) { + super(context); + } + + /** + * Child classes should override this method to create custom logic for hiding preferences. + * + * @return true if the preference is to be displayed. + */ + @Override + public boolean isAvailable() { + return true; + } + + /** + * Called when developer options is enabled + */ + public void onDeveloperOptionsEnabled() { + if (isAvailable()) { + onDeveloperOptionsSwitchEnabled(); + } + } + + /** + * Called when developer options is disabled + */ + public void onDeveloperOptionsDisabled() { + if (isAvailable()) { + onDeveloperOptionsSwitchDisabled(); + } + } + + /** + * Called when developer options is enabled and the preference is available + */ + protected void onDeveloperOptionsSwitchEnabled() { + } + + /** + * Called when developer options is disabled and the preference is available + */ + protected void onDeveloperOptionsSwitchDisabled() { + } + +} diff --git a/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java b/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java new file mode 100644 index 000000000000..4e78d9b3d33c --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/development/DevelopmentSettingsEnabler.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 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.settingslib.development; + +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.provider.Settings; +import android.support.v4.content.LocalBroadcastManager; + +public class DevelopmentSettingsEnabler { + + public static final String DEVELOPMENT_SETTINGS_CHANGED_ACTION = + "com.android.settingslib.development.DevelopmentSettingsEnabler.SETTINGS_CHANGED"; + + private DevelopmentSettingsEnabler() {} + + public static void setDevelopmentSettingsEnabled(Context context, boolean enable) { + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, enable ? 1 : 0); + LocalBroadcastManager.getInstance(context) + .sendBroadcast(new Intent(DEVELOPMENT_SETTINGS_CHANGED_ACTION)); + } + + public static boolean isDevelopmentSettingsEnabled(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, + Build.TYPE.equals("eng") ? 1 : 0) != 0; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/development/SystemPropPoker.java b/packages/SettingsLib/src/com/android/settingslib/development/SystemPropPoker.java new file mode 100644 index 000000000000..628d0d08b526 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/development/SystemPropPoker.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 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.settingslib.development; + +import android.os.AsyncTask; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import android.util.Log; + +public class SystemPropPoker { + private static final String TAG = "SystemPropPoker"; + + private static final SystemPropPoker sInstance = new SystemPropPoker(); + + private boolean mBlockPokes = false; + + private SystemPropPoker() {} + + @NonNull + public static SystemPropPoker getInstance() { + return sInstance; + } + + public void blockPokes() { + mBlockPokes = true; + } + + public void unblockPokes() { + mBlockPokes = false; + } + + public void poke() { + if (!mBlockPokes) { + createPokerTask().execute(); + } + } + + @VisibleForTesting + PokerTask createPokerTask() { + return new PokerTask(); + } + + public static class PokerTask extends AsyncTask<Void, Void, Void> { + + @VisibleForTesting + String[] listServices() { + return ServiceManager.listServices(); + } + + @VisibleForTesting + IBinder checkService(String service) { + return ServiceManager.checkService(service); + } + + @Override + protected Void doInBackground(Void... params) { + String[] services = listServices(); + if (services == null) { + Log.e(TAG, "There are no services, how odd"); + return null; + } + for (String service : services) { + IBinder obj = checkService(service); + if (obj != null) { + Parcel data = Parcel.obtain(); + try { + obj.transact(IBinder.SYSPROPS_TRANSACTION, data, null, 0); + } catch (RemoteException e) { + // Ignore + } catch (Exception e) { + Log.i(TAG, "Someone wrote a bad service '" + service + + "' that doesn't like to be poked", e); + } + data.recycle(); + } + } + return null; + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java new file mode 100644 index 000000000000..ba358f83c9d5 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractBluetoothAddressPreferenceController.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017 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.settingslib.deviceinfo; + +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; + +import com.android.settingslib.R; +import com.android.settingslib.core.lifecycle.Lifecycle; + +/** + * Preference controller for bluetooth address + */ +public abstract class AbstractBluetoothAddressPreferenceController + extends AbstractConnectivityPreferenceController { + + @VisibleForTesting + static final String KEY_BT_ADDRESS = "bt_address"; + + private static final String[] CONNECTIVITY_INTENTS = { + BluetoothAdapter.ACTION_STATE_CHANGED + }; + + private Preference mBtAddress; + + public AbstractBluetoothAddressPreferenceController(Context context, Lifecycle lifecycle) { + super(context, lifecycle); + } + + @Override + public boolean isAvailable() { + return BluetoothAdapter.getDefaultAdapter() != null; + } + + @Override + public String getPreferenceKey() { + return KEY_BT_ADDRESS; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mBtAddress = screen.findPreference(KEY_BT_ADDRESS); + updateConnectivity(); + } + + @Override + protected String[] getConnectivityIntents() { + return CONNECTIVITY_INTENTS; + } + + @SuppressLint("HardwareIds") + @Override + protected void updateConnectivity() { + BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter(); + if (bluetooth != null && mBtAddress != null) { + String address = bluetooth.isEnabled() ? bluetooth.getAddress() : null; + if (!TextUtils.isEmpty(address)) { + // Convert the address to lowercase for consistency with the wifi MAC address. + mBtAddress.setSummary(address.toLowerCase()); + } else { + mBtAddress.setSummary(R.string.status_unavailable); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java new file mode 100644 index 000000000000..c6552f77a2b2 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractConnectivityPreferenceController.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017 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.settingslib.deviceinfo; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.Message; + +import com.android.internal.util.ArrayUtils; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +import java.lang.ref.WeakReference; + +/** + * Base class for preference controllers which listen to connectivity broadcasts + */ +public abstract class AbstractConnectivityPreferenceController + extends AbstractPreferenceController implements LifecycleObserver, OnStart, OnStop { + + private final BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (ArrayUtils.contains(getConnectivityIntents(), action)) { + getHandler().sendEmptyMessage(EVENT_UPDATE_CONNECTIVITY); + } + } + }; + + private static final int EVENT_UPDATE_CONNECTIVITY = 600; + + private Handler mHandler; + + public AbstractConnectivityPreferenceController(Context context, Lifecycle lifecycle) { + super(context); + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public void onStop() { + mContext.unregisterReceiver(mConnectivityReceiver); + } + + @Override + public void onStart() { + final IntentFilter connectivityIntentFilter = new IntentFilter(); + final String[] intents = getConnectivityIntents(); + for (String intent : intents) { + connectivityIntentFilter.addAction(intent); + } + + mContext.registerReceiver(mConnectivityReceiver, connectivityIntentFilter, + android.Manifest.permission.CHANGE_NETWORK_STATE, null); + } + + protected abstract String[] getConnectivityIntents(); + + protected abstract void updateConnectivity(); + + private Handler getHandler() { + if (mHandler == null) { + mHandler = new ConnectivityEventHandler(this); + } + return mHandler; + } + + private static class ConnectivityEventHandler extends Handler { + private WeakReference<AbstractConnectivityPreferenceController> mPreferenceController; + + public ConnectivityEventHandler(AbstractConnectivityPreferenceController activity) { + mPreferenceController = new WeakReference<>(activity); + } + + @Override + public void handleMessage(Message msg) { + AbstractConnectivityPreferenceController preferenceController + = mPreferenceController.get(); + if (preferenceController == null) { + return; + } + + switch (msg.what) { + case EVENT_UPDATE_CONNECTIVITY: + preferenceController.updateConnectivity(); + break; + default: + throw new IllegalStateException("Unknown message " + msg.what); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java new file mode 100644 index 000000000000..bb8404b0abd5 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractImsStatusPreferenceController.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017 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.settingslib.deviceinfo; + +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; +import android.os.PersistableBundle; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; + +import com.android.settingslib.R; +import com.android.settingslib.core.lifecycle.Lifecycle; + +/** + * Preference controller for IMS status + */ +public abstract class AbstractImsStatusPreferenceController + extends AbstractConnectivityPreferenceController { + + @VisibleForTesting + static final String KEY_IMS_REGISTRATION_STATE = "ims_reg_state"; + + private static final String[] CONNECTIVITY_INTENTS = { + BluetoothAdapter.ACTION_STATE_CHANGED, + ConnectivityManager.CONNECTIVITY_ACTION, + WifiManager.LINK_CONFIGURATION_CHANGED_ACTION, + WifiManager.NETWORK_STATE_CHANGED_ACTION, + }; + + private Preference mImsStatus; + + public AbstractImsStatusPreferenceController(Context context, + Lifecycle lifecycle) { + super(context, lifecycle); + } + + @Override + public boolean isAvailable() { + CarrierConfigManager configManager = mContext.getSystemService(CarrierConfigManager.class); + int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + PersistableBundle config = null; + if (configManager != null) { + config = configManager.getConfigForSubId(subId); + } + return config != null && config.getBoolean( + CarrierConfigManager.KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL); + } + + @Override + public String getPreferenceKey() { + return KEY_IMS_REGISTRATION_STATE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mImsStatus = screen.findPreference(KEY_IMS_REGISTRATION_STATE); + updateConnectivity(); + } + + @Override + protected String[] getConnectivityIntents() { + return CONNECTIVITY_INTENTS; + } + + @Override + protected void updateConnectivity() { + int subId = SubscriptionManager.getDefaultDataSubscriptionId(); + if (mImsStatus != null) { + TelephonyManager tm = mContext.getSystemService(TelephonyManager.class); + mImsStatus.setSummary((tm != null && tm.isImsRegistered(subId)) ? + R.string.ims_reg_status_registered : R.string.ims_reg_status_not_registered); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java new file mode 100644 index 000000000000..ded30226e2ae --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractIpAddressPreferenceController.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2017 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.settingslib.deviceinfo; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.wifi.WifiManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settingslib.R; +import com.android.settingslib.core.lifecycle.Lifecycle; + +import java.net.InetAddress; +import java.util.Iterator; + +/** + * Preference controller for IP address + */ +public abstract class AbstractIpAddressPreferenceController + extends AbstractConnectivityPreferenceController { + + @VisibleForTesting + static final String KEY_IP_ADDRESS = "wifi_ip_address"; + + private static final String[] CONNECTIVITY_INTENTS = { + ConnectivityManager.CONNECTIVITY_ACTION, + WifiManager.LINK_CONFIGURATION_CHANGED_ACTION, + WifiManager.NETWORK_STATE_CHANGED_ACTION, + }; + + private Preference mIpAddress; + private final ConnectivityManager mCM; + + public AbstractIpAddressPreferenceController(Context context, Lifecycle lifecycle) { + super(context, lifecycle); + mCM = context.getSystemService(ConnectivityManager.class); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return KEY_IP_ADDRESS; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mIpAddress = screen.findPreference(KEY_IP_ADDRESS); + updateConnectivity(); + } + + @Override + protected String[] getConnectivityIntents() { + return CONNECTIVITY_INTENTS; + } + + @Override + protected void updateConnectivity() { + String ipAddress = getDefaultIpAddresses(mCM); + if (ipAddress != null) { + mIpAddress.setSummary(ipAddress); + } else { + mIpAddress.setSummary(R.string.status_unavailable); + } + } + + /** + * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style + * addresses. + * @param cm ConnectivityManager + * @return the formatted and newline-separated IP addresses, or null if none. + */ + private static String getDefaultIpAddresses(ConnectivityManager cm) { + LinkProperties prop = cm.getActiveLinkProperties(); + return formatIpAddresses(prop); + } + + private static String formatIpAddresses(LinkProperties prop) { + if (prop == null) return null; + Iterator<InetAddress> iter = prop.getAllAddresses().iterator(); + // If there are no entries, return null + if (!iter.hasNext()) return null; + // Concatenate all available addresses, newline separated + StringBuilder addresses = new StringBuilder(); + while (iter.hasNext()) { + addresses.append(iter.next().getHostAddress()); + if (iter.hasNext()) addresses.append("\n"); + } + return addresses.toString(); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java new file mode 100644 index 000000000000..90f14ef4c32c --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 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.settingslib.deviceinfo; + +import android.content.Context; +import android.os.Build; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; + +import com.android.settingslib.core.AbstractPreferenceController; + +/** + * Preference controller for displaying device serial number. Wraps {@link Build#getSerial()}. + */ +public class AbstractSerialNumberPreferenceController extends AbstractPreferenceController { + + @VisibleForTesting + static final String KEY_SERIAL_NUMBER = "serial_number"; + + private final String mSerialNumber; + + public AbstractSerialNumberPreferenceController(Context context) { + this(context, Build.getSerial()); + } + + @VisibleForTesting + AbstractSerialNumberPreferenceController(Context context, String serialNumber) { + super(context); + mSerialNumber = serialNumber; + } + + @Override + public boolean isAvailable() { + return !TextUtils.isEmpty(mSerialNumber); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + final Preference pref = screen.findPreference(KEY_SERIAL_NUMBER); + if (pref != null) { + pref.setSummary(mSerialNumber); + } + } + + @Override + public String getPreferenceKey() { + return KEY_SERIAL_NUMBER; + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java new file mode 100644 index 000000000000..a78440c271c9 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSimStatusImeiInfoPreferenceController.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 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.settingslib.deviceinfo; + +import android.content.Context; +import android.os.UserManager; + +import com.android.settingslib.Utils; +import com.android.settingslib.core.AbstractPreferenceController; + +public abstract class AbstractSimStatusImeiInfoPreferenceController + extends AbstractPreferenceController { + public AbstractSimStatusImeiInfoPreferenceController(Context context) { + super(context); + } + + @Override + public boolean isAvailable() { + return mContext.getSystemService(UserManager.class).isAdminUser() + && !Utils.isWifiOnly(mContext); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java new file mode 100644 index 000000000000..ac61ade19222 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractUptimePreferenceController.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017 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.settingslib.deviceinfo; + +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.format.DateUtils; + +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; + +import java.lang.ref.WeakReference; + +/** + * Preference controller for uptime + */ +public abstract class AbstractUptimePreferenceController extends AbstractPreferenceController + implements LifecycleObserver, OnStart, OnStop { + + @VisibleForTesting + static final String KEY_UPTIME = "up_time"; + private static final int EVENT_UPDATE_STATS = 500; + + private Preference mUptime; + private Handler mHandler; + + public AbstractUptimePreferenceController(Context context, Lifecycle lifecycle) { + super(context); + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public void onStart() { + getHandler().sendEmptyMessage(EVENT_UPDATE_STATS); + } + + @Override + public void onStop() { + getHandler().removeMessages(EVENT_UPDATE_STATS); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return KEY_UPTIME; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mUptime = screen.findPreference(KEY_UPTIME); + updateTimes(); + } + + private Handler getHandler() { + if (mHandler == null) { + mHandler = new MyHandler(this); + } + return mHandler; + } + + private void updateTimes() { + mUptime.setSummary(DateUtils.formatDuration(SystemClock.elapsedRealtime())); + } + + private static class MyHandler extends Handler { + private WeakReference<AbstractUptimePreferenceController> mStatus; + + public MyHandler(AbstractUptimePreferenceController activity) { + mStatus = new WeakReference<>(activity); + } + + @Override + public void handleMessage(Message msg) { + AbstractUptimePreferenceController status = mStatus.get(); + if (status == null) { + return; + } + + switch (msg.what) { + case EVENT_UPDATE_STATS: + status.updateTimes(); + sendEmptyMessageDelayed(EVENT_UPDATE_STATS, 1000); + break; + + default: + throw new IllegalStateException("Unknown message " + msg.what); + } + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java new file mode 100644 index 000000000000..d57b64f0c0cb --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractWifiMacAddressPreferenceController.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2017 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.settingslib.deviceinfo; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.text.TextUtils; + +import com.android.settingslib.R; +import com.android.settingslib.core.lifecycle.Lifecycle; + +/** + * Preference controller for WIFI MAC address + */ +public abstract class AbstractWifiMacAddressPreferenceController + extends AbstractConnectivityPreferenceController { + + @VisibleForTesting + static final String KEY_WIFI_MAC_ADDRESS = "wifi_mac_address"; + + private static final String[] CONNECTIVITY_INTENTS = { + ConnectivityManager.CONNECTIVITY_ACTION, + WifiManager.LINK_CONFIGURATION_CHANGED_ACTION, + WifiManager.NETWORK_STATE_CHANGED_ACTION, + }; + + private Preference mWifiMacAddress; + private final WifiManager mWifiManager; + + public AbstractWifiMacAddressPreferenceController(Context context, Lifecycle lifecycle) { + super(context, lifecycle); + mWifiManager = context.getSystemService(WifiManager.class); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return KEY_WIFI_MAC_ADDRESS; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mWifiMacAddress = screen.findPreference(KEY_WIFI_MAC_ADDRESS); + updateConnectivity(); + } + + @Override + protected String[] getConnectivityIntents() { + return CONNECTIVITY_INTENTS; + } + + @SuppressLint("HardwareIds") + @Override + protected void updateConnectivity() { + WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); + String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress(); + if (!TextUtils.isEmpty(macAddress)) { + mWifiMacAddress.setSummary(macAddress); + } else { + mWifiMacAddress.setSummary(R.string.status_unavailable); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java index ee7885d2a077..07033304653c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java @@ -18,7 +18,6 @@ package com.android.settingslib.drawer; import android.content.ComponentName; import android.content.Context; import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -27,7 +26,6 @@ import android.util.Pair; import com.android.settingslib.applications.InterestingConfigChanges; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -104,10 +102,10 @@ public class CategoryManager { } for (int i = 0; i < mCategories.size(); i++) { DashboardCategory category = mCategories.get(i); - for (int j = 0; j < category.tiles.size(); j++) { - Tile tile = category.tiles.get(j); + for (int j = 0; j < category.getTilesCount(); j++) { + Tile tile = category.getTile(j); if (tileBlacklist.contains(tile.intent.getComponent())) { - category.tiles.remove(j--); + category.removeTile(j--); } } } @@ -181,7 +179,7 @@ public class CategoryManager { newCategory = new DashboardCategory(); categoryByKeyMap.put(newCategoryKey, newCategory); } - newCategory.tiles.add(tile); + newCategory.addTile(tile); } } } @@ -198,7 +196,7 @@ public class CategoryManager { synchronized void sortCategories(Context context, Map<String, DashboardCategory> categoryByKeyMap) { for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) { - sortCategoriesForExternalTiles(context, categoryEntry.getValue()); + categoryEntry.getValue().sortTiles(context.getPackageName()); } } @@ -210,16 +208,16 @@ public class CategoryManager { synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) { for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) { final DashboardCategory category = categoryEntry.getValue(); - final int count = category.tiles.size(); + final int count = category.getTilesCount(); final Set<ComponentName> components = new ArraySet<>(); for (int i = count - 1; i >= 0; i--) { - final Tile tile = category.tiles.get(i); + final Tile tile = category.getTile(i); if (tile.intent == null) { continue; } final ComponentName tileComponent = tile.intent.getComponent(); if (components.contains(tileComponent)) { - category.tiles.remove(i); + category.removeTile(i); } else { components.add(tileComponent); } @@ -234,28 +232,7 @@ public class CategoryManager { */ private synchronized void sortCategoriesForExternalTiles(Context context, DashboardCategory dashboardCategory) { - final String skipPackageName = context.getPackageName(); + dashboardCategory.sortTiles(context.getPackageName()); - // Sort tiles based on [priority, package within priority] - Collections.sort(dashboardCategory.tiles, (tile1, tile2) -> { - final String package1 = tile1.intent.getComponent().getPackageName(); - final String package2 = tile2.intent.getComponent().getPackageName(); - final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2); - // First sort by priority - final int priorityCompare = tile2.priority - tile1.priority; - if (priorityCompare != 0) { - return priorityCompare; - } - // Then sort by package name, skip package take precedence - if (packageCompare != 0) { - if (TextUtils.equals(package1, skipPackageName)) { - return -1; - } - if (TextUtils.equals(package2, skipPackageName)) { - return 1; - } - } - return packageCompare; - }); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java index f6f81682ad6f..3a03644b6226 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java @@ -16,6 +16,8 @@ package com.android.settingslib.drawer; +import static java.lang.String.CASE_INSENSITIVE_ORDER; + import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; @@ -23,6 +25,8 @@ import android.text.TextUtils; import android.util.Log; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; public class DashboardCategory implements Parcelable { @@ -48,39 +52,63 @@ public class DashboardCategory implements Parcelable { /** * List of the category's children */ - public List<Tile> tiles = new ArrayList<>(); - + private List<Tile> mTiles = new ArrayList<>(); + + DashboardCategory(DashboardCategory in) { + if (in != null) { + title = in.title; + key = in.key; + priority = in.priority; + for (Tile tile : in.mTiles) { + mTiles.add(tile); + } + } + } public DashboardCategory() { // Empty } - public void addTile(Tile tile) { - tiles.add(tile); + /** + * Get a copy of the list of the category's children. + * + * Note: the returned list serves as a read-only list. If tiles needs to be added or removed + * from the actual tiles list, it should be done through {@link #addTile}, {@link #removeTile}. + */ + public synchronized List<Tile> getTiles() { + final List<Tile> result = new ArrayList<>(mTiles.size()); + for (Tile tile : mTiles) { + result.add(tile); + } + return result; + } + + public synchronized void addTile(Tile tile) { + mTiles.add(tile); } - public void addTile(int n, Tile tile) { - tiles.add(n, tile); + public synchronized void addTile(int n, Tile tile) { + mTiles.add(n, tile); } - public void removeTile(Tile tile) { - tiles.remove(tile); + public synchronized void removeTile(Tile tile) { + mTiles.remove(tile); } - public void removeTile(int n) { - tiles.remove(n); + public synchronized void removeTile(int n) { + mTiles.remove(n); } public int getTilesCount() { - return tiles.size(); + return mTiles.size(); } public Tile getTile(int n) { - return tiles.get(n); + return mTiles.get(n); } - public boolean containsComponent(ComponentName component) { - for (Tile tile : tiles) { + public synchronized boolean containsComponent(ComponentName component) { + for (Tile tile : mTiles) { if (TextUtils.equals(tile.intent.getComponent().getClassName(), component.getClassName())) { if (DEBUG) { @@ -95,6 +123,40 @@ public class DashboardCategory implements Parcelable { return false; } + /** + * Sort priority value for tiles in this category. + */ + public void sortTiles() { + Collections.sort(mTiles, TILE_COMPARATOR); + } + + /** + * Sort priority value and package name for tiles in this category. + */ + public synchronized void sortTiles(String skipPackageName) { + // Sort mTiles based on [priority, package within priority] + Collections.sort(mTiles, (tile1, tile2) -> { + final String package1 = tile1.intent.getComponent().getPackageName(); + final String package2 = tile2.intent.getComponent().getPackageName(); + final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2); + // First sort by priority + final int priorityCompare = tile2.priority - tile1.priority; + if (priorityCompare != 0) { + return priorityCompare; + } + // Then sort by package name, skip package take precedence + if (packageCompare != 0) { + if (TextUtils.equals(package1, skipPackageName)) { + return -1; + } + if (TextUtils.equals(package2, skipPackageName)) { + return 1; + } + } + return packageCompare; + }); + } + @Override public int describeContents() { return 0; @@ -106,11 +168,11 @@ public class DashboardCategory implements Parcelable { dest.writeString(key); dest.writeInt(priority); - final int count = tiles.size(); + final int count = mTiles.size(); dest.writeInt(count); for (int n = 0; n < count; n++) { - Tile tile = tiles.get(n); + Tile tile = mTiles.get(n); tile.writeToParcel(dest, flags); } } @@ -124,7 +186,7 @@ public class DashboardCategory implements Parcelable { for (int n = 0; n < count; n++) { Tile tile = Tile.CREATOR.createFromParcel(in); - tiles.add(tile); + mTiles.add(tile); } } @@ -141,4 +203,13 @@ public class DashboardCategory implements Parcelable { return new DashboardCategory[size]; } }; + + public static final Comparator<Tile> TILE_COMPARATOR = + new Comparator<Tile>() { + @Override + public int compare(Tile lhs, Tile rhs) { + return rhs.priority - lhs.priority; + } + }; + } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java index 6e676bc4f460..e986e0f78de4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java @@ -65,7 +65,7 @@ public class TileUtils { * * <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY} */ - private static final String EXTRA_SETTINGS_ACTION = + public static final String EXTRA_SETTINGS_ACTION = "com.android.settings.action.EXTRA_SETTINGS"; /** @@ -149,13 +149,6 @@ public class TileUtils { public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title"; /** - * @deprecated Use {@link #META_DATA_PREFERENCE_TITLE} with {@code android:resource} - */ - @Deprecated - public static final String META_DATA_PREFERENCE_TITLE_RES_ID = - "com.android.settings.title.resid"; - - /** * Name of the meta-data item that should be set in the AndroidManifest.xml * to specify the summary text that should be displayed for the preference. */ @@ -176,7 +169,7 @@ public class TileUtils { * custom view which should be displayed for the preference. The custom view will be inflated * as a remote view. * - * This also can be used with {@link META_DATA_PREFERENCE_SUMMARY_URI} above, by setting the id + * This also can be used with {@link #META_DATA_PREFERENCE_SUMMARY_URI}, by setting the id * of the summary TextView to '@android:id/summary'. */ public static final String META_DATA_PREFERENCE_CUSTOM_VIEW = @@ -260,7 +253,7 @@ public class TileUtils { } ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values()); for (DashboardCategory category : categories) { - Collections.sort(category.tiles, TILE_COMPARATOR); + category.sortTiles(); } Collections.sort(categories, CATEGORY_COMPARATOR); if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took " @@ -421,14 +414,7 @@ public class TileUtils { metaData.getBoolean(META_DATA_PREFERENCE_ICON_TINTABLE); } } - int resId = 0; - if (metaData.containsKey(META_DATA_PREFERENCE_TITLE_RES_ID)) { - resId = metaData.getInt(META_DATA_PREFERENCE_TITLE_RES_ID); - if (resId != 0) { - title = res.getString(resId); - } - } - if ((resId == 0) && metaData.containsKey(META_DATA_PREFERENCE_TITLE)) { + if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) { if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) { title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE)); } else { @@ -466,12 +452,14 @@ public class TileUtils { } // Set the icon - if (iconFromUri != null) { - tile.icon = Icon.createWithResource(iconFromUri.first, iconFromUri.second); - } else { - if (icon == 0) { + if (icon == 0) { + // Only fallback to activityinfo.icon if metadata does not contain ICON_URI. + // ICON_URI should be loaded in app UI when need the icon object. + if (!tile.metaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) { icon = activityInfo.icon; } + } + if (icon != 0) { tile.icon = Icon.createWithResource(activityInfo.packageName, icon); } @@ -513,7 +501,7 @@ public class TileUtils { /** * Gets the icon package name and resource id from content provider. - * @param Context context + * @param context context * @param packageName package name of the target activity * @param uriString URI for the content provider * @param providerMap Maps URI authorities to providers @@ -543,7 +531,7 @@ public class TileUtils { /** * Gets text associated with the input key from the content provider. - * @param Context context + * @param context context * @param uriString URI for the content provider * @param providerMap Maps URI authorities to providers * @param key Key mapping to the text in bundle returned by the content provider @@ -607,14 +595,6 @@ public class TileUtils { return pathSegments.get(0); } - public static final Comparator<Tile> TILE_COMPARATOR = - new Comparator<Tile>() { - @Override - public int compare(Tile lhs, Tile rhs) { - return rhs.priority - lhs.priority; - } - }; - private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR = new Comparator<DashboardCategory>() { @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java index 750601d82ce5..8a09df2849c9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java @@ -57,14 +57,15 @@ public class UserAdapter implements SpinnerAdapter, ListAdapter { if (userInfo.isManagedProfile()) { mName = context.getString(R.string.managed_user_title); icon = context.getDrawable( - com.android.internal.R.drawable.ic_corp_icon); + com.android.internal.R.drawable.ic_corp_badge); } else { mName = userInfo.name; final int userId = userInfo.id; if (um.getUserIcon(userId) != null) { icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId)); } else { - icon = UserIcons.getDefaultUserIcon(userId, /* light= */ false); + icon = UserIcons.getDefaultUserIcon( + context.getResources(), userId, /* light= */ false); } } this.mIcon = encircle(context, icon); diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java index ec45b7e91700..4fe9d56a4179 100755 --- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java +++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java @@ -16,7 +16,6 @@ package com.android.settingslib.graph; -import android.animation.ArgbEvaluator; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; @@ -90,7 +89,6 @@ public class BatteryMeterDrawableBase extends Drawable { private final RectF mPlusFrame = new RectF(); private final Path mShapePath = new Path(); - private final Path mClipPath = new Path(); private final Path mTextPath = new Path(); public BatteryMeterDrawableBase(Context context, int frameColor) { @@ -101,7 +99,7 @@ public class BatteryMeterDrawableBase extends Drawable { final int N = levels.length(); mColors = new int[2 * N]; - for (int i=0; i < N; i++) { + for (int i = 0; i < N; i++) { mColors[2 * i] = levels.getInt(i, 0); if (colors.getType(i) == TypedValue.TYPE_ATTRIBUTE) { mColors[2 * i + 1] = Utils.getColorAttr(context, colors.getThemeAttributeId(i, 0)); @@ -416,8 +414,8 @@ public class BatteryMeterDrawableBase extends Drawable { : (mLevel == 100 ? 0.38f : 0.5f))); mTextHeight = -mTextPaint.getFontMetrics().ascent; pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level / 10) : level); - pctX = mWidth * 0.5f; - pctY = (mHeight + mTextHeight) * 0.47f; + pctX = mWidth * 0.5f + left; + pctY = (mHeight + mTextHeight) * 0.47f + top; pctOpaque = levelTop > pctY; if (!pctOpaque) { mTextPath.reset(); @@ -432,16 +430,16 @@ public class BatteryMeterDrawableBase extends Drawable { // draw the battery shape, clipped to charging level mFrame.top = levelTop; - mClipPath.reset(); - mClipPath.addRect(mFrame, Path.Direction.CCW); - mShapePath.op(mClipPath, Path.Op.INTERSECT); + c.save(); + c.clipRect(mFrame); c.drawPath(mShapePath, mBatteryPaint); + c.restore(); if (!mCharging && !mPowerSaveEnabled) { if (level <= mCriticalLevel) { // draw the warning text - final float x = mWidth * 0.5f; - final float y = (mHeight + mWarningTextHeight) * 0.48f; + final float x = mWidth * 0.5f + left; + final float y = (mHeight + mWarningTextHeight) * 0.48f + top; c.drawText(mWarningString, x, y, mWarningTextPaint); } else if (pctOpaque) { // draw the percentage text diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java index 1bbc878b56c9..5e25f519a130 100755..100644 --- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java @@ -34,6 +34,7 @@ import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import android.widget.Toast; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.InputMethodUtils; import com.android.settingslib.R; import com.android.settingslib.RestrictedLockUtils; @@ -91,20 +92,28 @@ public class InputMethodPreference extends RestrictedSwitchPreference implements public InputMethodPreference(final Context context, final InputMethodInfo imi, final boolean isImeEnabler, final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener) { + this(context, imi, imi.loadLabel(context.getPackageManager()), isAllowedByOrganization, + onSaveListener); + if (!isImeEnabler) { + // Remove switch widget. + setWidgetLayoutResource(NO_WIDGET); + } + } + + @VisibleForTesting + InputMethodPreference(final Context context, final InputMethodInfo imi, + final CharSequence title, final boolean isAllowedByOrganization, + final OnSavePreferenceListener onSaveListener) { super(context); setPersistent(false); mImi = imi; mIsAllowedByOrganization = isAllowedByOrganization; mOnSaveListener = onSaveListener; - if (!isImeEnabler) { - // Remove switch widget. - setWidgetLayoutResource(NO_WIDGET); - } // Disable on/off switch texts. setSwitchTextOn(EMPTY_TEXT); setSwitchTextOff(EMPTY_TEXT); setKey(imi.getId()); - setTitle(imi.loadLabel(context.getPackageManager())); + setTitle(title); final String settingsActivity = imi.getSettingsActivity(); if (TextUtils.isEmpty(settingsActivity)) { setIntent(null); @@ -283,18 +292,18 @@ public class InputMethodPreference extends RestrictedSwitchPreference implements if (this == rhs) { return 0; } - if (mHasPriorityInSorting == rhs.mHasPriorityInSorting) { - final CharSequence t0 = getTitle(); - final CharSequence t1 = rhs.getTitle(); - if (TextUtils.isEmpty(t0)) { - return 1; - } - if (TextUtils.isEmpty(t1)) { - return -1; - } - return collator.compare(t0.toString(), t1.toString()); + if (mHasPriorityInSorting != rhs.mHasPriorityInSorting) { + // Prefer always checked system IMEs + return mHasPriorityInSorting ? -1 : 1; + } + final CharSequence title = getTitle(); + final CharSequence rhsTitle = rhs.getTitle(); + final boolean emptyTitle = TextUtils.isEmpty(title); + final boolean rhsEmptyTitle = TextUtils.isEmpty(rhsTitle); + if (!emptyTitle && !rhsEmptyTitle) { + return collator.compare(title.toString(), rhsTitle.toString()); } - // Prefer always checked system IMEs - return mHasPriorityInSorting ? -1 : 1; + // For historical reasons, an empty text needs to be put at the first. + return (emptyTitle ? -1 : 0) - (rhsEmptyTitle ? -1 : 0); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java index 5fdab2967d1e..f824ec75968b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSubtypePreference.java @@ -17,12 +17,12 @@ package com.android.settingslib.inputmethod; import android.content.Context; -import android.support.v14.preference.SwitchPreference; import android.support.v7.preference.Preference; import android.text.TextUtils; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.InputMethodUtils; import java.text.Collator; @@ -39,18 +39,28 @@ public class InputMethodSubtypePreference extends SwitchWithNoTextPreference { public InputMethodSubtypePreference(final Context context, final InputMethodSubtype subtype, final InputMethodInfo imi) { + this(context, + imi.getId() + subtype.hashCode(), + InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(subtype, context, imi), + subtype.getLocale(), + context.getResources().getConfiguration().locale); + } + + @VisibleForTesting + InputMethodSubtypePreference( + final Context context, + final String prefKey, + final CharSequence title, + final String subtypeLocaleString, + final Locale systemLocale) { super(context); setPersistent(false); - setKey(imi.getId() + subtype.hashCode()); - final CharSequence subtypeLabel = - InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(subtype, context, imi); - setTitle(subtypeLabel); - final String subtypeLocaleString = subtype.getLocale(); + setKey(prefKey); + setTitle(title); if (TextUtils.isEmpty(subtypeLocaleString)) { mIsSystemLocale = false; mIsSystemLanguage = false; } else { - final Locale systemLocale = context.getResources().getConfiguration().locale; mIsSystemLocale = subtypeLocaleString.equals(systemLocale.toString()); mIsSystemLanguage = mIsSystemLocale || InputMethodUtils.getLanguageFromLocaleString(subtypeLocaleString) @@ -76,15 +86,15 @@ public class InputMethodSubtypePreference extends SwitchWithNoTextPreference { if (!mIsSystemLanguage && rhsPref.mIsSystemLanguage) { return 1; } - final CharSequence t0 = getTitle(); - final CharSequence t1 = rhs.getTitle(); - if (t0 == null && t1 == null) { - return Integer.compare(hashCode(), rhs.hashCode()); - } - if (t0 != null && t1 != null) { - return collator.compare(t0.toString(), t1.toString()); + final CharSequence title = getTitle(); + final CharSequence rhsTitle = rhs.getTitle(); + final boolean emptyTitle = TextUtils.isEmpty(title); + final boolean rhsEmptyTitle = TextUtils.isEmpty(rhsTitle); + if (!emptyTitle && !rhsEmptyTitle) { + return collator.compare(title.toString(), rhsTitle.toString()); } - return t0 == null ? -1 : 1; + // For historical reasons, an empty text needs to be put at the first. + return (emptyTitle ? -1 : 0) - (rhsEmptyTitle ? -1 : 0); } return super.compareTo(rhs); } diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS b/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS new file mode 100644 index 000000000000..a0e28baee3f6 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/OWNERS @@ -0,0 +1,5 @@ +# Default reviewers for this and subdirectories. +takaoka@google.com +yukawa@google.com + +# Emergency approvers in case the above are not available
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java new file mode 100644 index 000000000000..e3e27ce17c9a --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2017 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.settingslib.license; + +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; +import android.util.Log; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +/** + * The utility class that generate a license html file from xml files. + * All the HTML snippets and logic are copied from build/make/tools/generate-notice-files.py. + * + * TODO: Remove duplicate codes once backward support ends. + */ +class LicenseHtmlGeneratorFromXml { + private static final String TAG = "LicenseHtmlGeneratorFromXml"; + + private static final String TAG_ROOT = "licenses"; + private static final String TAG_FILE_NAME = "file-name"; + private static final String TAG_FILE_CONTENT = "file-content"; + private static final String ATTR_CONTENT_ID = "contentId"; + + private static final String HTML_HEAD_STRING = + "<html><head>\n" + + "<style type=\"text/css\">\n" + + "body { padding: 0; font-family: sans-serif; }\n" + + ".same-license { background-color: #eeeeee;\n" + + " border-top: 20px solid white;\n" + + " padding: 10px; }\n" + + ".label { font-weight: bold; }\n" + + ".file-list { margin-left: 1em; color: blue; }\n" + + "</style>\n" + + "</head>" + + "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">\n" + + "<div class=\"toc\">\n" + + "<ul>"; + + private static final String HTML_MIDDLE_STRING = + "</ul>\n" + + "</div><!-- table of contents -->\n" + + "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">"; + + private static final String HTML_REAR_STRING = + "</table></body></html>"; + + private final List<File> mXmlFiles; + + /* + * A map from a file name to a content id (MD5 sum of file content) for its license. + * For example, "/system/priv-app/TeleService/TeleService.apk" maps to + * "9645f39e9db895a4aa6e02cb57294595". Here "9645f39e9db895a4aa6e02cb57294595" is a MD5 sum + * of the content of packages/services/Telephony/MODULE_LICENSE_APACHE2. + */ + private final Map<String, String> mFileNameToContentIdMap = new HashMap(); + + /* + * A map from a content id (MD5 sum of file content) to a license file content. + * For example, "9645f39e9db895a4aa6e02cb57294595" maps to the content string of + * packages/services/Telephony/MODULE_LICENSE_APACHE2. Here "9645f39e9db895a4aa6e02cb57294595" + * is a MD5 sum of the file content. + */ + private final Map<String, String> mContentIdToFileContentMap = new HashMap(); + + static class ContentIdAndFileNames { + final String mContentId; + final List<String> mFileNameList = new ArrayList(); + + ContentIdAndFileNames(String contentId) { + mContentId = contentId; + } + } + + private LicenseHtmlGeneratorFromXml(List<File> xmlFiles) { + mXmlFiles = xmlFiles; + } + + public static boolean generateHtml(List<File> xmlFiles, File outputFile) { + LicenseHtmlGeneratorFromXml genertor = new LicenseHtmlGeneratorFromXml(xmlFiles); + return genertor.generateHtml(outputFile); + } + + private boolean generateHtml(File outputFile) { + for (File xmlFile : mXmlFiles) { + parse(xmlFile); + } + + if (mFileNameToContentIdMap.isEmpty() || mContentIdToFileContentMap.isEmpty()) { + return false; + } + + PrintWriter writer = null; + try { + writer = new PrintWriter(outputFile); + + generateHtml(mFileNameToContentIdMap, mContentIdToFileContentMap, writer); + + writer.flush(); + writer.close(); + return true; + } catch (FileNotFoundException | SecurityException e) { + Log.e(TAG, "Failed to generate " + outputFile, e); + + if (writer != null) { + writer.close(); + } + return false; + } + } + + private void parse(File xmlFile) { + if (xmlFile == null || !xmlFile.exists() || xmlFile.length() == 0) { + return; + } + + InputStreamReader in = null; + try { + if (xmlFile.getName().endsWith(".gz")) { + in = new InputStreamReader(new GZIPInputStream(new FileInputStream(xmlFile))); + } else { + in = new FileReader(xmlFile); + } + + parse(in, mFileNameToContentIdMap, mContentIdToFileContentMap); + + in.close(); + } catch (XmlPullParserException | IOException e) { + Log.e(TAG, "Failed to parse " + xmlFile, e); + if (in != null) { + try { + in.close(); + } catch (IOException ie) { + Log.w(TAG, "Failed to close " + xmlFile); + } + } + } + } + + /* + * Parses an input stream and fills a map from a file name to a content id for its license + * and a map from a content id to a license file content. + * + * Following xml format is expected from the input stream. + * + * <licenses> + * <file-name contentId="content_id_of_license1">file1</file-name> + * <file-name contentId="content_id_of_license2">file2</file-name> + * ... + * <file-content contentId="content_id_of_license1">license1 file contents</file-content> + * <file-content contentId="content_id_of_license2">license2 file contents</file-content> + * ... + * </licenses> + */ + @VisibleForTesting + static void parse(InputStreamReader in, Map<String, String> outFileNameToContentIdMap, + Map<String, String> outContentIdToFileContentMap) + throws XmlPullParserException, IOException { + Map<String, String> fileNameToContentIdMap = new HashMap<String, String>(); + Map<String, String> contentIdToFileContentMap = new HashMap<String, String>(); + + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in); + parser.nextTag(); + + parser.require(XmlPullParser.START_TAG, "", TAG_ROOT); + + int state = parser.getEventType(); + while (state != XmlPullParser.END_DOCUMENT) { + if (state == XmlPullParser.START_TAG) { + if (TAG_FILE_NAME.equals(parser.getName())) { + String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID); + if (!TextUtils.isEmpty(contentId)) { + String fileName = readText(parser).trim(); + if (!TextUtils.isEmpty(fileName)) { + fileNameToContentIdMap.put(fileName, contentId); + } + } + } else if (TAG_FILE_CONTENT.equals(parser.getName())) { + String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID); + if (!TextUtils.isEmpty(contentId) + && !outContentIdToFileContentMap.containsKey(contentId) + && !contentIdToFileContentMap.containsKey(contentId)) { + String fileContent = readText(parser); + if (!TextUtils.isEmpty(fileContent)) { + contentIdToFileContentMap.put(contentId, fileContent); + } + } + } + } + + state = parser.next(); + } + outFileNameToContentIdMap.putAll(fileNameToContentIdMap); + outContentIdToFileContentMap.putAll(contentIdToFileContentMap); + } + + private static String readText(XmlPullParser parser) + throws IOException, XmlPullParserException { + StringBuffer result = new StringBuffer(); + int state = parser.next(); + while (state == XmlPullParser.TEXT) { + result.append(parser.getText()); + state = parser.next(); + } + return result.toString(); + } + + @VisibleForTesting + static void generateHtml(Map<String, String> fileNameToContentIdMap, + Map<String, String> contentIdToFileContentMap, PrintWriter writer) { + List<String> fileNameList = new ArrayList(); + fileNameList.addAll(fileNameToContentIdMap.keySet()); + Collections.sort(fileNameList); + + writer.println(HTML_HEAD_STRING); + + int count = 0; + Map<String, Integer> contentIdToOrderMap = new HashMap(); + List<ContentIdAndFileNames> contentIdAndFileNamesList = new ArrayList(); + + // Prints all the file list with a link to its license file content. + for (String fileName : fileNameList) { + String contentId = fileNameToContentIdMap.get(fileName); + // Assigns an id to a newly referred license file content. + if (!contentIdToOrderMap.containsKey(contentId)) { + contentIdToOrderMap.put(contentId, count); + + // An index in contentIdAndFileNamesList is the order of each element. + contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId)); + count++; + } + + int id = contentIdToOrderMap.get(contentId); + contentIdAndFileNamesList.get(id).mFileNameList.add(fileName); + writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName); + } + + writer.println(HTML_MIDDLE_STRING); + + count = 0; + // Prints all contents of the license files in order of id. + for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) { + writer.format("<tr id=\"id%d\"><td class=\"same-license\">\n", count); + writer.println("<div class=\"label\">Notices for file(s):</div>"); + writer.println("<div class=\"file-list\">"); + for (String fileName : contentIdAndFileNames.mFileNameList) { + writer.format("%s <br/>\n", fileName); + } + writer.println("</div><!-- file-list -->"); + writer.println("<pre class=\"license-text\">"); + writer.println(contentIdToFileContentMap.get( + contentIdAndFileNames.mContentId)); + writer.println("</pre><!-- license-text -->"); + writer.println("</td></tr><!-- same-license -->"); + + count++; + } + + writer.println(HTML_REAR_STRING); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java new file mode 100644 index 000000000000..a9fb20ca9e17 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017 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.settingslib.license; + +import android.content.Context; +import android.support.annotation.VisibleForTesting; +import android.util.Log; + +import com.android.settingslib.utils.AsyncLoader; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * LicenseHtmlLoader is a loader which loads a license html file from default license xml files. + */ +public class LicenseHtmlLoader extends AsyncLoader<File> { + private static final String TAG = "LicenseHtmlLoader"; + + private static final String[] DEFAULT_LICENSE_XML_PATHS = { + "/system/etc/NOTICE.xml.gz", + "/vendor/etc/NOTICE.xml.gz", + "/odm/etc/NOTICE.xml.gz", + "/oem/etc/NOTICE.xml.gz"}; + private static final String NOTICE_HTML_FILE_NAME = "NOTICE.html"; + + private Context mContext; + + public LicenseHtmlLoader(Context context) { + super(context); + mContext = context; + } + + @Override + public File loadInBackground() { + return generateHtmlFromDefaultXmlFiles(); + } + + @Override + protected void onDiscardResult(File f) { + } + + private File generateHtmlFromDefaultXmlFiles() { + final List<File> xmlFiles = getVaildXmlFiles(); + if (xmlFiles.isEmpty()) { + Log.e(TAG, "No notice file exists."); + return null; + } + + File cachedHtmlFile = getCachedHtmlFile(); + if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile) + || generateHtmlFile(xmlFiles, cachedHtmlFile)) { + return cachedHtmlFile; + } + + return null; + } + + @VisibleForTesting + List<File> getVaildXmlFiles() { + final List<File> xmlFiles = new ArrayList(); + for (final String xmlPath : DEFAULT_LICENSE_XML_PATHS) { + File file = new File(xmlPath); + if (file.exists() && file.length() != 0) { + xmlFiles.add(file); + } + } + return xmlFiles; + } + + @VisibleForTesting + File getCachedHtmlFile() { + return new File(mContext.getCacheDir(), NOTICE_HTML_FILE_NAME); + } + + @VisibleForTesting + boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) { + boolean outdated = true; + if (cachedHtmlFile.exists() && cachedHtmlFile.length() != 0) { + outdated = false; + for (File file : xmlFiles) { + if (cachedHtmlFile.lastModified() < file.lastModified()) { + outdated = true; + break; + } + } + } + return outdated; + } + + @VisibleForTesting + boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) { + return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile); + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java index 2151ba096c62..a89092040e71 100644 --- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java +++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionList.java @@ -17,7 +17,6 @@ package com.android.settingslib.suggestions; import android.content.Intent; -import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -68,15 +67,6 @@ public class SuggestionList { return false; } - public List<Tile> getSuggestionForCategory(String category) { - for (Map.Entry<SuggestionCategory, List<Tile>> entry : mSuggestions.entrySet()) { - if (TextUtils.equals(entry.getKey().category, category)) { - return entry.getValue(); - } - } - return null; - } - /** * Filter suggestions list so they are all unique. */ diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java index 56b84415e907..9c347631d817 100644 --- a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java +++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionParser.java @@ -261,7 +261,7 @@ public class SuggestionParser { if (requiredAccountType == null) { return true; } - AccountManager accountManager = AccountManager.get(mContext); + AccountManager accountManager = mContext.getSystemService(AccountManager.class); Account[] accounts = accountManager.getAccountsByType(requiredAccountType); boolean satisfiesRequiredAccount = accounts.length > 0; if (!satisfiesRequiredAccount) { diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java b/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java new file mode 100644 index 000000000000..06770ac09558 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017 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.settingslib.utils; + +import android.content.AsyncTaskLoader; +import android.content.Context; + +/** + * This class fills in some boilerplate for AsyncTaskLoader to actually load things. + * + * Subclasses need to implement {@link AsyncLoader#loadInBackground()} to perform the actual + * background task, and {@link AsyncLoader#onDiscardResult(T)} to clean up previously loaded + * results. + * + * This loader is based on the MailAsyncTaskLoader from the AOSP EmailUnified repo. + * + * @param <T> the data type to be loaded. + */ +public abstract class AsyncLoader<T> extends AsyncTaskLoader<T> { + private T mResult; + + public AsyncLoader(final Context context) { + super(context); + } + + @Override + protected void onStartLoading() { + if (mResult != null) { + deliverResult(mResult); + } + + if (takeContentChanged() || mResult == null) { + forceLoad(); + } + } + + @Override + protected void onStopLoading() { + cancelLoad(); + } + + @Override + public void deliverResult(final T data) { + if (isReset()) { + if (data != null) { + onDiscardResult(data); + } + return; + } + + final T oldResult = mResult; + mResult = data; + + if (isStarted()) { + super.deliverResult(data); + } + + if (oldResult != null && oldResult != mResult) { + onDiscardResult(oldResult); + } + } + + @Override + protected void onReset() { + super.onReset(); + + onStopLoading(); + + if (mResult != null) { + onDiscardResult(mResult); + } + mResult = null; + } + + @Override + public void onCanceled(final T data) { + super.onCanceled(data); + + if (data != null) { + onDiscardResult(data); + } + } + + /** + * Called when discarding the load results so subclasses can take care of clean-up or + * recycling tasks. This is not called if the same result (by way of pointer equality) is + * returned again by a subsequent call to loadInBackground, or if result is null. + * + * Note that this may be called concurrently with loadInBackground(), and in some circumstances + * may be called more than once for a given object. + * + * @param result The value returned from {@link AsyncLoader#loadInBackground()} which + * is to be discarded. + */ + protected abstract void onDiscardResult(T result); +} diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java b/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java index 2024991b3d58..88adcdb16edc 100644 --- a/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/utils/ThreadUtils.java @@ -15,10 +15,17 @@ */ package com.android.settingslib.utils; +import android.os.Handler; import android.os.Looper; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + public class ThreadUtils { + private static volatile Thread sMainThread; + private static volatile Handler sMainThreadHandler; + private static volatile ExecutorService sSingleThreadExecutor; /** * Returns true if the current thread is the UI thread. @@ -31,6 +38,17 @@ public class ThreadUtils { } /** + * Returns a shared UI thread handler. + */ + public static Handler getUiThreadHandler() { + if (sMainThreadHandler == null) { + sMainThreadHandler = new Handler(Looper.getMainLooper()); + } + + return sMainThreadHandler; + } + + /** * Checks that the current thread is the UI thread. Otherwise throws an exception. */ public static void ensureMainThread() { @@ -39,4 +57,21 @@ public class ThreadUtils { } } + /** + * Posts runnable in background using shared background thread pool. + */ + public static void postOnBackgroundThread(Runnable runnable) { + if (sSingleThreadExecutor == null) { + sSingleThreadExecutor = Executors.newSingleThreadExecutor(); + } + sSingleThreadExecutor.execute(runnable); + } + + /** + * Posts the runnable on the main thread. + */ + public static void postOnMainThread(Runnable runnable) { + getUiThreadHandler().post(runnable); + } + } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 682b85e46165..58f122619cd6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -63,6 +63,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -186,7 +187,6 @@ public class AccessPoint implements Comparable<AccessPoint> { private WifiConfiguration mConfig; private int mRssi = UNREACHABLE_RSSI; - private long mSeen = 0; private WifiInfo mInfo; private NetworkInfo mNetworkInfo; @@ -270,7 +270,6 @@ public class AccessPoint implements Comparable<AccessPoint> { // Do not evict old scan results on initial creation updateRssi(); - updateSeen(); mId = sLastId.incrementAndGet(); } @@ -316,7 +315,6 @@ public class AccessPoint implements Comparable<AccessPoint> { this.pskType = that.pskType; this.mConfig = that.mConfig; //TODO: Watch out, this object is mutated. this.mRssi = that.mRssi; - this.mSeen = that.mSeen; this.mInfo = that.mInfo; this.mNetworkInfo = that.mNetworkInfo; this.mScanResultCache.clear(); @@ -551,8 +549,7 @@ public class AccessPoint implements Comparable<AccessPoint> { mIsScoredNetworkMetered = false; if (isActive() && mInfo != null) { - NetworkKey key = new NetworkKey(new WifiKey( - AccessPoint.convertToQuotedString(ssid), mInfo.getBSSID())); + NetworkKey key = NetworkKey.createFromWifiInfo(mInfo); ScoredNetwork score = scoreCache.getScoredNetwork(key); if (score != null) { mIsScoredNetworkMetered |= score.meteredHint; @@ -653,21 +650,6 @@ public class AccessPoint implements Comparable<AccessPoint> { } } - /** Updates {@link #mSeen} based on the scan result cache. */ - private void updateSeen() { - long seen = 0; - for (ScanResult result : mScanResultCache.values()) { - if (result.timestamp > seen) { - seen = result.timestamp; - } - } - - // Only replace the previous value if we have a recent scan result to use - if (seen != 0) { - mSeen = seen; - } - } - /** * Returns if the network should be considered metered. */ @@ -1131,7 +1113,6 @@ public class AccessPoint implements Comparable<AccessPoint> { mScanResultCache.put(result.BSSID, result); updateRssi(); - mSeen = result.timestamp; // even if the timestamp is old it is still valid mIsCarrierAp = result.isCarrierAp; mCarrierApEapType = result.carrierApEapType; mCarrierName = result.carrierName; @@ -1183,7 +1164,6 @@ public class AccessPoint implements Comparable<AccessPoint> { /* Add or update the scan result for the BSSID */ mScanResultCache.put(result.BSSID, result); if (evictOldScanResults) evictOldScanResults(); - updateSeen(); updateRssi(); int newLevel = getLevel(); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java index 9ed8de059996..dd55188e390f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -15,13 +15,13 @@ */ package com.android.settingslib.wifi; +import android.annotation.Nullable; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.graphics.drawable.StateListDrawable; -import android.net.NetworkBadging; import android.net.wifi.WifiConfiguration; import android.os.Looper; import android.os.UserHandle; @@ -31,14 +31,17 @@ import android.support.v7.preference.PreferenceViewHolder; import android.text.TextUtils; import android.util.AttributeSet; import android.util.SparseArray; +import android.view.View; import android.widget.ImageView; import android.widget.TextView; import com.android.settingslib.R; import com.android.settingslib.TronUtils; +import com.android.settingslib.TwoTargetPreference; import com.android.settingslib.Utils; +import com.android.settingslib.wifi.AccessPoint.Speed; -public class AccessPointPreference extends Preference { +public class AccessPointPreference extends TwoTargetPreference { private static final int[] STATE_SECURED = { R.attr.state_encrypted @@ -60,9 +63,10 @@ public class AccessPointPreference extends Preference { R.string.accessibility_wifi_signal_full }; - private final StateListDrawable mFrictionSld; + @Nullable private final StateListDrawable mFrictionSld; private final int mBadgePadding; private final UserBadgeCache mBadgeCache; + private final IconInjector mIconInjector; private TextView mTitleView; private boolean mForSavedNetworks = false; @@ -71,7 +75,7 @@ public class AccessPointPreference extends Preference { private int mLevel; private CharSequence mContentDescription; private int mDefaultIconResId; - private int mWifiSpeed = NetworkBadging.BADGING_NONE; + private int mWifiSpeed = Speed.NONE; public static String generatePreferenceKey(AccessPoint accessPoint) { StringBuilder builder = new StringBuilder(); @@ -86,60 +90,52 @@ public class AccessPointPreference extends Preference { return builder.toString(); } + @Nullable + private static StateListDrawable getFrictionStateListDrawable(Context context) { + TypedArray frictionSld; + try { + frictionSld = context.getTheme().obtainStyledAttributes(FRICTION_ATTRS); + } catch (Resources.NotFoundException e) { + // Fallback for platforms that do not need friction icon resources. + frictionSld = null; + } + return frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null; + } + // Used for dummy pref. public AccessPointPreference(Context context, AttributeSet attrs) { super(context, attrs); mFrictionSld = null; mBadgePadding = 0; mBadgeCache = null; + mIconInjector = new IconInjector(context); } public AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, boolean forSavedNetworks) { - super(context); - setWidgetLayoutResource(R.layout.access_point_friction_widget); - mBadgeCache = cache; - mAccessPoint = accessPoint; - mForSavedNetworks = forSavedNetworks; - mAccessPoint.setTag(this); - mLevel = -1; - - TypedArray frictionSld; - try { - frictionSld = context.getTheme().obtainStyledAttributes(FRICTION_ATTRS); - } catch (Resources.NotFoundException e) { - // Fallback for platforms that do not need friction icon resources. - frictionSld = null; - } - mFrictionSld = frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null; - - // Distance from the end of the title at which this AP's user badge should sit. - mBadgePadding = context.getResources() - .getDimensionPixelSize(R.dimen.wifi_preference_badge_padding); + this(accessPoint, context, cache, 0 /* iconResId */, forSavedNetworks); refresh(); } public AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, int iconResId, boolean forSavedNetworks) { + this(accessPoint, context, cache, iconResId, forSavedNetworks, + getFrictionStateListDrawable(context), -1 /* level */, new IconInjector(context)); + } + + @VisibleForTesting + AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, + int iconResId, boolean forSavedNetworks, StateListDrawable frictionSld, + int level, IconInjector iconInjector) { super(context); - setWidgetLayoutResource(R.layout.access_point_friction_widget); mBadgeCache = cache; mAccessPoint = accessPoint; mForSavedNetworks = forSavedNetworks; mAccessPoint.setTag(this); - mLevel = -1; + mLevel = level; mDefaultIconResId = iconResId; - - TypedArray frictionSld; - try { - frictionSld = context.getTheme().obtainStyledAttributes(FRICTION_ATTRS); - } catch (Resources.NotFoundException e) { - // Fallback for platforms that do not need friction icon resources. - frictionSld = null; - } - mFrictionSld = frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null; - - // Distance from the end of the title at which this AP's user badge should sit. + mFrictionSld = frictionSld; + mIconInjector = iconInjector; mBadgePadding = context.getResources() .getDimensionPixelSize(R.dimen.wifi_preference_badge_padding); } @@ -170,6 +166,20 @@ public class AccessPointPreference extends Preference { ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon); bindFrictionImage(frictionImageView); + setDividerVisibility(view, View.GONE); + } + + protected void setDividerVisibility(final PreferenceViewHolder view, + @View.Visibility int visibility) { + final View divider = view.findViewById(R.id.two_target_divider); + if (divider != null) { + divider.setVisibility(visibility); + } + } + + @Override + protected int getSecondTargetResId() { + return R.layout.access_point_friction_widget; } protected void updateIcon(int level, Context context) { @@ -179,9 +189,7 @@ public class AccessPointPreference extends Preference { } TronUtils.logWifiSettingsSpeed(context, mWifiSpeed); - // TODO(b/62355275): Revert this to N code after deleting NetworkBadging API - Drawable drawable = NetworkBadging.getWifiIcon( - level, NetworkBadging.BADGING_NONE, getContext().getTheme()); + Drawable drawable = mIconInjector.getIcon(level); if (!mForSavedNetworks && drawable != null) { drawable.setTint(Utils.getColorAttr(context, android.R.attr.colorControlNormal)); setIcon(drawable); @@ -325,4 +333,16 @@ public class AccessPointPreference extends Preference { return mBadges.valueAt(index); } } + + static class IconInjector { + private final Context mContext; + + public IconInjector(Context context) { + mContext = context; + } + + public Drawable getIcon(int level) { + return mContext.getDrawable(Utils.getWifiIconResource(level)); + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS new file mode 100644 index 000000000000..d5d2e9e8c146 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/OWNERS @@ -0,0 +1,7 @@ +# Default reviewers for this and subdirectories. +asapperstein@google.com +asargent@google.com +dling@google.com +zhfan@google.com + +# Emergency approvers in case the above are not available
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 3c664b0b01a9..c56e1da14921 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -24,7 +24,6 @@ import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkInfo; -import android.net.NetworkInfo.DetailedState; import android.net.NetworkKey; import android.net.NetworkRequest; import android.net.NetworkScoreManager; @@ -36,10 +35,14 @@ import android.net.wifi.WifiManager; import android.net.wifi.WifiNetworkScoreCache; import android.net.wifi.WifiNetworkScoreCache.CacheListener; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.Message; +import android.os.Process; import android.provider.Settings; import android.support.annotation.GuardedBy; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; import android.text.format.DateUtils; import android.util.ArraySet; import android.util.Log; @@ -47,8 +50,12 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.widget.Toast; -import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.R; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnDestroy; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; import java.io.PrintWriter; import java.util.ArrayList; @@ -64,7 +71,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * Tracks saved or available wifi networks and their state. */ -public class WifiTracker { +public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy { /** * Default maximum age in millis of cached scored networks in * {@link AccessPoint#mScoredNetworkCache} to be used for speed label generation. @@ -80,7 +87,7 @@ public class WifiTracker { * and used so as to assist with in-the-field WiFi connectivity debugging */ public static boolean sVerboseLogging; - // TODO(b/36733768): Remove flag includeSaved and includePasspoints. + // TODO(b/36733768): Remove flag includeSaved // TODO: Allow control of this? // Combo scans can take 5-6s to complete - set to 10s. @@ -96,9 +103,9 @@ public class WifiTracker { private final WifiListener mListener; private final boolean mIncludeSaved; private final boolean mIncludeScans; - private final boolean mIncludePasspoints; - @VisibleForTesting final MainHandler mMainHandler; - @VisibleForTesting final WorkHandler mWorkHandler; + @VisibleForTesting MainHandler mMainHandler; + @VisibleForTesting WorkHandler mWorkHandler; + private HandlerThread mWorkThread; private WifiTrackerNetworkCallback mNetworkCallback; @@ -142,7 +149,7 @@ public class WifiTracker { private WifiInfo mLastInfo; private final NetworkScoreManager mNetworkScoreManager; - private final WifiNetworkScoreCache mScoreCache; + private WifiNetworkScoreCache mScoreCache; private boolean mNetworkScoringUiEnabled; private long mMaxSpeedLabelScoreCacheAge; @@ -155,65 +162,64 @@ public class WifiTracker { @GuardedBy("mLock") private boolean mStaleScanResults = true; - public WifiTracker(Context context, WifiListener wifiListener, - boolean includeSaved, boolean includeScans) { - this(context, wifiListener, null, includeSaved, includeScans); + private static IntentFilter newIntentFilter() { + IntentFilter filter = new IntentFilter(); + filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); + filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); + filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); + filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); + filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + filter.addAction(WifiManager.RSSI_CHANGED_ACTION); + + return filter; } - public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, + /** + * Use the lifecycle constructor below whenever possible + */ + @Deprecated + public WifiTracker(Context context, WifiListener wifiListener, boolean includeSaved, boolean includeScans) { - this(context, wifiListener, workerLooper, includeSaved, includeScans, false); + this(context, wifiListener, includeSaved, includeScans, + context.getSystemService(WifiManager.class), + context.getSystemService(ConnectivityManager.class), + context.getSystemService(NetworkScoreManager.class), + newIntentFilter()); } public WifiTracker(Context context, WifiListener wifiListener, - boolean includeSaved, boolean includeScans, boolean includePasspoints) { - this(context, wifiListener, null, includeSaved, includeScans, includePasspoints); - } - - public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, - boolean includeSaved, boolean includeScans, boolean includePasspoints) { - this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints, + @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) { + this(context, wifiListener, includeSaved, includeScans, context.getSystemService(WifiManager.class), context.getSystemService(ConnectivityManager.class), - context.getSystemService(NetworkScoreManager.class), Looper.myLooper() - ); + context.getSystemService(NetworkScoreManager.class), + newIntentFilter()); + lifecycle.addObserver(this); } @VisibleForTesting - WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper, - boolean includeSaved, boolean includeScans, boolean includePasspoints, + WifiTracker(Context context, WifiListener wifiListener, + boolean includeSaved, boolean includeScans, WifiManager wifiManager, ConnectivityManager connectivityManager, - NetworkScoreManager networkScoreManager, Looper currentLooper) { + NetworkScoreManager networkScoreManager, + IntentFilter filter) { if (!includeSaved && !includeScans) { throw new IllegalArgumentException("Must include either saved or scans"); } mContext = context; - if (currentLooper == null) { - // When we aren't on a looper thread, default to the main. - currentLooper = Looper.getMainLooper(); - } - mMainHandler = new MainHandler(currentLooper); - mWorkHandler = new WorkHandler( - workerLooper != null ? workerLooper : currentLooper); + mMainHandler = new MainHandler(Looper.getMainLooper()); mWifiManager = wifiManager; mIncludeSaved = includeSaved; mIncludeScans = includeScans; - mIncludePasspoints = includePasspoints; mListener = wifiListener; mConnectivityManager = connectivityManager; // check if verbose logging has been turned on or off sVerboseLogging = (mWifiManager.getVerboseLoggingLevel() > 0); - mFilter = new IntentFilter(); - mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); - mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); - mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); - mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); - mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); - mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); - mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); - mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); + mFilter = filter; mNetworkRequest = new NetworkRequest.Builder() .clearCapabilities() @@ -222,7 +228,22 @@ public class WifiTracker { mNetworkScoreManager = networkScoreManager; - mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) { + final HandlerThread workThread = new HandlerThread(TAG + + "{" + Integer.toHexString(System.identityHashCode(this)) + "}", + Process.THREAD_PRIORITY_BACKGROUND); + workThread.start(); + setWorkThread(workThread); + } + + /** + * Sanity warning: this wipes out mScoreCache, so use with extreme caution + * @param workThread substitute Handler thread, for testing purposes only + */ + @VisibleForTesting + void setWorkThread(HandlerThread workThread) { + mWorkThread = workThread; + mWorkHandler = new WorkHandler(workThread.getLooper()); + mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) { @Override public void networkCacheUpdated(List<ScoredNetwork> networks) { synchronized (mLock) { @@ -237,6 +258,11 @@ public class WifiTracker { }); } + @Override + public void onDestroy() { + mWorkThread.quit(); + } + /** Synchronously update the list of access points with the latest information. */ @MainThread public void forceUpdate() { @@ -265,15 +291,6 @@ public class WifiTracker { } /** - * Force a scan for wifi networks to happen now. - */ - public void forceScan() { - if (mWifiManager.isWifiEnabled() && mScanner != null) { - mScanner.forceScan(); - } - } - - /** * Temporarily stop scanning for wifi networks. */ public void pauseScanning() { @@ -305,8 +322,9 @@ public class WifiTracker { * <p>Registers listeners and starts scanning for wifi networks. If this is not called * then forceUpdate() must be called to populate getAccessPoints(). */ + @Override @MainThread - public void startTracking() { + public void onStart() { synchronized (mLock) { registerScoreCache(); @@ -354,15 +372,16 @@ public class WifiTracker { /** * Stop tracking wifi networks and scores. * - * <p>This should always be called when done with a WifiTracker (if startTracking was called) to + * <p>This should always be called when done with a WifiTracker (if onStart was called) to * ensure proper cleanup and prevent any further callbacks from occurring. * * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION). */ + @Override @MainThread - public void stopTracking() { + public void onStop() { synchronized (mLock) { if (mRegistered) { mContext.unregisterReceiver(mReceiver); @@ -761,15 +780,6 @@ public class WifiTracker { } } - public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved, - boolean includeScans, boolean includePasspoints) { - WifiTracker tracker = new WifiTracker(context, - null, null, includeSaved, includeScans, includePasspoints); - tracker.forceUpdate(); - tracker.copyAndNotifyListeners(false /*notifyListeners*/); - return tracker.getAccessPoints(); - } - @VisibleForTesting final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -959,11 +969,6 @@ public class WifiTracker { } } - void forceScan() { - removeMessages(MSG_SCAN); - sendEmptyMessage(MSG_SCAN); - } - void pause() { mRetry = 0; removeMessages(MSG_SCAN); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java index 79cee046140d..8b5863aee91f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java @@ -16,8 +16,10 @@ package com.android.settingslib.wifi; import android.content.Context; -import android.os.Looper; import android.support.annotation.Keep; +import android.support.annotation.NonNull; + +import com.android.settingslib.core.lifecycle.Lifecycle; /** * Factory method used to inject WifiTracker instances. @@ -31,12 +33,11 @@ public class WifiTrackerFactory { } public static WifiTracker create( - Context context, WifiTracker.WifiListener wifiListener, Looper workerLooper, - boolean includeSaved, boolean includeScans, boolean includePasspoints) { + Context context, WifiTracker.WifiListener wifiListener, @NonNull Lifecycle lifecycle, + boolean includeSaved, boolean includeScans) { if(sTestingWifiTracker != null) { return sTestingWifiTracker; } - return new WifiTracker( - context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints); + return new WifiTracker(context, wifiListener, lifecycle, includeSaved, includeScans); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java index 14fa7966eab1..4c52a9f8d3ce 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothA2dpWrapperImpl.java +++ b/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java @@ -14,48 +14,56 @@ * limitations under the License. */ -package com.android.settingslib.bluetooth; +package com.android.settingslib.wrapper; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothDevice; -public class BluetoothA2dpWrapperImpl implements BluetoothA2dpWrapper { - - public static class Factory implements BluetoothA2dpWrapper.Factory { - @Override - public BluetoothA2dpWrapper getInstance(BluetoothA2dp service) { - return new BluetoothA2dpWrapperImpl(service); - } - } +/** + * This class replicates some methods of android.bluetooth.BluetoothA2dp that are new and not + * yet available in our current version of Robolectric. It provides a thin wrapper to call the real + * methods in production and a mock in tests. + */ +public class BluetoothA2dpWrapper { private BluetoothA2dp mService; - public BluetoothA2dpWrapperImpl(BluetoothA2dp service) { + public BluetoothA2dpWrapper(BluetoothA2dp service) { mService = service; } - @Override + /** + * @return the real {@code BluetoothA2dp} object + */ public BluetoothA2dp getService() { return mService; } - @Override + /** + * Wraps {@code BluetoothA2dp.getCodecStatus} + */ public BluetoothCodecStatus getCodecStatus() { return mService.getCodecStatus(); } - @Override + /** + * Wraps {@code BluetoothA2dp.supportsOptionalCodecs} + */ public int supportsOptionalCodecs(BluetoothDevice device) { return mService.supportsOptionalCodecs(device); } - @Override + /** + * Wraps {@code BluetoothA2dp.getOptionalCodecsEnabled} + */ public int getOptionalCodecsEnabled(BluetoothDevice device) { return mService.getOptionalCodecsEnabled(device); } - @Override + /** + * Wraps {@code BluetoothA2dp.setOptionalCodecsEnabled} + */ public void setOptionalCodecsEnabled(BluetoothDevice device, int value) { mService.setOptionalCodecsEnabled(device, value); } diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java new file mode 100644 index 000000000000..b1f3f3ce4300 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/wrapper/PackageManagerWrapper.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2017 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.settingslib.wrapper; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; +import android.os.storage.VolumeInfo; + +import java.util.List; + +/** + * A thin wrapper class that simplifies testing by putting a mockable layer between the application + * and the PackageManager. This class only provides access to the minimum number of functions from + * the PackageManager needed for DeletionHelper to work. + */ +public class PackageManagerWrapper { + + private final PackageManager mPm; + + public PackageManagerWrapper(PackageManager pm) { + mPm = pm; + } + + /** + * Returns the real {@code PackageManager} object. + */ + public PackageManager getPackageManager() { + return mPm; + } + + /** + * Calls {@code PackageManager.getInstalledApplicationsAsUser()}. + * + * @see android.content.pm.PackageManager#getInstalledApplicationsAsUser + */ + public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) { + return mPm.getInstalledApplicationsAsUser(flags, userId); + } + + /** + * Calls {@code PackageManager.getInstalledPackagesAsUser} + */ + public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) { + return mPm.getInstalledPackagesAsUser(flags, userId); + } + + /** + * Calls {@code PackageManager.hasSystemFeature()}. + * + * @see android.content.pm.PackageManager#hasSystemFeature + */ + public boolean hasSystemFeature(String name) { + return mPm.hasSystemFeature(name); + } + + /** + * Calls {@code PackageManager.queryIntentActivitiesAsUser()}. + * + * @see android.content.pm.PackageManager#queryIntentActivitiesAsUser + */ + public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) { + return mPm.queryIntentActivitiesAsUser(intent, flags, userId); + } + + /** + * Calls {@code PackageManager.getInstallReason()}. + * + * @see android.content.pm.PackageManager#getInstallReason + */ + public int getInstallReason(String packageName, UserHandle user) { + return mPm.getInstallReason(packageName, user); + } + + /** + * Calls {@code PackageManager.getApplicationInfoAsUser} + */ + public ApplicationInfo getApplicationInfoAsUser(String packageName, int i, int userId) + throws PackageManager.NameNotFoundException { + return mPm.getApplicationInfoAsUser(packageName, i, userId); + } + + /** + * Calls {@code PackageManager.setDefaultBrowserPackageNameAsUser} + */ + public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) { + return mPm.setDefaultBrowserPackageNameAsUser(packageName, userId); + } + + /** + * Calls {@code PackageManager.getDefaultBrowserPackageNameAsUser} + */ + public String getDefaultBrowserPackageNameAsUser(int userId) { + return mPm.getDefaultBrowserPackageNameAsUser(userId); + } + + /** + * Calls {@code PackageManager.getHomeActivities} + */ + public ComponentName getHomeActivities(List<ResolveInfo> homeActivities) { + return mPm.getHomeActivities(homeActivities); + } + + /** + * Calls {@code PackageManager.queryIntentServicesAsUser} + */ + public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int i, int user) { + return mPm.queryIntentServicesAsUser(intent, i, user); + } + + /** + * Calls {@code PackageManager.replacePreferredActivity} + */ + public void replacePreferredActivity(IntentFilter homeFilter, int matchCategoryEmpty, + ComponentName[] componentNames, ComponentName component) { + mPm.replacePreferredActivity(homeFilter, matchCategoryEmpty, componentNames, component); + } + + /** + * Gets information about a particular package from the package manager. + * + * @param packageName The name of the package we would like information about. + * @param i additional options flags. see javadoc for + * {@link PackageManager#getPackageInfo(String, int)} + * @return The PackageInfo for the requested package + */ + public PackageInfo getPackageInfo(String packageName, int i) throws NameNotFoundException { + return mPm.getPackageInfo(packageName, i); + } + + /** + * Retrieves the icon associated with this particular set of ApplicationInfo + * + * @param info The ApplicationInfo to retrieve the icon for + * @return The icon as a drawable. + */ + public Drawable getUserBadgedIcon(ApplicationInfo info) { + return mPm.getUserBadgedIcon(mPm.loadUnbadgedItemIcon(info, info), + new UserHandle(UserHandle.getUserId(info.uid))); + } + + /** + * Retrieves the label associated with the particular set of ApplicationInfo + * + * @param app The ApplicationInfo to retrieve the label for + * @return the label as a CharSequence + */ + public CharSequence loadLabel(ApplicationInfo app) { + return app.loadLabel(mPm); + } + + /** + * Retrieve all activities that can be performed for the given intent. + */ + public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) { + return mPm.queryIntentActivities(intent, flags); + } + + /** + * Calls {@code PackageManager.getPrimaryStorageCurrentVolume} + */ + public VolumeInfo getPrimaryStorageCurrentVolume() { + return mPm.getPrimaryStorageCurrentVolume(); + } + + /** + * Calls {@code PackageManager.deletePackageAsUser} + */ + public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer, int flags, + int userId) { + mPm.deletePackageAsUser(packageName, observer, flags, userId); + } + + /** + * Calls {@code PackageManager.getPackageUidAsUser} + */ + public int getPackageUidAsUser(String pkg, int userId) + throws PackageManager.NameNotFoundException { + return mPm.getPackageUidAsUser(pkg, userId); + } + + /** + * Calls {@code PackageManager.setApplicationEnabledSetting} + */ + public void setApplicationEnabledSetting(String packageName, int newState, int flags) { + mPm.setApplicationEnabledSetting(packageName, newState, flags); + } + + /** + * Calls {@code PackageManager.getApplicationEnabledSetting} + */ + public int getApplicationEnabledSetting(String packageName) { + return mPm.getApplicationEnabledSetting(packageName); + } + + /** + * Calls {@code PackageManager.setComponentEnabledSetting} + */ + public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) { + mPm.setComponentEnabledSetting(componentName, newState, flags); + } + + /** + * Calls {@code PackageManager.getApplicationInfo} + */ + public ApplicationInfo getApplicationInfo(String packageName, int flags) + throws NameNotFoundException { + return mPm.getApplicationInfo(packageName, flags); + } + + /** + * Calls {@code PackageManager.getApplicationLabel} + */ + public CharSequence getApplicationLabel(ApplicationInfo info) { + return mPm.getApplicationLabel(info); + } + + /** + * Calls {@code PackageManager.queryBroadcastReceivers} + */ + public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) { + return mPm.queryBroadcastReceivers(intent, flags); + } +} + |