Merge "Import translations. DO NOT MERGE"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2343ac2..7abdf27 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2952,6 +2952,11 @@
android:value="com.android.settings.deletionhelper.AutomaticStorageManagerSettings" />
</activity>
+ <activity android:name="Settings$LegacySupportActivity">
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.dashboard.SupportFragment"/>
+ </activity>
+
<!-- Information architecture host activities -->
<!-- Alias for battery settings in new IA. Remove and merge metadata into TargetActivity -->
@@ -3001,17 +3006,16 @@
android:resource="@string/system_dashboard_summary"/>
</activity>
- <activity android:name=".Settings$SupportDashboardActivity"
+ <activity android:name=".dashboard.SupportDashboardActivity"
android:label="@string/page_tab_title_support"
android:icon="@drawable/ic_help"
+ android:theme="@android:style/Theme.NoDisplay"
android:enabled="@bool/config_support_enabled">
<intent-filter android:priority="-2">
<action android:name="com.android.settings.action.SETTINGS"/>
</intent-filter>
<meta-data android:name="com.android.settings.category"
android:value="com.android.settings.category.ia.homepage"/>
- <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
- android:value="com.android.settings.dashboard.SupportFragment"/>
<meta-data android:name="com.android.settings.summary"
android:resource="@string/support_summary"/>
</activity>
diff --git a/res/layout/dream_info_row.xml b/res/layout/dream_info_row.xml
deleted file mode 100644
index fd70ad5..0000000
--- a/res/layout/dream_info_row.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-<!--
- Copyright (C) 2012 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.
--->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:clickable="true"
- android:focusable="true"
- android:orientation="horizontal"
- android:background="?android:attr/selectableItemBackground" >
-
- <!-- Dream icon -->
-
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="@android:dimen/app_icon_size"
- android:layout_height="@android:dimen/app_icon_size"
- android:layout_centerVertical="true"
- android:layout_marginBottom="10dp"
- android:layout_marginStart="0dp"
- android:layout_marginEnd="6dp"
- android:layout_marginTop="10dp"
- android:contentDescription="@null"
- android:maxHeight="@android:dimen/app_icon_size"
- android:maxWidth="@android:dimen/app_icon_size"
- android:scaleType="fitCenter" />
-
- <!-- Dream caption -->
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:layout_centerVertical="true"
- android:ellipsize="end"
- android:singleLine="true"
- android:gravity="center_vertical"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textAlignment="viewStart"
- android:labelFor="@android:id/button2" />
-
- <!-- Dream radio button -->
-
- <RadioButton
- android:id="@android:id/button1"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_alignParentEnd="true"
- android:layout_centerVertical="true"
- android:duplicateParentState="true"
- android:clickable="false"
- android:focusable="false" />
-
- <!-- Divider -->
-
- <ImageView
- android:id="@+id/divider"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_centerVertical="true"
- android:contentDescription="@null"
- android:src="@drawable/nav_divider" />
-
- <!-- Settings icon -->
-
- <ImageView
- android:id="@android:id/button2"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_centerVertical="true"
- android:layout_margin="0dip"
- android:background="?android:attr/selectableItemBackground"
- android:contentDescription="@string/screensaver_settings_button"
- android:padding="8dip"
- android:clickable="true"
- android:focusable="true"
- android:src="@drawable/ic_settings" />
-
-</LinearLayout>
diff --git a/res/layout/dream_start_button.xml b/res/layout/dream_start_button.xml
new file mode 100644
index 0000000..7d5e0c9
--- /dev/null
+++ b/res/layout/dream_start_button.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:gravity="bottom"
+ android:paddingTop="4dp"
+ android:paddingStart="72dp"
+ android:paddingEnd="72dp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Button
+ android:id="@+id/dream_start_now_button"
+ style="@style/DreamStartButton"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:text="@string/screensaver_settings_dream_start"
+ android:paddingEnd="8dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/preference_dropdown_material_settings.xml b/res/layout/preference_dropdown_material_settings.xml
new file mode 100644
index 0000000..22f98b6
--- /dev/null
+++ b/res/layout/preference_dropdown_material_settings.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+ -->
+
+
+<!-- Based off frameworks/base/core/res/res/layout/preference_dropdown_material.xml
+ except that icon space in this layout is always reserved -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/preference_no_icon_padding_start"
+ android:visibility="invisible" />
+
+ <include layout="@layout/preference_material_settings"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 873576c..5e7442b 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -1056,4 +1056,18 @@
<item>90</item>
</string-array>
+ <string-array name="when_to_start_screensaver_entries" translatable="false">
+ <item>@string/screensaver_settings_summary_sleep</item>
+ <item>@string/screensaver_settings_summary_dock</item>
+ <item>@string/screensaver_settings_summary_either_long</item>
+ <item>@string/screensaver_settings_summary_never</item>
+ </string-array>
+
+ <string-array name="when_to_start_screensaver_values" translatable="false">
+ <item>while_charging_only</item>
+ <item>while_docked_only</item>
+ <item>either_charging_or_docked</item>
+ <item>never</item>
+ </string-array>
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c92d0f0..8be8e42 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -8716,5 +8716,6 @@
<!-- Trigger Carrier Provisioning [CHAR LIMIT=NONE] -->
<string name="trigger_carrier_provisioning">Trigger Carrier Provisioning</string>
-
+ <!-- Help URI, USB Audio [DO NOT TRANSLATE] -->
+ <string name="help_url_audio_accessory_not_supported" translatable="false"></string>
</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index bae36b3..0780e82 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -421,6 +421,8 @@
<style name="AppActionPrimaryButton" parent="android:Widget.Material.Button.Colored"/>
+ <style name="DreamStartButton" parent="android:Widget.Material.Button" />
+
<style name="LockPatternStyle">
<item name="*android:regularColor">@color/lock_pattern_view_regular_color</item>
<item name="*android:successColor">@color/lock_pattern_view_success_color</item>
diff --git a/res/values/styles_preference.xml b/res/values/styles_preference.xml
index a5e8b60..9c7fca9 100644
--- a/res/values/styles_preference.xml
+++ b/res/values/styles_preference.xml
@@ -52,7 +52,9 @@
<item name="android:dialogLayout">@layout/preference_dialog_edittext</item>
</style>
- <style name="SettingsDropdownPreference" parent="SettingsPreference" />
+ <style name="SettingsDropdownPreference" parent="SettingsPreference">
+ <item name="android:layout">@layout/preference_dropdown_material_settings</item>
+ </style>
<style name="SettingsDialogPreference" parent="SettingsPreference" />
diff --git a/res/xml/display_settings.xml b/res/xml/display_settings.xml
index 9d4acb4..8815665 100644
--- a/res/xml/display_settings.xml
+++ b/res/xml/display_settings.xml
@@ -76,7 +76,7 @@
<Preference
android:key="screensaver"
android:title="@string/screensaver_settings_title"
- android:fragment="com.android.settings.DreamSettings" />
+ android:fragment="com.android.settings.dream.DreamSettings" />
<!-- Hide night mode for now
<ListPreference
diff --git a/res/xml/dream_fragment_overview.xml b/res/xml/dream_fragment_overview.xml
new file mode 100644
index 0000000..d27a6e3
--- /dev/null
+++ b/res/xml/dream_fragment_overview.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
+ android:title="@string/screensaver_settings_title" >
+
+ <com.android.settings.widget.GearPreference
+ android:key="current_screensaver"
+ android:title="@string/screensaver_settings_current"
+ android:fragment="com.android.settings.dream.CurrentDreamPicker" />
+
+ <Preference
+ android:key="when_to_start"
+ android:title="@string/screensaver_settings_when_to_dream"
+ android:fragment="com.android.settings.dream.WhenToDreamPicker" />
+
+
+ <!-- Layout preference doesn't obey allowDividerAbove, so put it in a PreferenceCategory -->
+ <PreferenceCategory>
+ <com.android.settings.applications.LayoutPreference
+ android:key="dream_start_now_button_container"
+ android:selectable="false"
+ android:layout="@layout/dream_start_button" />
+ </PreferenceCategory>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/power_usage_summary.xml b/res/xml/power_usage_summary.xml
index abd659e..3c685c0 100644
--- a/res/xml/power_usage_summary.xml
+++ b/res/xml/power_usage_summary.xml
@@ -25,6 +25,9 @@
android:selectable="true"
android:layout="@layout/battery_header"/>
+ <Preference
+ android:key="high_usage"/>
+
<PreferenceCategory
android:key="device_usage_list">
diff --git a/src/com/android/settings/ApnEditor.java b/src/com/android/settings/ApnEditor.java
index f668957..d5affa8 100644
--- a/src/com/android/settings/ApnEditor.java
+++ b/src/com/android/settings/ApnEditor.java
@@ -64,6 +64,7 @@
implements OnPreferenceChangeListener, OnKeyListener {
private final static String TAG = ApnEditor.class.getSimpleName();
+ private final static boolean VDBG = false; // STOPSHIP if true
private final static String SAVED_POS = "pos";
private final static String KEY_AUTH_TYPE = "auth_type";
@@ -785,6 +786,46 @@
}
/**
+ * Add key, value to cv and compare the value against the value at index in mCursor. Return true
+ * if values are different. assumeDiff indicates if values can be assumed different in which
+ * case no comparison is needed.
+ * @return true if value is different from the value at index in mCursor
+ */
+ boolean setStringValueAndCheckIfDiff(ContentValues cv, String key, String value,
+ boolean assumeDiff, int index) {
+ cv.put(key, value);
+ String valueFromCursor = mCursor.getString(index);
+ if (VDBG) {
+ Log.d(TAG, "setStringValueAndCheckIfDiff: assumeDiff: " + assumeDiff
+ + " key: " + key
+ + " value: '" + value
+ + "' valueFromCursor: '" + valueFromCursor + "'");
+ }
+ return assumeDiff
+ || !((TextUtils.isEmpty(value) && TextUtils.isEmpty(valueFromCursor))
+ || (value != null && value.equals(valueFromCursor)));
+ }
+
+ /**
+ * Add key, value to cv and compare the value against the value at index in mCursor. Return true
+ * if values are different. assumeDiff indicates if values can be assumed different in which
+ * case no comparison is needed.
+ * @return true if value is different from the value at index in mCursor
+ */
+ boolean setIntValueAndCheckIfDiff(ContentValues cv, String key, int value,
+ boolean assumeDiff, int index) {
+ cv.put(key, value);
+ int valueFromCursor = mCursor.getInt(index);
+ if (VDBG) {
+ Log.d(TAG, "setIntValueAndCheckIfDiff: assumeDiff: " + assumeDiff
+ + " key: " + key
+ + " value: '" + value
+ + "' valueFromCursor: '" + valueFromCursor + "'");
+ }
+ return assumeDiff || value != valueFromCursor;
+ }
+
+ /**
* Check the key fields' validity and save if valid.
* @param force save even if the fields are not valid, if the app is
* being suspended
@@ -820,33 +861,110 @@
}
ContentValues values = new ContentValues();
+ // call update() if it's a new APN. If not, check if any field differs from the db value;
+ // if any diff is found update() should be called
+ boolean callUpdate = mNewApn;
// Add a dummy name "Untitled", if the user exits the screen without adding a name but
// entered other information worth keeping.
- values.put(Telephony.Carriers.NAME,
- name.length() < 1 ? getResources().getString(R.string.untitled_apn) : name);
- values.put(Telephony.Carriers.APN, apn);
- values.put(Telephony.Carriers.PROXY, checkNotSet(mProxy.getText()));
- values.put(Telephony.Carriers.PORT, checkNotSet(mPort.getText()));
- values.put(Telephony.Carriers.MMSPROXY, checkNotSet(mMmsProxy.getText()));
- values.put(Telephony.Carriers.MMSPORT, checkNotSet(mMmsPort.getText()));
- values.put(Telephony.Carriers.USER, checkNotSet(mUser.getText()));
- values.put(Telephony.Carriers.SERVER, checkNotSet(mServer.getText()));
- values.put(Telephony.Carriers.PASSWORD, checkNotSet(mPassword.getText()));
- values.put(Telephony.Carriers.MMSC, checkNotSet(mMmsc.getText()));
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.NAME,
+ name.length() < 1 ? getResources().getString(R.string.untitled_apn) : name,
+ callUpdate,
+ NAME_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.APN,
+ apn,
+ callUpdate,
+ APN_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.PROXY,
+ checkNotSet(mProxy.getText()),
+ callUpdate,
+ PROXY_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.PORT,
+ checkNotSet(mPort.getText()),
+ callUpdate,
+ PORT_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MMSPROXY,
+ checkNotSet(mMmsProxy.getText()),
+ callUpdate,
+ MMSPROXY_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MMSPORT,
+ checkNotSet(mMmsPort.getText()),
+ callUpdate,
+ MMSPORT_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.USER,
+ checkNotSet(mUser.getText()),
+ callUpdate,
+ USER_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.SERVER,
+ checkNotSet(mServer.getText()),
+ callUpdate,
+ SERVER_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.PASSWORD,
+ checkNotSet(mPassword.getText()),
+ callUpdate,
+ PASSWORD_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MMSC,
+ checkNotSet(mMmsc.getText()),
+ callUpdate,
+ MMSC_INDEX);
String authVal = mAuthType.getValue();
if (authVal != null) {
- values.put(Telephony.Carriers.AUTH_TYPE, Integer.parseInt(authVal));
+ callUpdate = setIntValueAndCheckIfDiff(values,
+ Telephony.Carriers.AUTH_TYPE,
+ Integer.parseInt(authVal),
+ callUpdate,
+ AUTH_TYPE_INDEX);
}
- values.put(Telephony.Carriers.PROTOCOL, checkNotSet(mProtocol.getValue()));
- values.put(Telephony.Carriers.ROAMING_PROTOCOL, checkNotSet(mRoamingProtocol.getValue()));
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.PROTOCOL,
+ checkNotSet(mProtocol.getValue()),
+ callUpdate,
+ PROTOCOL_INDEX);
- values.put(Telephony.Carriers.TYPE, checkNotSet(mApnType.getText()));
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.ROAMING_PROTOCOL,
+ checkNotSet(mRoamingProtocol.getValue()),
+ callUpdate,
+ ROAMING_PROTOCOL_INDEX);
- values.put(Telephony.Carriers.MCC, mcc);
- values.put(Telephony.Carriers.MNC, mnc);
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.TYPE,
+ checkNotSet(mApnType.getText()),
+ callUpdate,
+ TYPE_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MCC,
+ mcc,
+ callUpdate,
+ MCC_INDEX);
+
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MNC,
+ mnc,
+ callUpdate,
+ MNC_INDEX);
values.put(Telephony.Carriers.NUMERIC, mcc + mnc);
@@ -866,7 +984,11 @@
bearerBitmask |= ServiceState.getBitmaskForTech(Integer.parseInt(bearer));
}
}
- values.put(Telephony.Carriers.BEARER_BITMASK, bearerBitmask);
+ callUpdate = setIntValueAndCheckIfDiff(values,
+ Telephony.Carriers.BEARER_BITMASK,
+ bearerBitmask,
+ callUpdate,
+ BEARER_BITMASK_INDEX);
int bearerVal;
if (bearerBitmask == 0 || mBearerInitialVal == 0) {
@@ -879,13 +1001,35 @@
// random tech from the new bitmask??
bearerVal = 0;
}
- values.put(Telephony.Carriers.BEARER, bearerVal);
+ callUpdate = setIntValueAndCheckIfDiff(values,
+ Telephony.Carriers.BEARER,
+ bearerVal,
+ callUpdate,
+ BEARER_INDEX);
- values.put(Telephony.Carriers.MVNO_TYPE, checkNotSet(mMvnoType.getValue()));
- values.put(Telephony.Carriers.MVNO_MATCH_DATA, checkNotSet(mMvnoMatchData.getText()));
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MVNO_TYPE,
+ checkNotSet(mMvnoType.getValue()),
+ callUpdate,
+ MVNO_TYPE_INDEX);
- values.put(Telephony.Carriers.CARRIER_ENABLED, mCarrierEnabled.isChecked() ? 1 : 0);
- getContentResolver().update(mUri, values, null, null);
+ callUpdate = setStringValueAndCheckIfDiff(values,
+ Telephony.Carriers.MVNO_MATCH_DATA,
+ checkNotSet(mMvnoMatchData.getText()),
+ callUpdate,
+ MVNO_MATCH_DATA_INDEX);
+
+ callUpdate = setIntValueAndCheckIfDiff(values,
+ Telephony.Carriers.CARRIER_ENABLED,
+ mCarrierEnabled.isChecked() ? 1 : 0,
+ callUpdate,
+ CARRIER_ENABLED_INDEX);
+
+ if (callUpdate) {
+ getContentResolver().update(mUri, values, null, null);
+ } else {
+ if (VDBG) Log.d(TAG, "validateAndSave: not calling update()");
+ }
return true;
}
diff --git a/src/com/android/settings/DreamSettings.java b/src/com/android/settings/DreamSettings.java
deleted file mode 100644
index bb9e978..0000000
--- a/src/com/android/settings/DreamSettings.java
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * Copyright (C) 2012 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.settings;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceViewHolder;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.MenuItem.OnMenuItemClickListener;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnTouchListener;
-import android.widget.ImageView;
-import android.widget.RadioButton;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settings.widget.SwitchBar;
-import com.android.settingslib.dream.DreamBackend;
-import com.android.settingslib.dream.DreamBackend.DreamInfo;
-
-import java.util.List;
-
-public class DreamSettings extends SettingsPreferenceFragment implements
- SwitchBar.OnSwitchChangeListener {
- private static final String TAG = DreamSettings.class.getSimpleName();
- static final boolean DEBUG = false;
- private static final int DIALOG_WHEN_TO_DREAM = 1;
- private static final String PACKAGE_SCHEME = "package";
-
- private final PackageReceiver mPackageReceiver = new PackageReceiver();
-
- private Context mContext;
- private DreamBackend mBackend;
- private SwitchBar mSwitchBar;
- private MenuItem[] mMenuItemsWhenEnabled;
- private boolean mRefreshing;
-
- @Override
- public int getHelpResource() {
- return R.string.help_url_dreams;
- }
-
- @Override
- public void onAttach(Activity activity) {
- logd("onAttach(%s)", activity.getClass().getSimpleName());
- super.onAttach(activity);
- mContext = activity;
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.DREAM;
- }
-
- @Override
- public void onCreate(Bundle icicle) {
- logd("onCreate(%s)", icicle);
- super.onCreate(icicle);
-
- mBackend = new DreamBackend(getActivity());
-
- setHasOptionsMenu(true);
- }
-
- @Override
- public void onSwitchChanged(Switch switchView, boolean isChecked) {
- if (!mRefreshing) {
- mBackend.setEnabled(isChecked);
- refreshFromBackend();
- }
- }
-
- @Override
- public void onStart() {
- logd("onStart()");
- super.onStart();
- }
-
- @Override
- public void onDestroyView() {
- logd("onDestroyView()");
- super.onDestroyView();
-
- mSwitchBar.removeOnSwitchChangeListener(this);
- mSwitchBar.hide();
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- logd("onActivityCreated(%s)", savedInstanceState);
- super.onActivityCreated(savedInstanceState);
-
- TextView emptyView = (TextView) getView().findViewById(android.R.id.empty);
- emptyView.setText(R.string.screensaver_settings_disabled_prompt);
- setEmptyView(emptyView);
-
- final SettingsActivity sa = (SettingsActivity) getActivity();
- mSwitchBar = sa.getSwitchBar();
- mSwitchBar.addOnSwitchChangeListener(this);
- mSwitchBar.show();
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- logd("onCreateOptionsMenu()");
-
- boolean isEnabled = mBackend.isEnabled();
-
- // create "start" action
- MenuItem start = createMenuItem(menu, R.string.screensaver_settings_dream_start,
- MenuItem.SHOW_AS_ACTION_NEVER,
- isEnabled, new Runnable(){
- @Override
- public void run() {
- mBackend.startDreaming();
- }});
-
- // create "when to dream" overflow menu item
- MenuItem whenToDream = createMenuItem(menu,
- R.string.screensaver_settings_when_to_dream,
- MenuItem.SHOW_AS_ACTION_NEVER,
- isEnabled,
- new Runnable() {
- @Override
- public void run() {
- showDialog(DIALOG_WHEN_TO_DREAM);
- }});
-
- // create "help" overflow menu item (make sure it appears last)
- super.onCreateOptionsMenu(menu, inflater);
-
- mMenuItemsWhenEnabled = new MenuItem[] { start, whenToDream };
- }
-
- private MenuItem createMenuItem(Menu menu,
- int titleRes, int actionEnum, boolean isEnabled, final Runnable onClick) {
- MenuItem item = menu.add(titleRes);
- item.setShowAsAction(actionEnum);
- item.setEnabled(isEnabled);
- item.setOnMenuItemClickListener(new OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- onClick.run();
- return true;
- }
- });
- return item;
- }
-
- @Override
- public Dialog onCreateDialog(int dialogId) {
- logd("onCreateDialog(%s)", dialogId);
- if (dialogId == DIALOG_WHEN_TO_DREAM)
- return createWhenToDreamDialog();
- return super.onCreateDialog(dialogId);
- }
-
- @Override
- public int getDialogMetricsCategory(int dialogId) {
- if (dialogId == DIALOG_WHEN_TO_DREAM) {
- return MetricsEvent.DIALOG_DREAM_START_DELAY;
- }
- return 0;
- }
-
- private Dialog createWhenToDreamDialog() {
- final CharSequence[] items = {
- mContext.getString(R.string.screensaver_settings_summary_dock),
- mContext.getString(R.string.screensaver_settings_summary_sleep),
- mContext.getString(R.string.screensaver_settings_summary_either_short)
- };
-
- int initialSelection = mBackend.isActivatedOnDock() && mBackend.isActivatedOnSleep() ? 2
- : mBackend.isActivatedOnDock() ? 0
- : mBackend.isActivatedOnSleep() ? 1
- : -1;
-
- return new AlertDialog.Builder(mContext)
- .setTitle(R.string.screensaver_settings_when_to_dream)
- .setSingleChoiceItems(items, initialSelection, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int item) {
- mBackend.setActivatedOnDock(item == 0 || item == 2);
- mBackend.setActivatedOnSleep(item == 1 || item == 2);
- dialog.dismiss();
- }
- })
- .create();
- }
-
- @Override
- public void onPause() {
- logd("onPause()");
- super.onPause();
-
- mContext.unregisterReceiver(mPackageReceiver);
- }
-
- @Override
- public void onResume() {
- logd("onResume()");
- super.onResume();
- refreshFromBackend();
-
- // listen for package changes
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_PACKAGE_ADDED);
- filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
- filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
- filter.addDataScheme(PACKAGE_SCHEME);
- mContext.registerReceiver(mPackageReceiver , filter);
- }
-
- public static int getSummaryResource(Context context) {
- DreamBackend backend = new DreamBackend(context);
- boolean isEnabled = backend.isEnabled();
- boolean activatedOnSleep = backend.isActivatedOnSleep();
- boolean activatedOnDock = backend.isActivatedOnDock();
- boolean activatedOnEither = activatedOnSleep && activatedOnDock;
- return !isEnabled ? R.string.screensaver_settings_summary_off
- : activatedOnEither ? R.string.screensaver_settings_summary_either_long
- : activatedOnSleep ? R.string.screensaver_settings_summary_sleep
- : activatedOnDock ? R.string.screensaver_settings_summary_dock
- : 0;
- }
-
- public static CharSequence getSummaryTextWithDreamName(Context context) {
- DreamBackend backend = new DreamBackend(context);
- boolean isEnabled = backend.isEnabled();
- if (!isEnabled) {
- return context.getString(R.string.screensaver_settings_summary_off);
- } else {
- return backend.getActiveDreamName();
- }
- }
-
- private void refreshFromBackend() {
- logd("refreshFromBackend()");
- mRefreshing = true;
- boolean dreamsEnabled = mBackend.isEnabled();
- if (mSwitchBar.isChecked() != dreamsEnabled) {
- mSwitchBar.setChecked(dreamsEnabled);
- }
-
- if (getPreferenceScreen() == null) {
- setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getContext()));
- }
- getPreferenceScreen().removeAll();
- if (dreamsEnabled) {
- List<DreamBackend.DreamInfo> dreamInfos = mBackend.getDreamInfos();
- final int N = dreamInfos.size();
- for (int i = 0; i < N; i++) {
- getPreferenceScreen().addPreference(
- new DreamInfoPreference(getPrefContext(), dreamInfos.get(i)));
- }
- }
- if (mMenuItemsWhenEnabled != null) {
- for (MenuItem menuItem : mMenuItemsWhenEnabled) {
- menuItem.setEnabled(dreamsEnabled);
- }
- }
- mRefreshing = false;
- }
-
- private static void logd(String msg, Object... args) {
- if (DEBUG) Log.d(TAG, args == null || args.length == 0 ? msg : String.format(msg, args));
- }
-
- private class DreamInfoPreference extends Preference {
-
- private final DreamInfo mInfo;
-
- public DreamInfoPreference(Context context, DreamInfo info) {
- super(context);
- mInfo = info;
- setLayoutResource(R.layout.dream_info_row);
- setTitle(mInfo.caption);
- setIcon(mInfo.icon);
- }
-
- public void onBindViewHolder(final PreferenceViewHolder holder) {
- super.onBindViewHolder(holder);
-
- // bind radio button
- RadioButton radioButton = (RadioButton) holder.findViewById(android.R.id.button1);
- radioButton.setChecked(mInfo.isActive);
- radioButton.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- holder.itemView.onTouchEvent(event);
- return false;
- }
- });
-
- // bind settings button + divider
- boolean showSettings = mInfo.settingsComponentName != null;
- View settingsDivider = holder.findViewById(R.id.divider);
- settingsDivider.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE);
-
- ImageView settingsButton = (ImageView) holder.findViewById(android.R.id.button2);
- settingsButton.setVisibility(showSettings ? View.VISIBLE : View.INVISIBLE);
- settingsButton.setAlpha(mInfo.isActive ? 1f : Utils.DISABLED_ALPHA);
- settingsButton.setEnabled(mInfo.isActive);
- settingsButton.setFocusable(mInfo.isActive);
- settingsButton.setOnClickListener(new OnClickListener(){
- @Override
- public void onClick(View v) {
- mBackend.launchSettings(mInfo);
- }
- });
- }
-
- @Override
- public void performClick() {
- if (mInfo.isActive)
- return;
- for (int i = 0; i < getPreferenceScreen().getPreferenceCount(); i++) {
- DreamInfoPreference preference =
- (DreamInfoPreference) getPreferenceScreen().getPreference(i);
- preference.mInfo.isActive = false;
- preference.notifyChanged();
- }
- mInfo.isActive = true;
- mBackend.setActiveDream(mInfo.componentName);
- notifyChanged();
- }
- }
-
- private class PackageReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- logd("PackageReceiver.onReceive");
- refreshFromBackend();
- }
- }
-}
diff --git a/src/com/android/settings/LicenseHtmlGeneratorFromXml.java b/src/com/android/settings/LicenseHtmlGeneratorFromXml.java
new file mode 100644
index 0000000..7025c5a
--- /dev/null
+++ b/src/com/android/settings/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.settings;
+
+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/src/com/android/settings/LicenseHtmlLoader.java b/src/com/android/settings/LicenseHtmlLoader.java
new file mode 100644
index 0000000..9717926
--- /dev/null
+++ b/src/com/android/settings/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.settings;
+
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+import com.android.settings.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.
+ */
+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/src/com/android/settings/RadioInfo.java b/src/com/android/settings/RadioInfo.java
index 880f3fe..406d293 100644
--- a/src/com/android/settings/RadioInfo.java
+++ b/src/com/android/settings/RadioInfo.java
@@ -412,7 +412,7 @@
imsVolteProvisionedSwitch = (Switch) findViewById(R.id.volte_provisioned_switch);
imsVtProvisionedSwitch = (Switch) findViewById(R.id.vt_provisioned_switch);
imsWfcProvisionedSwitch = (Switch) findViewById(R.id.wfc_provisioned_switch);
- eabProvisionedSwitch = (Switch) findViewById(R.id.wfc_provisioned_switch);
+ eabProvisionedSwitch = (Switch) findViewById(R.id.eab_provisioned_switch);
radioPowerOnSwitch = (Switch) findViewById(R.id.radio_power);
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index e2404ec..2e6aed0 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -169,6 +169,7 @@
}
}
public static class WebViewAppPickerActivity extends SettingsActivity { /* empty */ }
+ public static class LegacySupportActivity extends SettingsActivity{ /* empty */ }
// Top level categories for new IA
public static class NetworkDashboardActivity extends SettingsActivity {}
@@ -177,6 +178,5 @@
public static class StorageDashboardActivity extends SettingsActivity {}
public static class UserAndAccountDashboardActivity extends SettingsActivity {}
public static class SystemDashboardActivity extends SettingsActivity {}
- public static class SupportDashboardActivity extends SettingsActivity {}
}
diff --git a/src/com/android/settings/SettingsLicenseActivity.java b/src/com/android/settings/SettingsLicenseActivity.java
index e0b7efe..5b23a68 100644
--- a/src/com/android/settings/SettingsLicenseActivity.java
+++ b/src/com/android/settings/SettingsLicenseActivity.java
@@ -17,32 +17,87 @@
package com.android.settings;
import android.app.Activity;
+import android.app.LoaderManager;
import android.content.ActivityNotFoundException;
+import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
+import android.content.Loader;
import android.net.Uri;
import android.os.Bundle;
import android.os.StrictMode;
import android.os.SystemProperties;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.content.FileProvider;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
+import com.android.settings.users.RestrictedProfileSettings;
+
import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
/**
* The "dialog" that shows from "License" in the Settings app.
*/
-public class SettingsLicenseActivity extends Activity {
+public class SettingsLicenseActivity extends Activity implements
+ LoaderManager.LoaderCallbacks<File> {
private static final String TAG = "SettingsLicenseActivity";
private static final String DEFAULT_LICENSE_PATH = "/system/etc/NOTICE.html.gz";
private static final String PROPERTY_LICENSE_PATH = "ro.config.license_path";
+ private static final int LOADER_ID_LICENSE_HTML_LOADER = 0;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- final String path = SystemProperties.get(PROPERTY_LICENSE_PATH, DEFAULT_LICENSE_PATH);
+ final String licenseHtmlPath =
+ SystemProperties.get(PROPERTY_LICENSE_PATH, DEFAULT_LICENSE_PATH);
+ if (isFilePathValid(licenseHtmlPath)) {
+ showSelectedFile(licenseHtmlPath);
+ } else {
+ showHtmlFromDefaultXmlFiles();
+ }
+ }
+
+ @Override
+ public Loader<File> onCreateLoader(int id, Bundle args) {
+ return new LicenseHtmlLoader(this);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<File> loader, File generatedHtmlFile) {
+ showGeneratedHtmlFile(generatedHtmlFile);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<File> loader) {
+ }
+
+ private void showHtmlFromDefaultXmlFiles() {
+ getLoaderManager().initLoader(LOADER_ID_LICENSE_HTML_LOADER, Bundle.EMPTY, this);
+ }
+
+ @VisibleForTesting
+ Uri getUriFromGeneratedHtmlFile(File generatedHtmlFile) {
+ return FileProvider.getUriForFile(this, RestrictedProfileSettings.FILE_PROVIDER_AUTHORITY,
+ generatedHtmlFile);
+ }
+
+ private void showGeneratedHtmlFile(File generatedHtmlFile) {
+ if (generatedHtmlFile != null) {
+ showHtmlFromUri(getUriFromGeneratedHtmlFile(generatedHtmlFile));
+ } else {
+ Log.e(TAG, "Failed to generate.");
+ showErrorAndFinish();
+ }
+ }
+
+ private void showSelectedFile(final String path) {
if (TextUtils.isEmpty(path)) {
Log.e(TAG, "The system property for the license file is empty");
showErrorAndFinish();
@@ -50,18 +105,24 @@
}
final File file = new File(path);
- if (!file.exists() || file.length() == 0) {
+ if (!isFileValid(file)) {
Log.e(TAG, "License file " + path + " does not exist");
showErrorAndFinish();
return;
}
+ showHtmlFromUri(Uri.fromFile(file));
+ }
+ private void showHtmlFromUri(Uri uri) {
// Kick off external viewer due to WebView security restrictions; we
// carefully point it at HTMLViewer, since it offers to decompress
// before viewing.
final Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(Uri.fromFile(file), "text/html");
+ intent.setDataAndType(uri, "text/html");
intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.settings_license_activity_title));
+ if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setPackage("com.android.htmlviewer");
@@ -79,4 +140,13 @@
.show();
finish();
}
+
+ private boolean isFilePathValid(final String path) {
+ return !TextUtils.isEmpty(path) && isFileValid(new File(path));
+ }
+
+ @VisibleForTesting
+ boolean isFileValid(final File file) {
+ return file.exists() && file.length() != 0;
+ }
}
diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java
index 44dd353..eb3e726 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettings.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettings.java
@@ -478,7 +478,7 @@
final String serviceState = serviceEnabled ?
getString(R.string.accessibility_summary_state_enabled) :
getString(R.string.accessibility_summary_state_disabled);
- final String serviceSummary = info.loadSummary(getPackageManager());
+ final CharSequence serviceSummary = info.loadSummary(getPackageManager());
final String stateSummaryCombo = getString(
R.string.accessibility_summary_default_combination,
serviceState, serviceSummary);
diff --git a/src/com/android/settings/accessibility/ShortcutServicePickerFragment.java b/src/com/android/settings/accessibility/ShortcutServicePickerFragment.java
index 3250521..d7749ea 100644
--- a/src/com/android/settings/accessibility/ShortcutServicePickerFragment.java
+++ b/src/com/android/settings/accessibility/ShortcutServicePickerFragment.java
@@ -69,7 +69,7 @@
candidates.add(new DefaultAppInfo(mPm,
UserHandle.myUserId(),
installedServiceInfo.getComponentName(),
- installedServiceInfo.loadSummary(mPm.getPackageManager()),
+ (String) installedServiceInfo.loadSummary(mPm.getPackageManager()),
true /* enabled */));
}
diff --git a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java b/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
index 51225aa..2ea61bc 100644
--- a/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
+++ b/src/com/android/settings/applications/AppAndNotificationDashboardFragment.java
@@ -62,6 +62,7 @@
private static List<PreferenceController> buildPreferenceControllers(Context context) {
final List<PreferenceController> controllers = new ArrayList<>();
controllers.add(new SpecialAppAccessPreferenceController(context));
+ controllers.add(new AppPermissionsPreferenceController(context));
return controllers;
}
diff --git a/src/com/android/settings/applications/AppPermissionsPreferenceController.java b/src/com/android/settings/applications/AppPermissionsPreferenceController.java
new file mode 100644
index 0000000..57ec6d8
--- /dev/null
+++ b/src/com/android/settings/applications/AppPermissionsPreferenceController.java
@@ -0,0 +1,143 @@
+/*
+ * 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.settings.applications;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.support.v7.preference.Preference;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+import com.android.settings.R;
+import com.android.settings.core.PreferenceController;
+import java.util.List;
+import java.util.Set;
+
+public class AppPermissionsPreferenceController extends PreferenceController {
+
+ private static final String TAG = "AppPermissionPrefCtrl";
+ private static final String KEY_APP_PERMISSION_GROUPS = "manage_perms";
+ private static final String[] PERMISSION_GROUPS = new String[] {
+ "android.permission-group.LOCATION",
+ "android.permission-group.MICROPHONE",
+ "android.permission-group.CAMERA",
+ "android.permission-group.SMS",
+ "android.permission-group.CONTACTS",
+ "android.permission-group.PHONE"};
+
+ private static final int NUM_PERMISSION_TO_USE = 3;
+
+ private final PackageManager mPackageManager;
+
+ public AppPermissionsPreferenceController(Context context) {
+ super(context);
+ mPackageManager = context.getPackageManager();
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_APP_PERMISSION_GROUPS;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final String summary = getSummary();
+ if (summary != null) {
+ preference.setSummary(summary);
+ }
+ }
+
+ /*
+ Summary text looks like: Apps using Permission1, Permission2, Permission3
+ The 3 permissions are the first three from the list which any app has granted:
+ Location, Microphone, Camera, Sms, Contacts, and Phone
+ */
+ private String getSummary() {
+ final Set<String> permissions = getAllPermissionsInGroups();
+ Set<String> grantedPermissionGroups = getGrantedPermissionGroups(permissions);
+ CharSequence summary = null;
+ int count = 0;
+ for (String group : PERMISSION_GROUPS) {
+ if (!grantedPermissionGroups.contains(group)) {
+ continue;
+ }
+ summary = concatSummaryText(summary, group);
+ if (++count >= NUM_PERMISSION_TO_USE) {
+ break;
+ }
+ }
+ return count > 0 ? mContext.getString(R.string.app_permissions_summary, summary) : null;
+ }
+
+ private Set<String> getGrantedPermissionGroups(Set<String> permissions) {
+ ArraySet<String> grantedPermissionGroups = new ArraySet<>();
+ List<PackageInfo> installedPackages =
+ mPackageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS);
+ for (PackageInfo installedPackage : installedPackages) {
+ if (installedPackage.permissions == null) {
+ continue;
+ }
+ for (PermissionInfo permissionInfo : installedPackage.permissions) {
+ if (permissions.contains(permissionInfo.name)
+ && !grantedPermissionGroups.contains(permissionInfo.group)) {
+ grantedPermissionGroups.add(permissionInfo.group);
+ }
+ }
+ }
+ return grantedPermissionGroups;
+ }
+
+ private CharSequence concatSummaryText(CharSequence currentSummary, String permission) {
+ final CharSequence label = getPermissionGroupLabel(permission);
+ if (TextUtils.isEmpty(currentSummary)) {
+ return label;
+ }
+ return mContext.getString(R.string.join_many_items_middle, currentSummary, label);
+ }
+
+ private CharSequence getPermissionGroupLabel(String group) {
+ try {
+ final PermissionGroupInfo groupInfo = mPackageManager.getPermissionGroupInfo(group, 0);
+ return groupInfo.loadLabel(mPackageManager);
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Error getting permissions label.", e);
+ }
+ return group;
+ }
+
+ private Set<String> getAllPermissionsInGroups() {
+ ArraySet<String> result = new ArraySet<>();
+ for (String group : PERMISSION_GROUPS) {
+ try {
+ final List<PermissionInfo> permissions =
+ mPackageManager.queryPermissionsByGroup(group, 0);
+ for (PermissionInfo permissionInfo : permissions) {
+ result.add(permissionInfo.name);
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Error getting permissions in group "+group, e);
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 5d61fe5..965501f 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -130,13 +130,13 @@
public static final int SIZE_EXTERNAL = 2;
// Filter options used for displayed list of applications
- // The order which they appear is the order they will show when spinner is present.
+ // Filters will appear sorted based on their value defined here.
public static final int FILTER_APPS_POWER_WHITELIST = 0;
public static final int FILTER_APPS_POWER_WHITELIST_ALL = 1;
public static final int FILTER_APPS_ALL = 2;
public static final int FILTER_APPS_ENABLED = 3;
- public static final int FILTER_APPS_DISABLED = 4;
- public static final int FILTER_APPS_INSTANT = 5;
+ public static final int FILTER_APPS_INSTANT = 4;
+ public static final int FILTER_APPS_DISABLED = 5;
public static final int FILTER_APPS_BLOCKED = 6;
public static final int FILTER_APPS_PERSONAL = 7;
public static final int FILTER_APPS_WORK = 8;
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index caac95f..b8cf856 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -25,7 +25,7 @@
import com.android.settings.DeviceAdminSettings;
import com.android.settings.DeviceInfoSettings;
import com.android.settings.DisplaySettings;
-import com.android.settings.DreamSettings;
+import com.android.settings.dream.DreamSettings;
import com.android.settings.IccLockSettings;
import com.android.settings.MasterClear;
import com.android.settings.PrivacySettings;
@@ -80,6 +80,7 @@
import com.android.settings.fuelgauge.BatterySaverSettings;
import com.android.settings.fuelgauge.PowerUsageSummary;
import com.android.settings.gestures.AssistGestureSettings;
+import com.android.settings.gestures.CameraLiftTriggerSettings;
import com.android.settings.gestures.DoubleTapPowerSettings;
import com.android.settings.gestures.DoubleTapScreenSettings;
import com.android.settings.gestures.DoubleTwistGestureSettings;
@@ -177,6 +178,7 @@
AccountSyncSettings.class.getName(),
AssistGestureSettings.class.getName(),
SwipeToNotificationSettings.class.getName(),
+ CameraLiftTriggerSettings.class.getName(),
DoubleTapPowerSettings.class.getName(),
DoubleTapScreenSettings.class.getName(),
PickupGestureSettings.class.getName(),
@@ -260,7 +262,6 @@
Settings.SecuritySettingsActivity.class.getName(),
Settings.AccessibilitySettingsActivity.class.getName(),
Settings.SystemDashboardActivity.class.getName(),
- Settings.SupportDashboardActivity.class.getName(),
// Home page > Network & Internet
Settings.WifiSettingsActivity.class.getName(),
Settings.DataUsageSummaryActivity.class.getName(),
diff --git a/src/com/android/settings/dashboard/SupportDashboardActivity.java b/src/com/android/settings/dashboard/SupportDashboardActivity.java
new file mode 100644
index 0000000..6cd6612
--- /dev/null
+++ b/src/com/android/settings/dashboard/SupportDashboardActivity.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.settings.dashboard;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import com.android.settings.Settings.LegacySupportActivity;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.overlay.SupportFeatureProvider;
+
+/**
+ * Trampoline activity that decides which version of support should be shown to the user.
+ */
+public class SupportDashboardActivity extends Activity {
+
+ public SupportDashboardActivity() {}
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ SupportFeatureProvider supportFeatureProvider = FeatureFactory.getFactory(this)
+ .getSupportFeatureProvider(this);
+
+ // try to launch support v2 if we have the feature provider
+ if (supportFeatureProvider != null && supportFeatureProvider.isSupportV2Enabled()) {
+ supportFeatureProvider.startSupportV2(this);
+ } else {
+ startActivity(new Intent(this, LegacySupportActivity.class));
+ }
+ finish();
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
index 2a5ac17..57f7345 100644
--- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
+++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java
@@ -18,6 +18,7 @@
import android.app.Activity;
import android.app.LoaderManager;
+import android.app.usage.StorageStatsManager;
import android.content.Context;
import android.content.Loader;
import android.graphics.drawable.Drawable;
@@ -29,6 +30,7 @@
import android.provider.SearchIndexableResource;
import android.support.annotation.VisibleForTesting;
import android.util.SparseArray;
+import android.view.View;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
@@ -44,9 +46,11 @@
import com.android.settings.deviceinfo.storage.StorageItemPreferenceController;
import com.android.settings.deviceinfo.storage.StorageSummaryDonutPreferenceController;
import com.android.settings.deviceinfo.storage.UserIconLoader;
+import com.android.settings.deviceinfo.storage.VolumeSizesLoader;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settingslib.applications.StorageStatsSource;
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
import java.util.ArrayList;
@@ -58,9 +62,12 @@
private static final String TAG = "StorageDashboardFrag";
private static final int STORAGE_JOB_ID = 0;
private static final int ICON_JOB_ID = 1;
+ private static final int VOLUME_SIZE_JOB_ID = 2;
private static final int OPTIONS_MENU_MIGRATE_DATA = 100;
private VolumeInfo mVolume;
+ private PrivateStorageInfo mStorageInfo;
+ private SparseArray<StorageAsyncLoader.AppsStorageResult> mAppsResult;
private StorageSummaryDonutPreferenceController mSummaryController;
private StorageItemPreferenceController mPreferenceController;
@@ -81,29 +88,6 @@
}
initializeOptionsMenu(activity);
-
- final long sharedDataSize = mVolume.getPath().getTotalSpace();
- long totalSize = sm.getPrimaryStorageSize();
- long systemSize = totalSize - sharedDataSize;
-
- if (totalSize <= 0) {
- totalSize = sharedDataSize;
- systemSize = 0;
- }
-
- final long usedBytes = totalSize - mVolume.getPath().getFreeSpace();
- mSummaryController.updateBytes(usedBytes, totalSize);
- mPreferenceController.setVolume(mVolume);
- mPreferenceController.setUsedSize(usedBytes);
- mPreferenceController.setTotalSize(totalSize);
- for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) {
- PreferenceController controller = mSecondaryUsers.get(i);
- if (controller instanceof SecondaryUserController) {
- SecondaryUserController userController = (SecondaryUserController) controller;
- userController.setTotalSize(totalSize);
-
- }
- }
}
@VisibleForTesting
@@ -116,10 +100,40 @@
}
@Override
+ public void onViewCreated(View v, Bundle savedInstanceState) {
+ super.onViewCreated(v, savedInstanceState);
+ setLoading(true, false);
+ }
+
+ @Override
public void onResume() {
super.onResume();
getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
getLoaderManager().initLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks());
+ getLoaderManager().initLoader(VOLUME_SIZE_JOB_ID, Bundle.EMPTY, new VolumeSizeCallbacks());
+ }
+
+ private void onReceivedSizes() {
+ if (mStorageInfo == null || mAppsResult == null) {
+ return;
+ }
+
+ long privateUsedBytes = mStorageInfo.totalBytes - mStorageInfo.freeBytes;
+ mSummaryController.updateBytes(privateUsedBytes, mStorageInfo.totalBytes);
+ mPreferenceController.setVolume(mVolume);
+ mPreferenceController.setUsedSize(privateUsedBytes);
+ mPreferenceController.setTotalSize(mStorageInfo.totalBytes);
+ for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) {
+ PreferenceController controller = mSecondaryUsers.get(i);
+ if (controller instanceof SecondaryUserController) {
+ SecondaryUserController userController = (SecondaryUserController) controller;
+ userController.setTotalSize(mStorageInfo.totalBytes);
+ }
+ }
+
+ mPreferenceController.onLoadFinished(mAppsResult.get(UserHandle.myUserId()));
+ updateSecondaryUserControllers(mSecondaryUsers, mAppsResult);
+ setLoading(false, true);
}
@Override
@@ -224,8 +238,8 @@
@Override
public void onLoadFinished(Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> loader,
SparseArray<StorageAsyncLoader.AppsStorageResult> data) {
- mPreferenceController.onLoadFinished(data.get(UserHandle.myUserId()));
- updateSecondaryUserControllers(mSecondaryUsers, data);
+ mAppsResult = data;
+ onReceivedSizes();
}
@Override
@@ -260,4 +274,31 @@
@Override
public void onLoaderReset(Loader<SparseArray<Drawable>> loader) {}
}
+
+ public final class VolumeSizeCallbacks
+ implements LoaderManager.LoaderCallbacks<PrivateStorageInfo> {
+ @Override
+ public Loader<PrivateStorageInfo> onCreateLoader(int id, Bundle args) {
+ Context context = getContext();
+ StorageManager sm = context.getSystemService(StorageManager.class);
+ StorageManagerVolumeProvider smvp = new StorageManagerVolumeProvider(sm);
+ final StorageStatsManager stats = context.getSystemService(StorageStatsManager.class);
+ return new VolumeSizesLoader(context, smvp, stats, mVolume);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<PrivateStorageInfo> loader) {}
+
+ @Override
+ public void onLoadFinished(
+ Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo) {
+ if (privateStorageInfo == null) {
+ getActivity().finish();
+ return;
+ }
+
+ mStorageInfo = privateStorageInfo;
+ onReceivedSizes();
+ }
+ }
}
diff --git a/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java
index 890d99e..91c4a6b 100644
--- a/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java
+++ b/src/com/android/settings/deviceinfo/storage/StorageSummaryDonutPreferenceController.java
@@ -34,6 +34,7 @@
public class StorageSummaryDonutPreferenceController extends PreferenceController {
private long mUsedBytes;
private long mTotalBytes;
+ private StorageSummaryDonutPreference mSummary;
public StorageSummaryDonutPreferenceController(Context context) {
super(context);
@@ -41,9 +42,8 @@
@Override
public void displayPreference(PreferenceScreen screen) {
- StorageSummaryDonutPreference summary = (StorageSummaryDonutPreference)
- screen.findPreference("pref_summary");
- summary.setEnabled(true);
+ mSummary = (StorageSummaryDonutPreference) screen.findPreference("pref_summary");
+ mSummary.setEnabled(true);
}
@Override
@@ -61,6 +61,13 @@
summary.setEnabled(true);
}
+ /** Invalidates the data on the view and re-renders. */
+ public void invalidateData() {
+ if (mSummary != null) {
+ updateState(mSummary);
+ }
+ }
+
@Override
public boolean isAvailable() {
return true;
@@ -79,6 +86,7 @@
public void updateBytes(long used, long total) {
mUsedBytes = used;
mTotalBytes = total;
+ invalidateData();
}
/**
diff --git a/src/com/android/settings/deviceinfo/storage/VolumeSizesLoader.java b/src/com/android/settings/deviceinfo/storage/VolumeSizesLoader.java
new file mode 100644
index 0000000..720f151
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/storage/VolumeSizesLoader.java
@@ -0,0 +1,68 @@
+/*
+ * 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.settings.deviceinfo.storage;
+
+import android.app.usage.StorageStatsManager;
+import android.content.Context;
+import android.os.storage.VolumeInfo;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.settings.utils.AsyncLoader;
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
+
+import java.io.IOException;
+
+public class VolumeSizesLoader extends AsyncLoader<PrivateStorageInfo> {
+ private StorageVolumeProvider mVolumeProvider;
+ private StorageStatsManager mStats;
+ private VolumeInfo mVolume;
+
+ public VolumeSizesLoader(
+ Context context,
+ StorageVolumeProvider volumeProvider,
+ StorageStatsManager stats,
+ VolumeInfo volume) {
+ super(context);
+ mVolumeProvider = volumeProvider;
+ mStats = stats;
+ mVolume = volume;
+ }
+
+ @Override
+ protected void onDiscardResult(PrivateStorageInfo result) {}
+
+ @Override
+ public PrivateStorageInfo loadInBackground() {
+ PrivateStorageInfo volumeSizes;
+ try {
+ volumeSizes = getVolumeSize(mVolumeProvider, mStats, mVolume);
+ } catch (IOException e) {
+ return null;
+ }
+ return volumeSizes;
+ }
+
+ @VisibleForTesting
+ static PrivateStorageInfo getVolumeSize(
+ StorageVolumeProvider storageVolumeProvider, StorageStatsManager stats, VolumeInfo info)
+ throws IOException {
+ long privateTotalBytes = storageVolumeProvider.getTotalBytes(stats, info);
+ long privateFreeBytes = storageVolumeProvider.getFreeBytes(stats, info);
+ return new PrivateStorageInfo(privateFreeBytes, privateTotalBytes);
+ }
+}
diff --git a/src/com/android/settings/display/ScreenSaverPreferenceController.java b/src/com/android/settings/display/ScreenSaverPreferenceController.java
index fab9062..7a10802 100644
--- a/src/com/android/settings/display/ScreenSaverPreferenceController.java
+++ b/src/com/android/settings/display/ScreenSaverPreferenceController.java
@@ -16,8 +16,8 @@
import android.content.Context;
import android.support.v7.preference.Preference;
-import com.android.settings.DreamSettings;
import com.android.settings.core.PreferenceController;
+import com.android.settings.dream.DreamSettings;
public class ScreenSaverPreferenceController extends PreferenceController {
diff --git a/src/com/android/settings/display/ThemePreferenceController.java b/src/com/android/settings/display/ThemePreferenceController.java
index a8d47d6..b535993 100644
--- a/src/com/android/settings/display/ThemePreferenceController.java
+++ b/src/com/android/settings/display/ThemePreferenceController.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.om.IOverlayManager;
import android.content.om.OverlayInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.RemoteException;
@@ -35,6 +36,7 @@
import libcore.util.Objects;
+import java.util.ArrayList;
import java.util.List;
public class ThemePreferenceController extends PreferenceController implements
@@ -109,12 +111,22 @@
return true;
}
+ private boolean isChangeableOverlay(String packageName) {
+ try {
+ PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
+ return pi != null && !pi.isStaticOverlay;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
private String getTheme() {
try {
List<OverlayInfo> infos = mOverlayService.getOverlayInfosForTarget("android",
UserHandle.myUserId());
for (int i = 0, size = infos.size(); i < size; i++) {
- if (infos.get(i).isEnabled()) {
+ if (infos.get(i).isEnabled() &&
+ isChangeableOverlay(infos.get(i).packageName)) {
return infos.get(i).packageName;
}
}
@@ -141,11 +153,13 @@
try {
List<OverlayInfo> infos = mOverlayService.getOverlayInfosForTarget("android",
UserHandle.myUserId());
- String[] pkgs = new String[infos.size()];
+ List<String> pkgs = new ArrayList(infos.size());
for (int i = 0, size = infos.size(); i < size; i++) {
- pkgs[i] = infos.get(i).packageName;
+ if (isChangeableOverlay(infos.get(i).packageName)) {
+ pkgs.add(infos.get(i).packageName);
+ }
}
- return pkgs;
+ return pkgs.toArray(new String[pkgs.size()]);
} catch (RemoteException e) {
}
return new String[0];
diff --git a/src/com/android/settings/dream/CurrentDreamPicker.java b/src/com/android/settings/dream/CurrentDreamPicker.java
new file mode 100644
index 0000000..da9bf9e
--- /dev/null
+++ b/src/com/android/settings/dream/CurrentDreamPicker.java
@@ -0,0 +1,117 @@
+/*
+ * 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.settings.dream;
+
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.widget.RadioButtonPickerFragment;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.DreamInfo;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public final class CurrentDreamPicker extends RadioButtonPickerFragment {
+
+ private DreamBackend mBackend;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ mBackend = DreamBackend.getInstance(context);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DREAM;
+ }
+
+ @Override
+ protected boolean setDefaultKey(String key) {
+ Map<String, ComponentName> componentNameMap = getDreamComponentsMap();
+ if (componentNameMap.get(key) != null) {
+ mBackend.setActiveDream(componentNameMap.get(key));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected String getDefaultKey() {
+ return mBackend.getActiveDream().flattenToString();
+ }
+
+ @Override
+ protected List<? extends CandidateInfo> getCandidates() {
+ final List<DreamCandidateInfo> candidates;
+ candidates = mBackend.getDreamInfos().stream()
+ .map(DreamCandidateInfo::new)
+ .collect(Collectors.toList());
+
+ return candidates;
+ }
+
+ @Override
+ protected void onSelectionPerformed(boolean success) {
+ super.onSelectionPerformed(success);
+
+ getActivity().finish();
+ }
+
+ private Map<String, ComponentName> getDreamComponentsMap() {
+ Map<String, ComponentName> comps = new HashMap<>();
+ mBackend.getDreamInfos()
+ .forEach((info) ->
+ comps.put(info.componentName.flattenToString(), info.componentName));
+
+ return comps;
+ }
+
+ private static final class DreamCandidateInfo extends CandidateInfo {
+ private final CharSequence name;
+ private final Drawable icon;
+ private final String key;
+
+ DreamCandidateInfo(DreamInfo info) {
+ super(true);
+
+ name = info.caption;
+ icon = info.icon;
+ key = info.componentName.flattenToString();
+ }
+
+ @Override
+ public CharSequence loadLabel() {
+ return name;
+ }
+
+ @Override
+ public Drawable loadIcon() {
+ return icon;
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+ }
+}
diff --git a/src/com/android/settings/dream/CurrentDreamPreferenceController.java b/src/com/android/settings/dream/CurrentDreamPreferenceController.java
new file mode 100644
index 0000000..5b448f8
--- /dev/null
+++ b/src/com/android/settings/dream/CurrentDreamPreferenceController.java
@@ -0,0 +1,79 @@
+/*
+ * 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.settings.dream;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.widget.GearPreference;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.DreamInfo;
+import java.util.Optional;
+
+public class CurrentDreamPreferenceController extends PreferenceController {
+ private final DreamBackend mBackend;
+ private final static String TAG = "CurrentDreamPreferenceController";
+ private final static String CURRENT_SCREENSAVER = "current_screensaver";
+
+ public CurrentDreamPreferenceController(Context context) {
+ super(context);
+ mBackend = DreamBackend.getInstance(context);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return mBackend.getDreamInfos().size() > 0;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return CURRENT_SCREENSAVER;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+
+ preference.setSummary(mBackend.getActiveDreamName());
+ setGearClickListenerForPreference(preference);
+ }
+
+ private void setGearClickListenerForPreference(Preference preference) {
+ if (!(preference instanceof GearPreference)) return;
+
+ GearPreference gearPreference = (GearPreference)preference;
+ Optional<DreamInfo> info = getActiveDreamInfo();
+ if (!info.isPresent() || info.get().settingsComponentName == null) {
+ gearPreference.setOnGearClickListener(null);
+ return;
+ }
+ gearPreference.setOnGearClickListener(gearPref -> launchScreenSaverSettings());
+ }
+
+ private void launchScreenSaverSettings() {
+ Optional<DreamInfo> info = getActiveDreamInfo();
+ if (!info.isPresent()) return;
+ mBackend.launchSettings(info.get());
+ }
+
+ private Optional<DreamInfo> getActiveDreamInfo() {
+ return mBackend.getDreamInfos()
+ .stream()
+ .filter((info) -> info.isActive)
+ .findFirst();
+ }
+}
diff --git a/src/com/android/settings/dream/DreamSettings.java b/src/com/android/settings/dream/DreamSettings.java
new file mode 100644
index 0000000..8cf9edb
--- /dev/null
+++ b/src/com/android/settings/dream/DreamSettings.java
@@ -0,0 +1,147 @@
+/*
+ * 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.settings.dream;
+
+import android.provider.SearchIndexableResource;
+import android.support.annotation.VisibleForTesting;
+import com.android.settings.R;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.WhenToDream;
+import java.util.ArrayList;
+import android.content.Context;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.android.settingslib.dream.DreamBackend.EITHER;
+import static com.android.settingslib.dream.DreamBackend.NEVER;
+import static com.android.settingslib.dream.DreamBackend.WHILE_CHARGING;
+import static com.android.settingslib.dream.DreamBackend.WHILE_DOCKED;
+
+public class DreamSettings extends DashboardFragment {
+
+ private static final String TAG = "DreamSettings";
+ static final String WHILE_CHARGING_ONLY = "while_charging_only";
+ static final String WHILE_DOCKED_ONLY = "while_docked_only";
+ static final String EITHER_CHARGING_OR_DOCKED = "either_charging_or_docked";
+ static final String NEVER_DREAM = "never";
+
+ @WhenToDream
+ static int getSettingFromPrefKey(String key) {
+ switch (key) {
+ case WHILE_CHARGING_ONLY:
+ return WHILE_CHARGING;
+ case WHILE_DOCKED_ONLY:
+ return WHILE_DOCKED;
+ case EITHER_CHARGING_OR_DOCKED:
+ return EITHER;
+ case NEVER_DREAM:
+ default:
+ return NEVER;
+ }
+ }
+
+ static String getKeyFromSetting(@WhenToDream int dreamSetting) {
+ switch (dreamSetting) {
+ case WHILE_CHARGING:
+ return WHILE_CHARGING_ONLY;
+ case WHILE_DOCKED:
+ return WHILE_DOCKED_ONLY;
+ case EITHER:
+ return EITHER_CHARGING_OR_DOCKED;
+ case NEVER:
+ default:
+ return NEVER_DREAM;
+ }
+ }
+
+ static int getDreamSettingDescriptionResId(@WhenToDream int dreamSetting) {
+ switch (dreamSetting) {
+ case WHILE_CHARGING:
+ return R.string.screensaver_settings_summary_sleep;
+ case WHILE_DOCKED:
+ return R.string.screensaver_settings_summary_dock;
+ case EITHER:
+ return R.string.screensaver_settings_summary_either_long;
+ case NEVER:
+ default:
+ return R.string.screensaver_settings_summary_never;
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsEvent.DREAM;
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.dream_fragment_overview;
+ }
+
+ @Override
+ protected String getLogTag() {
+ return TAG;
+ }
+
+ @Override
+ protected List<PreferenceController> getPreferenceControllers(Context context) {
+ return buildPreferenceControllers(context);
+ }
+
+ public static CharSequence getSummaryTextWithDreamName(Context context) {
+ DreamBackend backend = DreamBackend.getInstance(context);
+ return getSummaryTextFromBackend(backend, context);
+ }
+
+ @VisibleForTesting
+ static CharSequence getSummaryTextFromBackend(DreamBackend backend, Context context) {
+ if (!backend.isEnabled()) {
+ return context.getString(R.string.screensaver_settings_summary_off);
+ } else {
+ return backend.getActiveDreamName();
+ }
+ }
+
+ private static List<PreferenceController> buildPreferenceControllers(Context context) {
+ List<PreferenceController> controllers = new ArrayList<>();
+ controllers.add(new CurrentDreamPreferenceController(context));
+ controllers.add(new WhenToDreamPreferenceController(context));
+ controllers.add(new StartNowPreferenceController(context));
+ return controllers;
+ }
+
+ public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER
+ = new BaseSearchIndexProvider() {
+ @Override
+ public List<SearchIndexableResource> getXmlResourcesToIndex(
+ Context context, boolean enabled) {
+ final SearchIndexableResource sir = new SearchIndexableResource(context);
+ sir.xmlResId = R.xml.dream_fragment_overview;
+ return Arrays.asList(sir);
+ }
+
+ @Override
+ public List<PreferenceController> getPreferenceControllers(Context context) {
+ return buildPreferenceControllers(context);
+ }
+ };
+}
+
diff --git a/src/com/android/settings/dream/StartNowPreferenceController.java b/src/com/android/settings/dream/StartNowPreferenceController.java
new file mode 100644
index 0000000..994b70b
--- /dev/null
+++ b/src/com/android/settings/dream/StartNowPreferenceController.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.settings.dream;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.widget.Button;
+import com.android.settings.R;
+import com.android.settings.applications.LayoutPreference;
+import com.android.settings.core.PreferenceController;
+import com.android.settingslib.dream.DreamBackend;
+
+public class StartNowPreferenceController extends PreferenceController {
+ private static final String TAG = "StartNowPreferenceController";
+ private static final String PREF_KEY = "dream_start_now_button_container";
+ private final DreamBackend mBackend;
+
+ public StartNowPreferenceController(Context context) {
+ super(context);
+
+ mBackend = DreamBackend.getInstance(context);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return PREF_KEY;
+ }
+
+ @Override
+ public void displayPreference(PreferenceScreen screen) {
+ super.displayPreference(screen);
+
+ LayoutPreference pref = (LayoutPreference) screen.findPreference(getPreferenceKey());
+ Button startButton = (Button)pref.findViewById(R.id.dream_start_now_button);
+ startButton.setOnClickListener(v -> mBackend.startDreaming());
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+
+ Button startButton = (Button)((LayoutPreference)preference)
+ .findViewById(R.id.dream_start_now_button);
+ startButton.setEnabled(mBackend.getWhenToDreamSetting() != DreamBackend.NEVER);
+ }
+}
diff --git a/src/com/android/settings/dream/WhenToDreamPicker.java b/src/com/android/settings/dream/WhenToDreamPicker.java
new file mode 100644
index 0000000..a55064d
--- /dev/null
+++ b/src/com/android/settings/dream/WhenToDreamPicker.java
@@ -0,0 +1,115 @@
+/*
+ * 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.settings.dream;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.widget.RadioButtonPickerFragment;
+import com.android.settingslib.dream.DreamBackend;
+import java.util.ArrayList;
+import java.util.List;
+
+public class WhenToDreamPicker extends RadioButtonPickerFragment {
+
+ private static final String TAG = "WhenToDreamPicker";
+ private DreamBackend mBackend;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ mBackend = DreamBackend.getInstance(context);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsProto.MetricsEvent.DREAM;
+ }
+
+ @Override
+ protected List<? extends CandidateInfo> getCandidates() {
+ final String[] entries = entries();
+ final String[] values = keys();
+ final List<WhenToDreamCandidateInfo> candidates = new ArrayList<>();
+
+ if (entries == null || entries.length <= 0) return null;
+ if (values == null || values.length != entries.length) {
+ throw new IllegalArgumentException("Entries and values must be of the same length.");
+ }
+
+ for (int i = 0; i < entries.length; i++) {
+ candidates.add(new WhenToDreamCandidateInfo(entries[i], values[i]));
+ }
+
+ return candidates;
+ }
+
+ private String[] entries() {
+ return getResources().getStringArray(R.array.when_to_start_screensaver_entries);
+ }
+
+ private String[] keys() {
+ return getResources().getStringArray(R.array.when_to_start_screensaver_values);
+ }
+
+ @Override
+ protected String getDefaultKey() {
+ return DreamSettings.getKeyFromSetting(mBackend.getWhenToDreamSetting());
+ }
+
+ @Override
+ protected boolean setDefaultKey(String key) {
+ mBackend.setWhenToDream(DreamSettings.getSettingFromPrefKey(key));
+ return true;
+ }
+
+ @Override
+ protected void onSelectionPerformed(boolean success) {
+ super.onSelectionPerformed(success);
+
+ getActivity().finish();
+ }
+
+ private final class WhenToDreamCandidateInfo extends CandidateInfo {
+ private final String name;
+ private final String key;
+
+ WhenToDreamCandidateInfo(String title, String value) {
+ super(true);
+
+ name = title;
+ key = value;
+ }
+
+ @Override
+ public CharSequence loadLabel() {
+ return name;
+ }
+
+ @Override
+ public Drawable loadIcon() {
+ return null;
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+ }
+}
diff --git a/src/com/android/settings/dream/WhenToDreamPreferenceController.java b/src/com/android/settings/dream/WhenToDreamPreferenceController.java
new file mode 100644
index 0000000..0d870fd
--- /dev/null
+++ b/src/com/android/settings/dream/WhenToDreamPreferenceController.java
@@ -0,0 +1,52 @@
+/*
+ * 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.settings.dream;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import com.android.settings.core.PreferenceController;
+import com.android.settingslib.dream.DreamBackend;
+
+public class WhenToDreamPreferenceController extends PreferenceController {
+
+ private static final String WHEN_TO_START = "when_to_start";
+ private final DreamBackend mBackend;
+
+ WhenToDreamPreferenceController(Context context) {
+ super(context);
+
+ mBackend = DreamBackend.getInstance(context);
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+
+ int resId = DreamSettings.getDreamSettingDescriptionResId(mBackend.getWhenToDreamSetting());
+ preference.setSummary(preference.getContext().getString(resId));
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return WHEN_TO_START;
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java b/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java
index f7cb191..49a0179 100644
--- a/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java
+++ b/src/com/android/settings/fuelgauge/AppButtonsPreferenceController.java
@@ -60,6 +60,7 @@
import com.android.settings.enterprise.DevicePolicyManagerWrapper;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
import java.util.ArrayList;
@@ -155,7 +156,8 @@
@Override
public boolean isAvailable() {
- return mAppEntry != null;
+ // TODO(b/37313605): Re-enable once this controller supports instant apps
+ return mAppEntry != null && !AppUtils.isInstant(mAppEntry.info);
}
@Override
diff --git a/src/com/android/settings/fuelgauge/PowerUsageSummary.java b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
index bcf830b..349a08e 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageSummary.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageSummary.java
@@ -17,9 +17,11 @@
package com.android.settings.fuelgauge;
import android.app.Activity;
+import android.app.LoaderManager;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.Loader;
import android.graphics.drawable.Drawable;
import android.os.BatteryStats;
import android.os.Build;
@@ -59,6 +61,10 @@
import com.android.settings.display.AutoBrightnessPreferenceController;
import com.android.settings.display.BatteryPercentagePreferenceController;
import com.android.settings.display.TimeoutPreferenceController;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment;
+import com.android.settings.fuelgauge.anomaly.AnomalyLoader;
+import com.android.settings.fuelgauge.anomaly.AnomalyPreferenceController;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.widget.FooterPreferenceMixin;
@@ -74,7 +80,8 @@
* Displays a list of apps and subsystems that consume power, ordered by how much power was
* consumed since the last time it was unplugged.
*/
-public class PowerUsageSummary extends PowerUsageBase {
+public class PowerUsageSummary extends PowerUsageBase implements
+ AnomalyDialogFragment.AnomalyDialogListener {
static final String TAG = "PowerUsageSummary";
@@ -84,6 +91,7 @@
private static final String KEY_BATTERY_HEADER = "battery_header";
private static final int MAX_ITEMS_TO_LIST = USE_FAKE_DATA ? 30 : 10;
private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10;
+ private static final int ANOMALY_LOADER = 1;
private static final String KEY_SCREEN_USAGE = "screen_usage";
private static final String KEY_TIME_SINCE_LAST_FULL_CHARGE = "last_full_charge";
@@ -117,8 +125,29 @@
private LayoutPreference mBatteryLayoutPref;
private PreferenceGroup mAppListGroup;
+ private AnomalyPreferenceController mAnomalyPreferenceController;
private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
+ private LoaderManager.LoaderCallbacks<List<Anomaly>> mAnomalyLoaderCallbacks =
+ new LoaderManager.LoaderCallbacks<List<Anomaly>>() {
+
+ @Override
+ public Loader<List<Anomaly>> onCreateLoader(int id, Bundle args) {
+ return new AnomalyLoader(getContext(), mStatsHelper);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<Anomaly>> loader, List<Anomaly> data) {
+ // show high usage preference if possible
+ mAnomalyPreferenceController.updateAnomalyPreference(data);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<Anomaly>> loader) {
+
+ }
+ };
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -130,6 +159,7 @@
mLastFullChargePref = (PowerGaugePreference) findPreference(
KEY_TIME_SINCE_LAST_FULL_CHARGE);
mFooterPreferenceMixin.createFooterPreference().setTitle(R.string.battery_footer_summary);
+ mAnomalyPreferenceController = new AnomalyPreferenceController(this);
mBatteryUtils = BatteryUtils.getInstance(getContext());
@@ -163,6 +193,9 @@
@Override
public boolean onPreferenceTreeClick(Preference preference) {
+ if (mAnomalyPreferenceController.onPreferenceTreeClick(preference)) {
+ return true;
+ }
if (KEY_BATTERY_HEADER.equals(preference.getKey())) {
performBatteryHeaderClick();
return true;
@@ -403,6 +436,8 @@
return;
}
+ getLoaderManager().initLoader(ANOMALY_LOADER, null, mAnomalyLoaderCallbacks);
+
cacheRemoveAllPrefs(mAppListGroup);
mAppListGroup.setOrderingAsAdded(false);
boolean addedSome = false;
@@ -690,6 +725,11 @@
}
};
+ @Override
+ public void onAnomalyHandled(Anomaly anomaly) {
+ mAnomalyPreferenceController.hideAnomalyPreference();
+ }
+
private static class SummaryProvider implements SummaryLoader.SummaryProvider {
private final Context mContext;
private final SummaryLoader mLoader;
diff --git a/src/com/android/settings/fuelgauge/anomaly/Anomaly.java b/src/com/android/settings/fuelgauge/anomaly/Anomaly.java
new file mode 100644
index 0000000..a10d3f4
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/Anomaly.java
@@ -0,0 +1,136 @@
+/*
+ * 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.settings.fuelgauge.anomaly;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Data that represents an app has been detected as anomaly. It contains
+ *
+ * 1. Basic information of the app(i.e. uid, package name)
+ * 2. Type of anomaly
+ * 3. Data that has been detected as anomaly(i.e wakelock time)
+ */
+public class Anomaly implements Parcelable {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({AnomalyType.WAKE_LOCK})
+ public @interface AnomalyType {
+ int WAKE_LOCK = 0;
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({AnomalyActionType.FORCE_STOP})
+ public @interface AnomalyActionType {
+ int FORCE_STOP = 0;
+ }
+
+ /**
+ * Type of this this anomaly
+ */
+ public final int type;
+ public final int uid;
+ public final long wakelockTimeMs;
+
+ /**
+ * Display name of this anomaly, usually it is the app name
+ */
+ public final String displayName;
+ public final String packageName;
+
+ private Anomaly(Builder builder) {
+ type = builder.mType;
+ uid = builder.mUid;
+ displayName = builder.mDisplayName;
+ packageName = builder.mPackageName;
+ wakelockTimeMs = builder.mWakeLockTimeMs;
+ }
+
+ private Anomaly(Parcel in) {
+ type = in.readInt();
+ uid = in.readInt();
+ displayName = in.readString();
+ packageName = in.readString();
+ wakelockTimeMs = in.readLong();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(type);
+ dest.writeInt(uid);
+ dest.writeString(displayName);
+ dest.writeString(packageName);
+ dest.writeLong(wakelockTimeMs);
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public Anomaly createFromParcel(Parcel in) {
+ return new Anomaly(in);
+ }
+
+ public Anomaly[] newArray(int size) {
+ return new Anomaly[size];
+ }
+ };
+
+ public static final class Builder {
+ @AnomalyType
+ private int mType;
+ private int mUid;
+ private String mDisplayName;
+ private String mPackageName;
+ private long mWakeLockTimeMs;
+
+ public Builder setType(@AnomalyType int type) {
+ mType = type;
+ return this;
+ }
+
+ public Builder setUid(int uid) {
+ mUid = uid;
+ return this;
+ }
+
+ public Builder setDisplayName(String displayName) {
+ mDisplayName = displayName;
+ return this;
+ }
+
+ public Builder setPackageName(String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ public Builder setWakeLockTimeMs(long wakeLockTimeMs) {
+ mWakeLockTimeMs = wakeLockTimeMs;
+ return this;
+ }
+
+ public Anomaly build() {
+ return new Anomaly(this);
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java
new file mode 100644
index 0000000..122b478
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragment.java
@@ -0,0 +1,103 @@
+/*
+ * 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.settings.fuelgauge.anomaly;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.settings.R;
+import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.fuelgauge.anomaly.action.AnomalyAction;
+
+/**
+ * Dialog Fragment to show action dialog for each anomaly
+ */
+public class AnomalyDialogFragment extends InstrumentedDialogFragment implements
+ DialogInterface.OnClickListener {
+
+ private static final String ARG_ANOMALY = "anomaly";
+
+ @VisibleForTesting
+ Anomaly mAnomaly;
+
+ /**
+ * Listener to give the control back to target fragment
+ */
+ public interface AnomalyDialogListener {
+ /**
+ * This method is invoked once anomaly is handled, then target fragment could do
+ * extra work. One example is that fragment could remove the anomaly preference
+ * since it has been handled
+ *
+ * @param anomaly that has been handled
+ */
+ void onAnomalyHandled(Anomaly anomaly);
+ }
+
+ public static AnomalyDialogFragment newInstance(Anomaly anomaly) {
+ AnomalyDialogFragment dialogFragment = new AnomalyDialogFragment();
+
+ Bundle args = new Bundle(1);
+ args.putParcelable(ARG_ANOMALY, anomaly);
+ dialogFragment.setArguments(args);
+
+ return dialogFragment;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO(b/37681923): add anomaly metric id
+ return 0;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final AnomalyDialogListener lsn = (AnomalyDialogListener) getTargetFragment();
+ if (lsn == null) {
+ return;
+ }
+
+ final AnomalyAction anomalyAction = AnomalyUtils.getAnomalyAction(mAnomaly.type);
+ anomalyAction.handlePositiveAction(mAnomaly.packageName);
+ lsn.onAnomalyHandled(mAnomaly);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Bundle bundle = getArguments();
+ mAnomaly = bundle.getParcelable(ARG_ANOMALY);
+
+ final Context context = getContext();
+ final AnomalyAction anomalyAction = AnomalyUtils.getAnomalyAction(mAnomaly.type);
+ switch (anomalyAction.getActionType()) {
+ case Anomaly.AnomalyActionType.FORCE_STOP:
+ return new AlertDialog.Builder(context)
+ .setTitle(R.string.force_stop_dlg_title)
+ .setMessage(R.string.force_stop_dlg_text)
+ .setPositiveButton(R.string.dlg_ok, this)
+ .setNegativeButton(R.string.dlg_cancel, null)
+ .create();
+ default:
+ throw new IllegalArgumentException("unknown type " + mAnomaly.type);
+ }
+ }
+
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java
new file mode 100644
index 0000000..e689256a
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyLoader.java
@@ -0,0 +1,51 @@
+/*
+ * 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.settings.fuelgauge.anomaly;
+
+import android.content.Context;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.fuelgauge.anomaly.checker.WakeLockAnomalyDetector;
+import com.android.settings.utils.AsyncLoader;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Loader to compute which apps are anomaly and return a anomaly list. It will return
+ * an empty list if there is no anomaly.
+ */
+//TODO(b/36924669): add test for this file, for now it seems there is nothing to test
+public class AnomalyLoader extends AsyncLoader<List<Anomaly>> {
+ private BatteryStatsHelper mBatteryStatsHelper;
+
+ public AnomalyLoader(Context context, BatteryStatsHelper batteryStatsHelper) {
+ super(context);
+ mBatteryStatsHelper = batteryStatsHelper;
+ }
+
+ @Override
+ protected void onDiscardResult(List<Anomaly> result) {}
+
+ @Override
+ public List<Anomaly> loadInBackground() {
+ final List<Anomaly> anomalies = new ArrayList<>();
+ anomalies.addAll(new WakeLockAnomalyDetector().detectAnomalies(mBatteryStatsHelper));
+
+ return anomalies;
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceController.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceController.java
new file mode 100644
index 0000000..b499690
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceController.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.settings.fuelgauge.anomaly;
+
+import android.support.annotation.VisibleForTesting;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+
+import java.util.List;
+
+/**
+ * Manager that responsible for updating anomaly preference and handling preference click.
+ */
+public class AnomalyPreferenceController {
+ private static final String TAG = "AnomalyPreferenceController";
+ @VisibleForTesting
+ static final String ANOMALY_KEY = "high_usage";
+ private static final int REQUEST_ANOMALY_ACTION = 0;
+ private PreferenceFragment mFragment;
+ @VisibleForTesting
+ Preference mAnomalyPreference;
+ @VisibleForTesting
+ List<Anomaly> mAnomalies;
+
+ public AnomalyPreferenceController(PreferenceFragment fragment) {
+ mFragment = fragment;
+ mAnomalyPreference = mFragment.getPreferenceScreen().findPreference(ANOMALY_KEY);
+ hideAnomalyPreference();
+ }
+
+ public boolean onPreferenceTreeClick(Preference preference) {
+ if (mAnomalies != null && ANOMALY_KEY.equals(preference.getKey())) {
+ if (mAnomalies.size() == 1) {
+ final Anomaly anomaly = mAnomalies.get(0);
+ AnomalyDialogFragment dialogFragment = AnomalyDialogFragment.newInstance(anomaly);
+ dialogFragment.setTargetFragment(mFragment, REQUEST_ANOMALY_ACTION);
+ dialogFragment.show(mFragment.getFragmentManager(), TAG);
+ } else {
+ //TODO(b/37681665): start a new fragment to handle it
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Update anomaly preference based on {@code anomalies}, also store a reference
+ * of {@paramref anomalies}, which would be used in {@link #onPreferenceTreeClick(Preference)}
+ *
+ * @param anomalies used to update the summary, this method will store a reference of it
+ */
+ public void updateAnomalyPreference(List<Anomaly> anomalies) {
+ mAnomalies = anomalies;
+
+ mAnomalyPreference.setVisible(true);
+ //TODO(b/36924669): update summary for anomaly preference
+ }
+
+ public void hideAnomalyPreference() {
+ mAnomalyPreference.setVisible(false);
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
new file mode 100644
index 0000000..987482e
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/AnomalyUtils.java
@@ -0,0 +1,40 @@
+/*
+ * 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.settings.fuelgauge.anomaly;
+
+import com.android.settings.fuelgauge.anomaly.action.AnomalyAction;
+import com.android.settings.fuelgauge.anomaly.action.ForceStopAction;
+
+/**
+ * Utitily class for anomaly detection
+ */
+public class AnomalyUtils {
+
+ /**
+ * Return the corresponding {@link AnomalyAction} according to {@link AnomalyType}
+ *
+ * @return corresponding {@link AnomalyAction}, or null if cannot find it.
+ */
+ public static final AnomalyAction getAnomalyAction(int anomalyType) {
+ switch (anomalyType) {
+ case Anomaly.AnomalyType.WAKE_LOCK:
+ return new ForceStopAction();
+ default:
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java b/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java
new file mode 100644
index 0000000..8013797
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/action/AnomalyAction.java
@@ -0,0 +1,25 @@
+/*
+ * 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.settings.fuelgauge.anomaly.action;
+
+/**
+ * Interface for anomaly action, which is triggered if we need to handle the anomaly
+ */
+public interface AnomalyAction {
+ void handlePositiveAction(String packageName);
+ int getActionType();
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/action/ForceStopAction.java b/src/com/android/settings/fuelgauge/anomaly/action/ForceStopAction.java
new file mode 100644
index 0000000..be5d6ab
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/action/ForceStopAction.java
@@ -0,0 +1,34 @@
+/*
+ * 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.settings.fuelgauge.anomaly.action;
+
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+
+/**
+ * Force stop action for anomaly app, which means to stop the app which causes anomaly
+ */
+public class ForceStopAction implements AnomalyAction {
+ @Override
+ public void handlePositiveAction(String packageName) {
+ // force stop the package
+ }
+
+ @Override
+ public int getActionType() {
+ return Anomaly.AnomalyActionType.FORCE_STOP;
+ }
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/AnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/AnomalyDetector.java
new file mode 100644
index 0000000..b04ea66
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/checker/AnomalyDetector.java
@@ -0,0 +1,27 @@
+/*
+ * 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.settings.fuelgauge.anomaly.checker;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+
+import java.util.List;
+
+public interface AnomalyDetector {
+ List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper);
+}
diff --git a/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java b/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.java
new file mode 100644
index 0000000..a9ebc66
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/anomaly/checker/WakeLockAnomalyDetector.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.settings.fuelgauge.anomaly.checker;
+
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Check whether apps holding wakelock too long
+ */
+public class WakeLockAnomalyDetector implements AnomalyDetector {
+
+ @Override
+ public List<Anomaly> detectAnomalies(BatteryStatsHelper batteryStatsHelper) {
+ //TODO(b/36921529): check anomaly using the batteryStatsHelper
+ final List<Anomaly> anomalies = new ArrayList<>();
+ return anomalies;
+ }
+}
diff --git a/src/com/android/settings/gestures/SwipeToNotificationPreferenceController.java b/src/com/android/settings/gestures/SwipeToNotificationPreferenceController.java
index 22b88fc..89d38a1 100644
--- a/src/com/android/settings/gestures/SwipeToNotificationPreferenceController.java
+++ b/src/com/android/settings/gestures/SwipeToNotificationPreferenceController.java
@@ -60,7 +60,7 @@
@Override
protected boolean isSwitchPrefEnabled() {
return Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, 0)
+ Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, 1)
== 1;
}
}
diff --git a/src/com/android/settings/overlay/SupportFeatureProvider.java b/src/com/android/settings/overlay/SupportFeatureProvider.java
index 0f8d424..1831486 100644
--- a/src/com/android/settings/overlay/SupportFeatureProvider.java
+++ b/src/com/android/settings/overlay/SupportFeatureProvider.java
@@ -129,6 +129,19 @@
void startSupport(Activity activity, Account account, @SupportType int type);
/**
+ * Starts support v2, invokes the support home page. Will no-op if support v2 is not enabled.
+ *
+ * @param activity Calling activity.
+ */
+ void startSupportV2(Activity activity);
+
+ /**
+ * Checks if support v2 is enabled for this device.
+ * @return a boolean indicating if support v2 is enabled.
+ */
+ boolean isSupportV2Enabled();
+
+ /**
* Returns an {@link Intent} that opens help and allow user get help on sign in.
*/
Intent getSignInHelpIntent(Context context);
diff --git a/src/com/android/settings/search/SearchIndexableResources.java b/src/com/android/settings/search/SearchIndexableResources.java
index 63e8a7a..75ac350 100644
--- a/src/com/android/settings/search/SearchIndexableResources.java
+++ b/src/com/android/settings/search/SearchIndexableResources.java
@@ -47,6 +47,7 @@
import com.android.settings.deviceinfo.StorageDashboardFragment;
import com.android.settings.deviceinfo.StorageSettings;
import com.android.settings.display.ScreenZoomSettings;
+import com.android.settings.dream.DreamSettings;
import com.android.settings.enterprise.EnterprisePrivacySettings;
import com.android.settings.fuelgauge.BatterySaverSettings;
import com.android.settings.fuelgauge.PowerUsageAdvanced;
@@ -185,6 +186,7 @@
R.drawable.ic_settings_accessibility);
addIndex(ChannelImportanceSettings.class, NO_DATA_RES_ID,
R.drawable.ic_settings_notifications);
+ addIndex(DreamSettings.class, NO_DATA_RES_ID, R.drawable.ic_settings_display);
}
private SearchIndexableResources() {
diff --git a/src/com/android/settings/vpn2/LegacyVpnPreference.java b/src/com/android/settings/vpn2/LegacyVpnPreference.java
index a4d7221..4ef2808 100644
--- a/src/com/android/settings/vpn2/LegacyVpnPreference.java
+++ b/src/com/android/settings/vpn2/LegacyVpnPreference.java
@@ -34,7 +34,7 @@
LegacyVpnPreference(Context context) {
super(context, null /* attrs */);
- setIcon(R.drawable.ic_settings_24dp);
+ setIcon(R.drawable.ic_vpn_key);
}
public VpnProfile getProfile() {
diff --git a/tests/robotests/src/com/android/settings/LicenseHtmlGeneratorFromXmlTest.java b/tests/robotests/src/com/android/settings/LicenseHtmlGeneratorFromXmlTest.java
new file mode 100644
index 0000000..ef36e5f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/LicenseHtmlGeneratorFromXmlTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.settings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.xmlpull.v1.XmlPullParserException;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class LicenseHtmlGeneratorFromXmlTest {
+ private static final String VAILD_XML_STRING =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<licenses>\n" +
+ "<file-name contentId=\"0\">/file0</file-name>\n" +
+ "<file-name contentId=\"0\">/file1</file-name>\n" +
+ "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n" +
+ "</licenses>";
+
+ private static final String INVAILD_XML_STRING =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+ "<licenses2>\n" +
+ "<file-name contentId=\"0\">/file0</file-name>\n" +
+ "<file-name contentId=\"0\">/file1</file-name>\n" +
+ "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n" +
+ "</licenses2>";
+
+ private static final String EXPECTED_HTML_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>\n" +
+ "<li><a href=\"#id0\">/file0</a></li>\n" +
+ "<li><a href=\"#id0\">/file1</a></li>\n" +
+ "</ul>\n" +
+ "</div><!-- table of contents -->\n" +
+ "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n" +
+ "<tr id=\"id0\"><td class=\"same-license\">\n" +
+ "<div class=\"label\">Notices for file(s):</div>\n" +
+ "<div class=\"file-list\">\n" +
+ "/file0 <br/>\n" +
+ "/file1 <br/>\n" +
+ "</div><!-- file-list -->\n" +
+ "<pre class=\"license-text\">\n" +
+ "license content #0\n" +
+ "</pre><!-- license-text -->\n" +
+ "</td></tr><!-- same-license -->\n" +
+ "</table></body></html>\n";
+
+ @Test
+ public void testParseValidXmlStream() throws XmlPullParserException, IOException {
+ Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+
+ LicenseHtmlGeneratorFromXml.parse(
+ new InputStreamReader(new ByteArrayInputStream(VAILD_XML_STRING.getBytes())),
+ fileNameToContentIdMap, contentIdToFileContentMap);
+ assertThat(fileNameToContentIdMap.size()).isEqualTo(2);
+ assertThat(fileNameToContentIdMap.get("/file0")).isEqualTo("0");
+ assertThat(fileNameToContentIdMap.get("/file1")).isEqualTo("0");
+ assertThat(contentIdToFileContentMap.size()).isEqualTo(1);
+ assertThat(contentIdToFileContentMap.get("0")).isEqualTo("license content #0");
+ }
+
+ @Test(expected = XmlPullParserException.class)
+ public void testParseInvalidXmlStream() throws XmlPullParserException, IOException {
+ Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+
+ LicenseHtmlGeneratorFromXml.parse(
+ new InputStreamReader(new ByteArrayInputStream(INVAILD_XML_STRING.getBytes())),
+ fileNameToContentIdMap, contentIdToFileContentMap);
+ }
+
+ @Test
+ public void testGenerateHtml() {
+ Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+
+ fileNameToContentIdMap.put("/file0", "0");
+ fileNameToContentIdMap.put("/file1", "0");
+ contentIdToFileContentMap.put("0", "license content #0");
+
+ StringWriter output = new StringWriter();
+ LicenseHtmlGeneratorFromXml.generateHtml(
+ fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output));
+ assertThat(output.toString()).isEqualTo(EXPECTED_HTML_STRING);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/LicenseHtmlLoaderTest.java b/tests/robotests/src/com/android/settings/LicenseHtmlLoaderTest.java
new file mode 100644
index 0000000..96e88c2
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/LicenseHtmlLoaderTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.settings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class LicenseHtmlLoaderTest {
+ @Mock
+ private Context mContext;
+
+ LicenseHtmlLoader newLicenseHtmlLoader(ArrayList<File> xmlFiles,
+ File cachedHtmlFile, boolean isCachedHtmlFileOutdated,
+ boolean generateHtmlFileSucceeded) {
+ LicenseHtmlLoader loader = spy(new LicenseHtmlLoader(mContext));
+ doReturn(xmlFiles).when(loader).getVaildXmlFiles();
+ doReturn(cachedHtmlFile).when(loader).getCachedHtmlFile();
+ doReturn(isCachedHtmlFileOutdated).when(loader).isCachedHtmlFileOutdated(any(), any());
+ doReturn(generateHtmlFileSucceeded).when(loader).generateHtmlFile(any(), any());
+ return loader;
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testLoadInBackground() {
+ ArrayList<File> xmlFiles = new ArrayList();
+ xmlFiles.add(new File("test.xml"));
+ File cachedHtmlFile = new File("test.html");
+
+ LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true);
+
+ assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile);
+ verify(loader).generateHtmlFile(any(), any());
+ }
+
+ @Test
+ public void testLoadInBackgroundWithNoVaildXmlFiles() {
+ ArrayList<File> xmlFiles = new ArrayList();
+ File cachedHtmlFile = new File("test.html");
+
+ LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true);
+
+ assertThat(loader.loadInBackground()).isNull();
+ verify(loader, never()).generateHtmlFile(any(), any());
+ }
+
+ @Test
+ public void testLoadInBackgroundWithNonOutdatedCachedHtmlFile() {
+ ArrayList<File> xmlFiles = new ArrayList();
+ xmlFiles.add(new File("test.xml"));
+ File cachedHtmlFile = new File("test.html");
+
+ LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, false, true);
+
+ assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile);
+ verify(loader, never()).generateHtmlFile(any(), any());
+ }
+
+ @Test
+ public void testLoadInBackgroundWithGenerateHtmlFileFailed() {
+ ArrayList<File> xmlFiles = new ArrayList();
+ xmlFiles.add(new File("test.xml"));
+ File cachedHtmlFile = new File("test.html");
+
+ LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, false);
+
+ assertThat(loader.loadInBackground()).isNull();
+ verify(loader).generateHtmlFile(any(), any());
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/SettingsLicenseActivityTest.java b/tests/robotests/src/com/android/settings/SettingsLicenseActivityTest.java
new file mode 100644
index 0000000..3e28a2a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/SettingsLicenseActivityTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.settings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Application;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
+import android.net.Uri;
+import android.os.Bundle;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.res.builder.RobolectricPackageManager;
+import org.robolectric.util.ActivityController;
+import org.robolectric.shadows.ShadowActivity;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SettingsLicenseActivityTest {
+ private ActivityController<SettingsLicenseActivity> mActivityController;
+ private SettingsLicenseActivity mActivity;
+ private Application mApplication;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mApplication = RuntimeEnvironment.application;
+ mActivityController = Robolectric.buildActivity(SettingsLicenseActivity.class);
+ mActivity = spy(mActivityController.get());
+ }
+
+ void assertEqualIntents(Intent actual, Intent expected) {
+ assertThat(actual.getAction()).isEqualTo(expected.getAction());
+ assertThat(actual.getDataString()).isEqualTo(expected.getDataString());
+ assertThat(actual.getType()).isEqualTo(expected.getType());
+ assertThat(actual.getCategories()).isEqualTo(expected.getCategories());
+ assertThat(actual.getPackage()).isEqualTo(expected.getPackage());
+ assertThat(actual.getFlags()).isEqualTo(expected.getFlags());
+ }
+
+ @Test
+ public void testOnCreateWithValidHtmlFile() {
+ SystemProperties.set("ro.config.license_path", "/system/etc/NOTICE.html.gz");
+
+ doReturn(true).when(mActivity).isFileValid(any());
+ mActivity.onCreate(null);
+
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(Uri.parse("file:///system/etc/NOTICE.html.gz"), "text/html");
+ intent.putExtra(Intent.EXTRA_TITLE, mActivity.getString(
+ R.string.settings_license_activity_title));
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setPackage("com.android.htmlviewer");
+
+ assertEqualIntents(shadowOf(mApplication).getNextStartedActivity(), intent);
+ }
+
+ @Test
+ public void testOnCreateWithGeneratedHtmlFile() {
+ doReturn(null).when(mActivity).onCreateLoader(anyInt(), any());
+ doReturn(Uri.parse("content://com.android.settings.files/my_cache/generated_test.html"))
+ .when(mActivity).getUriFromGeneratedHtmlFile(any());
+
+ mActivity.onCreate(null);
+ mActivity.onLoadFinished(null, new File("/generated_test.html"));
+
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(
+ Uri.parse("content://com.android.settings.files/my_cache/generated_test.html"),
+ "text/html");
+ intent.putExtra(Intent.EXTRA_TITLE, mActivity.getString(
+ R.string.settings_license_activity_title));
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ intent.setPackage("com.android.htmlviewer");
+
+ assertEqualIntents(shadowOf(mApplication).getNextStartedActivity(), intent);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/applications/AppPermissionsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/AppPermissionsPreferenceControllerTest.java
new file mode 100644
index 0000000..6e3cc4b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/applications/AppPermissionsPreferenceControllerTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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.settings.applications;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AppPermissionsPreferenceControllerTest {
+
+ private static final String PERM_LOCATION = "android.permission-group.LOCATION";
+ private static final String PERM_MICROPHONE = "android.permission-group.MICROPHONE";
+ private static final String PERM_CAMERA = "android.permission-group.CAMERA";
+ private static final String PERM_SMS = "android.permission-group.SMS";
+ private static final String PERM_CONTACTS = "android.permission-group.CONTACTS";
+ private static final String PERM_PHONE = "android.permission-group.PHONE";
+ private static final String LABEL_LOCATION = "Location";
+ private static final String LABEL_MICROPHONE = "Microphone";
+ private static final String LABEL_CAMERA = "Camera";
+ private static final String LABEL_SMS = "Sms";
+ private static final String LABEL_CONTACTS = "Contacts";
+ private static final String LABEL_PHONE = "Phone";
+
+ @Mock
+ private Preference mPreference;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private PermissionGroupInfo mGroupLocation;
+ @Mock
+ private PermissionGroupInfo mGroupMic;
+ @Mock
+ private PermissionGroupInfo mGroupCamera;
+ @Mock
+ private PermissionGroupInfo mGroupSms;
+ @Mock
+ private PermissionGroupInfo mGroupContacts;
+ @Mock
+ private PermissionGroupInfo mGroupPhone;
+
+ private Context mContext;
+ private AppPermissionsPreferenceController mController;
+ private PermissionInfo mPermLocation;
+ private PermissionInfo mPermMic;
+ private PermissionInfo mPermCamera;
+ private PermissionInfo mPermSms;
+ private PermissionInfo mPermContacts;
+ private PermissionInfo mPermPhone;
+
+ @Before
+ public void setUp() throws NameNotFoundException {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+
+ // create permission groups
+ when(mPackageManager.getPermissionGroupInfo(eq(PERM_LOCATION), anyInt()))
+ .thenReturn(mGroupLocation);
+ when(mGroupLocation.loadLabel(mPackageManager)).thenReturn(LABEL_LOCATION);
+ when(mPackageManager.getPermissionGroupInfo(eq(PERM_MICROPHONE), anyInt()))
+ .thenReturn(mGroupMic);
+ when(mGroupMic.loadLabel(mPackageManager)).thenReturn(LABEL_MICROPHONE);
+ when(mPackageManager.getPermissionGroupInfo(eq(PERM_CAMERA), anyInt()))
+ .thenReturn(mGroupCamera);
+ when(mGroupCamera.loadLabel(mPackageManager)).thenReturn(LABEL_CAMERA);
+ when(mPackageManager.getPermissionGroupInfo(eq(PERM_SMS), anyInt())).thenReturn(mGroupSms);
+ when(mGroupSms.loadLabel(mPackageManager)).thenReturn(LABEL_SMS);
+ when(mPackageManager.getPermissionGroupInfo(eq(PERM_CONTACTS), anyInt()))
+ .thenReturn(mGroupContacts);
+ when(mGroupContacts.loadLabel(mPackageManager)).thenReturn(LABEL_CONTACTS);
+ when(mPackageManager.getPermissionGroupInfo(eq(PERM_PHONE), anyInt()))
+ .thenReturn(mGroupPhone);
+ when(mGroupPhone.loadLabel(mPackageManager)).thenReturn(LABEL_PHONE);
+
+ // create permissions
+ mPermLocation = new PermissionInfo();
+ mPermLocation.name = "Permission1";
+ mPermLocation.group = PERM_LOCATION;
+ mPermMic = new PermissionInfo();
+ mPermMic.name = "Permission2";
+ mPermMic.group = PERM_MICROPHONE;
+ mPermCamera = new PermissionInfo();
+ mPermCamera.name = "Permission3";
+ mPermCamera.group = PERM_CAMERA;
+ mPermSms = new PermissionInfo();
+ mPermSms.name = "Permission4";
+ mPermSms.group = PERM_SMS;
+ mPermContacts = new PermissionInfo();
+ mPermContacts.name = "Permission4";
+ mPermContacts.group = PERM_CONTACTS;
+ mPermPhone = new PermissionInfo();
+ mPermPhone.name = "Permission4";
+ mPermPhone.group = PERM_PHONE;
+ final List<PermissionInfo> permissions = new ArrayList<>();
+ permissions.add(mPermLocation);
+ permissions.add(mPermMic);
+ permissions.add(mPermCamera);
+ permissions.add(mPermSms);
+ permissions.add(mPermContacts);
+ permissions.add(mPermPhone);
+ when(mPackageManager.queryPermissionsByGroup(anyString(), anyInt()))
+ .thenReturn(permissions);
+
+ mController = spy(new AppPermissionsPreferenceController(mContext));
+ }
+
+ @Test
+ public void isAvailable_shouldAlwaysReturnTrue() {
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void updateState_noGrantedPermissions_shouldNotSetSummary() {
+ final List<PackageInfo> installedPackages = new ArrayList<>();
+ final PackageInfo info = new PackageInfo();
+ installedPackages.add(info);
+ when(mPackageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS))
+ .thenReturn(installedPackages);
+
+ mController.updateState(mPreference);
+
+ verify(mPreference, never()).setSummary(anyString());
+ }
+
+ @Test
+ public void updateState_hasPermissions_shouldSetSummary() {
+ final List<PackageInfo> installedPackages = new ArrayList<>();
+ final PackageInfo info = new PackageInfo();
+ installedPackages.add(info);
+ when(mPackageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS))
+ .thenReturn(installedPackages);
+ PermissionInfo[] permissions = new PermissionInfo[4];
+ info.permissions = permissions;
+
+ permissions[0] = mPermLocation;
+ permissions[1] = mPermMic;
+ permissions[2] = mPermCamera;
+ permissions[3] = mPermSms;
+ mController.updateState(mPreference);
+ verify(mPreference).setSummary("Apps using Location, Microphone, Camera");
+
+ permissions[0] = mPermPhone;
+ permissions[1] = mPermMic;
+ permissions[2] = mPermCamera;
+ permissions[3] = mPermSms;
+ mController.updateState(mPreference);
+ verify(mPreference).setSummary("Apps using Microphone, Camera, Sms");
+
+ permissions[0] = mPermPhone;
+ permissions[1] = mPermMic;
+ permissions[2] = mPermContacts;
+ permissions[3] = mPermSms;
+ mController.updateState(mPreference);
+ verify(mPreference).setSummary("Apps using Microphone, Sms, Contacts");
+
+ permissions = new PermissionInfo[2];
+ info.permissions = permissions;
+ permissions[0] = mPermLocation;
+ permissions[1] = mPermCamera;
+ mController.updateState(mPreference);
+ verify(mPreference).setSummary("Apps using Location, Camera");
+
+ permissions = new PermissionInfo[1];
+ info.permissions = permissions;
+ permissions[0] = mPermCamera;
+ mController.updateState(mPreference);
+ verify(mPreference).setSummary("Apps using Camera");
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/VolumeSizesLoaderTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/VolumeSizesLoaderTest.java
new file mode 100644
index 0000000..3f9ccaf
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/VolumeSizesLoaderTest.java
@@ -0,0 +1,30 @@
+package com.android.settings.deviceinfo.storage;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.os.storage.VolumeInfo;
+
+import com.android.settingslib.deviceinfo.PrivateStorageInfo;
+import com.android.settingslib.deviceinfo.StorageVolumeProvider;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class VolumeSizesLoaderTest {
+ @Test
+ public void getVolumeSize_getsValidSizes() throws Exception {
+ VolumeInfo info = mock(VolumeInfo.class);
+ StorageVolumeProvider storageVolumeProvider = mock(StorageVolumeProvider.class);
+ when(storageVolumeProvider.getTotalBytes(any(), any())).thenReturn(10000L);
+ when(storageVolumeProvider.getFreeBytes(any(), any())).thenReturn(1000L);
+
+ PrivateStorageInfo storageInfo =
+ VolumeSizesLoader.getVolumeSize(storageVolumeProvider, null, info);
+
+ assertThat(storageInfo.freeBytes).isEqualTo(1000L);
+ assertThat(storageInfo.totalBytes).isEqualTo(10000L);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/dream/CurrentDreamPickerTest.java b/tests/robotests/src/com/android/settings/dream/CurrentDreamPickerTest.java
new file mode 100644
index 0000000..24082b6
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dream/CurrentDreamPickerTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.settings.dream;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.UserManager;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.DreamInfo;
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class CurrentDreamPickerTest {
+ private static String COMPONENT_KEY = "mocked_component_name_string";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private DreamBackend mBackend;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Activity mActivity;
+ @Mock
+ private UserManager mUserManager;
+ private CurrentDreamPicker mPicker;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ FakeFeatureFactory.setupForTest(mActivity);
+
+ mPicker = new CurrentDreamPicker();
+ mPicker.onAttach((Context)mActivity);
+
+ ReflectionHelpers.setField(mPicker, "mBackend", mBackend);
+ }
+
+ @Test
+ public void getDefaultShouldReturnActiveDream() {
+ ComponentName mockComponentName = mock(ComponentName.class);
+ when(mockComponentName.flattenToString()).thenReturn(COMPONENT_KEY);
+ when(mBackend.getActiveDream()).thenReturn(mockComponentName);
+
+ assertThat(mPicker.getDefaultKey()).isEqualTo(COMPONENT_KEY);
+ }
+
+ @Test
+ public void setDefaultShouldUpdateActiveDream() {
+ DreamInfo mockInfo = mock(DreamInfo.class);
+ ComponentName mockName = mock(ComponentName.class);
+
+ mockInfo.componentName = mockName;
+ when(mockName.flattenToString()).thenReturn(COMPONENT_KEY);
+ when(mBackend.getDreamInfos()).thenReturn(new ArrayList<>(Arrays.asList(mockInfo)));
+
+ mPicker.setDefaultKey(COMPONENT_KEY);
+
+ verify(mBackend).setActiveDream(mockName);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/dream/CurrentDreamPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/dream/CurrentDreamPreferenceControllerTest.java
new file mode 100644
index 0000000..c55f080
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dream/CurrentDreamPreferenceControllerTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.settings.dream;
+
+import android.content.ComponentName;
+import android.content.Context;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.widget.GearPreference;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.DreamInfo;
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class CurrentDreamPreferenceControllerTest {
+ private static String TAG = "CurrentDreamPreferenceControllerTest";
+
+ private CurrentDreamPreferenceController mController;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private DreamBackend mBackend;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+ @Mock
+ private DreamInfo mDreamInfo;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mController = new CurrentDreamPreferenceController(mContext);
+ ReflectionHelpers.setField(mController, "mBackend", mBackend);
+ }
+
+ @Test
+ public void isDisabledIfNoDreamsAvailable() {
+ when(mBackend.getDreamInfos()).thenReturn(new ArrayList<>(0));
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Test
+ public void isEnabledIfDreamsAvailable() {
+ when(mBackend.getDreamInfos()).thenReturn(new ArrayList<>(Arrays.asList(mDreamInfo)));
+
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void gearShowsIfActiveDreamInfoHasOptions() {
+ mDreamInfo.settingsComponentName = mock(ComponentName.class);
+ mDreamInfo.isActive = true;
+
+ when(mBackend.getDreamInfos()).thenReturn(new ArrayList<>(Arrays.asList(mDreamInfo)));
+
+ GearPreference mockPref = mock(GearPreference.class);
+ ArgumentCaptor<GearPreference.OnGearClickListener> captor =
+ ArgumentCaptor.forClass(GearPreference.OnGearClickListener.class);
+
+ // verify that updateState sets a non-null gear click listener
+ mController.updateState(mockPref);
+ verify(mockPref).setOnGearClickListener(captor.capture());
+ captor.getAllValues().forEach(listener -> assertThat(listener).isNotNull());
+ }
+
+ @Test
+ public void gearHidesIfActiveDreamInfoHasNoOptions() {
+ mDreamInfo.settingsComponentName = null;
+ mDreamInfo.isActive = true;
+
+ when(mBackend.getDreamInfos()).thenReturn(new ArrayList<>(Arrays.asList(mDreamInfo)));
+
+ GearPreference mockPref = mock(GearPreference.class);
+ ArgumentCaptor<GearPreference.OnGearClickListener> captor =
+ ArgumentCaptor.forClass(GearPreference.OnGearClickListener.class);
+
+ // setting a null onGearClickListener removes the gear from view
+ mController.updateState(mockPref);
+ verify(mockPref).setOnGearClickListener(captor.capture());
+ captor.getAllValues().forEach(listener -> assertThat(listener).isNull());
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/dream/DreamSettingsTest.java b/tests/robotests/src/com/android/settings/dream/DreamSettingsTest.java
new file mode 100644
index 0000000..95f57b3
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dream/DreamSettingsTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.settings.dream;
+
+import android.content.Context;
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.WhenToDream;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DreamSettingsTest {
+ private static final List<String> KEYS = Arrays.asList(
+ DreamSettings.WHILE_CHARGING_ONLY,
+ DreamSettings.WHILE_DOCKED_ONLY,
+ DreamSettings.EITHER_CHARGING_OR_DOCKED,
+ DreamSettings.NEVER_DREAM
+ );
+
+ private static final @WhenToDream int[] SETTINGS = {
+ DreamBackend.WHILE_CHARGING,
+ DreamBackend.WHILE_DOCKED,
+ DreamBackend.EITHER,
+ DreamBackend.NEVER,
+ };
+
+ private static final int[] RES_IDS = {
+ R.string.screensaver_settings_summary_sleep,
+ R.string.screensaver_settings_summary_dock,
+ R.string.screensaver_settings_summary_either_long,
+ R.string.screensaver_settings_summary_never
+ };
+
+ @Test
+ public void getSettingFromPrefKey() {
+ for (int i = 0; i < KEYS.size(); i++) {
+ assertThat(DreamSettings.getSettingFromPrefKey(KEYS.get(i)))
+ .isEqualTo(SETTINGS[i]);
+ }
+ // Default case
+ assertThat(DreamSettings.getSettingFromPrefKey("garbage value"))
+ .isEqualTo(DreamBackend.NEVER);
+ }
+
+ @Test
+ public void getKeyFromSetting() {
+ for (int i = 0; i < SETTINGS.length; i++) {
+ assertThat(DreamSettings.getKeyFromSetting(SETTINGS[i]))
+ .isEqualTo(KEYS.get(i));
+ }
+ // Default
+ assertThat(DreamSettings.getKeyFromSetting(-1))
+ .isEqualTo(DreamSettings.NEVER_DREAM);
+ }
+
+ @Test
+ public void getDreamSettingDescriptionResId() {
+ for (int i = 0; i < SETTINGS.length; i++) {
+ assertThat(DreamSettings.getDreamSettingDescriptionResId(SETTINGS[i]))
+ .isEqualTo(RES_IDS[i]);
+ }
+ // Default
+ assertThat(DreamSettings.getDreamSettingDescriptionResId(-1))
+ .isEqualTo(R.string.screensaver_settings_summary_never);
+ }
+
+ @Test
+ public void summaryText_whenDreamsAreOff() {
+ DreamBackend mockBackend = mock(DreamBackend.class);
+ Context mockContext = mock(Context.class);
+ when(mockBackend.isEnabled()).thenReturn(false);
+
+ assertThat(DreamSettings.getSummaryTextFromBackend(mockBackend, mockContext))
+ .isEqualTo(mockContext.getString(R.string.screensaver_settings_summary_off));
+ }
+
+ @Test
+ public void summaryTest_WhenDreamsAreOn() {
+ final String fakeName = "test_name";
+ DreamBackend mockBackend = mock(DreamBackend.class);
+ Context mockContext = mock(Context.class);
+ when(mockBackend.isEnabled()).thenReturn(true);
+ when(mockBackend.getActiveDreamName()).thenReturn(fakeName);
+
+ assertThat(DreamSettings.getSummaryTextFromBackend(mockBackend, mockContext))
+ .isEqualTo(fakeName);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/dream/StartNowPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/dream/StartNowPreferenceControllerTest.java
new file mode 100644
index 0000000..5cbd610
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dream/StartNowPreferenceControllerTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.settings.dream;
+
+import android.content.Context;
+import android.support.v7.preference.PreferenceScreen;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.applications.LayoutPreference;
+import com.android.settingslib.dream.DreamBackend;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class StartNowPreferenceControllerTest {
+ private StartNowPreferenceController mController;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+ @Mock
+ private PreferenceScreen mScreen;
+ @Mock
+ private LayoutPreference mLayoutPref;
+ @Mock
+ private Button mButton;
+ @Mock
+ private DreamBackend mBackend;
+
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mController = new StartNowPreferenceController(mContext);
+ when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mLayoutPref);
+ when(mLayoutPref.findViewById(R.id.dream_start_now_button)).thenReturn(mButton);
+
+ ReflectionHelpers.setField(mController, "mBackend", mBackend);
+ }
+
+ @Test
+ public void setsOnClickListenerForStartNow() {
+ ArgumentCaptor<OnClickListener> captor =
+ ArgumentCaptor.forClass(Button.OnClickListener.class);
+
+ mController.displayPreference(mScreen);
+ verify(mButton).setOnClickListener(captor.capture());
+ assertThat(captor.getValue()).isNotNull();
+ }
+
+ @Test
+ public void buttonIsDisabledWhenNeverDreaming() {
+ when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.NEVER);
+
+ mController.updateState(mLayoutPref);
+ verify(mButton).setEnabled(false);
+ }
+
+ @Test
+ public void buttonIsEnabledWhenDreamIsAvailable() {
+ when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.EITHER);
+
+ mController.updateState(mLayoutPref);
+ verify(mButton).setEnabled(true);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/dream/WhenToDreamPickerTest.java b/tests/robotests/src/com/android/settings/dream/WhenToDreamPickerTest.java
new file mode 100644
index 0000000..2571e1b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dream/WhenToDreamPickerTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.settings.dream;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.UserManager;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.dream.DreamBackend;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WhenToDreamPickerTest {
+ private WhenToDreamPicker mPicker;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private DreamBackend mBackend;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Activity mActivity;
+ @Mock
+ private UserManager mUserManager;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mActivity.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+ FakeFeatureFactory.setupForTest(mActivity);
+
+ mPicker = new WhenToDreamPicker();
+ mPicker.onAttach((Context)mActivity);
+
+ ReflectionHelpers.setField(mPicker, "mBackend", mBackend);
+ }
+
+ @Test
+ public void getDefaultKeyReturnsCurrentWhenToDreamSetting() {
+ when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.WHILE_CHARGING);
+ assertThat(mPicker.getDefaultKey())
+ .isEqualTo(DreamSettings.getKeyFromSetting(DreamBackend.WHILE_CHARGING));
+
+ when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.WHILE_DOCKED);
+ assertThat(mPicker.getDefaultKey())
+ .isEqualTo(DreamSettings.getKeyFromSetting(DreamBackend.WHILE_DOCKED));
+
+ when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.EITHER);
+ assertThat(mPicker.getDefaultKey())
+ .isEqualTo(DreamSettings.getKeyFromSetting(DreamBackend.EITHER));
+
+ when(mBackend.getWhenToDreamSetting()).thenReturn(DreamBackend.NEVER);
+ assertThat(mPicker.getDefaultKey())
+ .isEqualTo(DreamSettings.getKeyFromSetting(DreamBackend.NEVER));
+ }
+
+ @Test
+ public void setDreamWhileCharging() {
+ String key = DreamSettings.getKeyFromSetting(DreamBackend.WHILE_CHARGING);
+ mPicker.setDefaultKey(key);
+ verify(mBackend).setWhenToDream(DreamBackend.WHILE_CHARGING);
+ }
+
+ @Test
+ public void setDreamWhileDocked() {
+ String key = DreamSettings.getKeyFromSetting(DreamBackend.WHILE_DOCKED);
+ mPicker.setDefaultKey(key);
+ verify(mBackend).setWhenToDream(DreamBackend.WHILE_DOCKED);
+ }
+
+ @Test
+ public void setDreamWhileChargingOrDocked() {
+ String key = DreamSettings.getKeyFromSetting(DreamBackend.EITHER);
+ mPicker.setDefaultKey(key);
+ verify(mBackend).setWhenToDream(DreamBackend.EITHER);
+ }
+
+ @Test
+ public void setDreamNever() {
+ String key = DreamSettings.getKeyFromSetting(DreamBackend.NEVER);
+ mPicker.setDefaultKey(key);
+ verify(mBackend).setWhenToDream(DreamBackend.NEVER);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java
new file mode 100644
index 0000000..0eded7b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/dream/WhenToDreamPreferenceControllerTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.settings.dream;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settingslib.dream.DreamBackend;
+import com.android.settingslib.dream.DreamBackend.WhenToDream;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class WhenToDreamPreferenceControllerTest {
+ private WhenToDreamPreferenceController mController;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+ @Mock
+ private DreamBackend mBackend;
+
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mController = new WhenToDreamPreferenceController(mContext);
+ ReflectionHelpers.setField(mController, "mBackend", mBackend);
+ }
+
+ @Test
+ public void updateSummary() {
+ // Don't have to test the other settings because DreamSettings tests that all
+ // @WhenToDream values map to the correct ResId
+ final @WhenToDream int testSetting = DreamBackend.WHILE_CHARGING;
+ final Preference mockPref = mock(Preference.class);
+ when(mockPref.getContext()).thenReturn(mContext);
+ when(mBackend.getWhenToDreamSetting()).thenReturn(testSetting);
+ final String expectedString =
+ mContext.getString(DreamSettings.getDreamSettingDescriptionResId(testSetting));
+
+ mController.updateState(mockPref);
+ verify(mockPref).setSummary(expectedString);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AppButtonsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AppButtonsPreferenceControllerTest.java
index 85b4f81..a341d83 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/AppButtonsPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/AppButtonsPreferenceControllerTest.java
@@ -19,8 +19,8 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
@@ -48,7 +48,9 @@
import com.android.settings.core.lifecycle.Lifecycle;
import com.android.settings.enterprise.DevicePolicyManagerWrapper;
import com.android.settings.testutils.FakeFeatureFactory;
+import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.instantapps.InstantAppDataProvider;
import org.junit.Before;
import org.junit.Test;
@@ -61,6 +63,7 @@
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
@@ -189,6 +192,32 @@
}
@Test
+ public void testIsAvailable_nonInstantApp() throws Exception {
+ mController.mAppEntry = mAppEntry;
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ new InstantAppDataProvider() {
+ @Override
+ public boolean isInstantApp(ApplicationInfo info) {
+ return false;
+ }
+ });
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void testIsAvailable_instantApp() throws Exception {
+ mController.mAppEntry = mAppEntry;
+ ReflectionHelpers.setStaticField(AppUtils.class, "sInstantAppDataProvider",
+ new InstantAppDataProvider() {
+ @Override
+ public boolean isInstantApp(ApplicationInfo info) {
+ return true;
+ }
+ });
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Test
public void testUpdateUninstallButton_isDeviceAdminApp_setButtonDisable() {
doReturn(true).when(mController).handleDisableable(any());
mAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java
new file mode 100644
index 0000000..95a8ff3
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyDialogFragmentTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.settings.fuelgauge.anomaly;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowDialog;
+import org.robolectric.util.FragmentTestUtil;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AnomalyDialogFragmentTest {
+ @Anomaly.AnomalyType
+ private static final int ANOMALY_TYPE = Anomaly.AnomalyType.WAKE_LOCK;
+ private static final String PACKAGE_NAME = "com.android.app";
+ private static final int UID = 111;
+ private Anomaly mAnomaly;
+ private AnomalyDialogFragment mAnomalyDialogFragment;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ mAnomaly = new Anomaly.Builder()
+ .setType(ANOMALY_TYPE)
+ .setUid(UID)
+ .setPackageName(PACKAGE_NAME)
+ .build();
+
+ mAnomalyDialogFragment = AnomalyDialogFragment.newInstance(mAnomaly);
+ }
+
+ @Test
+ public void testOnCreateDialog_hasCorrectData() {
+ FragmentTestUtil.startFragment(mAnomalyDialogFragment);
+
+ assertThat(mAnomalyDialogFragment.mAnomaly).isEqualTo(mAnomaly);
+ }
+
+ @Test
+ public void testOnCreateDialog_hasCorrectDialog() {
+ FragmentTestUtil.startFragment(mAnomalyDialogFragment);
+
+ final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog();
+ ShadowAlertDialog shadowDialog = shadowOf(dialog);
+
+ assertThat(shadowDialog.getMessage()).isEqualTo(
+ mContext.getString(R.string.force_stop_dlg_text));
+ assertThat(shadowDialog.getTitle()).isEqualTo(
+ mContext.getString(R.string.force_stop_dlg_title));
+ assertThat(dialog.getButton(DialogInterface.BUTTON_POSITIVE).getText()).isEqualTo(
+ mContext.getString(R.string.dlg_ok));
+ assertThat(dialog.getButton(DialogInterface.BUTTON_NEGATIVE).getText()).isEqualTo(
+ mContext.getString(R.string.dlg_cancel));
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceControllerTest.java
new file mode 100644
index 0000000..f2e4482
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyPreferenceControllerTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.settings.fuelgauge.anomaly;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v14.preference.PreferenceFragment;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.Settings;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.fuelgauge.ButtonActionDialogFragmentTest;
+import com.android.settings.testutils.shadow.SettingsShadowResources;
+import com.android.settings.testutils.shadow.ShadowDynamicIndexableContentMonitor;
+import com.android.settings.testutils.shadow.ShadowEventLogWriter;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowAlertDialog;
+import org.robolectric.shadows.ShadowDialog;
+import org.robolectric.util.FragmentTestUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AnomalyPreferenceControllerTest {
+ @Anomaly.AnomalyType
+ private static final int ANOMALY_TYPE = Anomaly.AnomalyType.WAKE_LOCK;
+ private static final String PACKAGE_NAME = "com.android.app";
+ private static final int UID = 111;
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PreferenceFragment mFragment;
+ @Mock
+ private FragmentManager mFragmentManager;
+ @Mock
+ private FragmentTransaction mFragmentTransaction;
+ private AnomalyPreferenceController mAnomalyPreferenceController;
+ private Preference mPreference;
+ private Context mContext;
+ private List<Anomaly> mAnomalyList;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mPreference = new Preference(mContext);
+ mPreference.setKey(AnomalyPreferenceController.ANOMALY_KEY);
+ when(mFragment.getPreferenceManager().findPreference(any())).thenReturn(mPreference);
+ when(mFragment.getFragmentManager()).thenReturn(mFragmentManager);
+ when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction);
+
+ mAnomalyList = new ArrayList<>();
+ Anomaly anomaly = new Anomaly.Builder()
+ .setType(ANOMALY_TYPE)
+ .setUid(UID)
+ .setPackageName(PACKAGE_NAME)
+ .build();
+ mAnomalyList.add(anomaly);
+
+ mAnomalyPreferenceController = new AnomalyPreferenceController(mFragment);
+ }
+
+ @Test
+ public void testUpdateAnomalyPreference_hasCorrectData() {
+ mAnomalyPreferenceController.updateAnomalyPreference(mAnomalyList);
+
+ //add more test when this method is complete
+ assertThat(mAnomalyPreferenceController.mAnomalies).isEqualTo(mAnomalyList);
+ }
+
+ @Test
+ public void testOnPreferenceTreeClick_oneAnomaly_showDialog() {
+ mAnomalyPreferenceController.mAnomalies = mAnomalyList;
+
+ mAnomalyPreferenceController.onPreferenceTreeClick(mPreference);
+
+ verify(mFragmentManager).beginTransaction();
+ verify(mFragmentTransaction).add(any(), anyString());
+ verify(mFragmentTransaction).commit();
+ }
+
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyTest.java
new file mode 100644
index 0000000..b57c7eb
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.settings.fuelgauge.anomaly;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AnomalyTest {
+ private static int TYPE = Anomaly.AnomalyType.WAKE_LOCK;
+ private static int UID = 111;
+ private static long WAKE_LOCK_TIME_MS = 1500;
+ private static String PACKAGE_NAME = "com.android.settings";
+ private static String DISPLAY_NAME = "settings";
+
+ @Test
+ public void testBuilder_buildCorrectly() {
+ Anomaly anomaly = new Anomaly.Builder()
+ .setType(TYPE)
+ .setUid(UID)
+ .setWakeLockTimeMs(WAKE_LOCK_TIME_MS)
+ .setPackageName(PACKAGE_NAME)
+ .setDisplayName(DISPLAY_NAME)
+ .build();
+
+ assertThat(anomaly.type).isEqualTo(TYPE);
+ assertThat(anomaly.uid).isEqualTo(UID);
+ assertThat(anomaly.wakelockTimeMs).isEqualTo(WAKE_LOCK_TIME_MS);
+ assertThat(anomaly.packageName).isEqualTo(PACKAGE_NAME);
+ assertThat(anomaly.displayName).isEqualTo(DISPLAY_NAME);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java
new file mode 100644
index 0000000..8d6d31b
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/anomaly/AnomalyUtilsTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.settings.fuelgauge.anomaly;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.fuelgauge.anomaly.action.ForceStopAction;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class AnomalyUtilsTest {
+
+ @Test
+ public void testGetAnomalyAction_typeWakeLock_returnForceStop() {
+ assertThat(AnomalyUtils.getAnomalyAction(Anomaly.AnomalyType.WAKE_LOCK)).isInstanceOf(
+ ForceStopAction.class);
+ }
+}
diff --git a/tests/unit/src/com/android/settings/CameraLiftTriggerSuggestionActivityTest.java b/tests/unit/src/com/android/settings/CameraLiftTriggerSuggestionActivityTest.java
new file mode 100644
index 0000000..cbf1db8
--- /dev/null
+++ b/tests/unit/src/com/android/settings/CameraLiftTriggerSuggestionActivityTest.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.settings;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static android.support.test.espresso.matcher.ViewMatchers.hasSibling;
+import static org.hamcrest.Matchers.allOf;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CameraLiftTriggerSuggestionActivityTest {
+ private Instrumentation mInstrumentation;
+ private Context mTargetContext;
+
+ @Before
+ public void setUp() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mTargetContext = mInstrumentation.getTargetContext();
+ }
+
+ @Test
+ public void launchCameraLiftTriggerSuggestion_shouldNotCrash() {
+ final Intent cameraTriggerSuggestionIntent = new Intent(mTargetContext,
+ Settings.CameraLiftTriggerSuggestionActivity.class);
+ final boolean cameraLiftTriggerEnabled = mTargetContext.getResources()
+ .getBoolean(R.bool.config_cameraLiftTriggerAvailable);
+
+ if (!cameraLiftTriggerEnabled) {
+ return;
+ }
+
+ mInstrumentation.startActivitySync(cameraTriggerSuggestionIntent);
+
+ onView(allOf(withText(R.string.camera_lift_trigger_title),
+ hasSibling(withText(R.string.camera_lift_trigger_summary))))
+ .check(matches(isDisplayed()));
+ }
+}
diff --git a/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java b/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java
index 3137d59..69c8c54 100644
--- a/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java
+++ b/tests/unit/src/com/android/settings/display/ThemePreferenceControllerTest.java
@@ -16,11 +16,10 @@
package com.android.settings.display;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -29,6 +28,7 @@
import android.content.ContextWrapper;
import android.content.om.OverlayInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
@@ -58,7 +58,7 @@
public void setup() {
mMockOverlayManager = mock(OverlayManager.class);
mMockPackageManager = mock(PackageManager.class);
- mContext = new ContextWrapper(InstrumentationRegistry.getContext()) {
+ mContext = new ContextWrapper(InstrumentationRegistry.getTargetContext()) {
@Override
public PackageManager getPackageManager() {
return mMockPackageManager;
@@ -82,6 +82,8 @@
}
return info;
});
+ when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(
+ new PackageInfo());
when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt())).thenReturn(
list(info1, info2));
ListPreference pref = mock(ListPreference.class);
@@ -91,31 +93,71 @@
CharSequence[] entries = arg.getValue();
- assertEquals(2, entries.length);
- assertEquals("Theme1", entries[0]);
- assertEquals("Theme2", entries[1]);
+ assertThat(entries).asList().containsExactly("Theme1", "Theme2");
verify(pref).setEntryValues(arg.capture());
CharSequence[] entryValues = arg.getValue();
- assertEquals("com.android.Theme1", entryValues[0]);
- assertEquals("com.android.Theme2", entryValues[1]);
+ assertThat(entryValues).asList().containsExactly(
+ "com.android.Theme1", "com.android.Theme2");
verify(pref).setValue(eq("com.android.Theme1"));
}
@Test
+ public void testUpdateState_withStaticOverlay() throws Exception {
+ OverlayInfo info1 = new OverlayInfo("com.android.Theme1", "android",
+ "", OverlayInfo.STATE_ENABLED, 0);
+ OverlayInfo info2 = new OverlayInfo("com.android.Theme2", "android",
+ "", OverlayInfo.STATE_ENABLED, 0);
+ when(mMockPackageManager.getApplicationInfo(any(), anyInt())).thenAnswer(inv -> {
+ ApplicationInfo info = mock(ApplicationInfo.class);
+ if ("com.android.Theme1".equals(inv.getArguments()[0])) {
+ when(info.loadLabel(any())).thenReturn("Theme1");
+ } else {
+ when(info.loadLabel(any())).thenReturn("Theme2");
+ }
+ return info;
+ });
+ PackageInfo pi = new PackageInfo();
+ pi.isStaticOverlay = true;
+ when(mMockPackageManager.getPackageInfo(eq("com.android.Theme1"), anyInt())).thenReturn(pi);
+ when(mMockPackageManager.getPackageInfo(eq("com.android.Theme2"), anyInt())).thenReturn(
+ new PackageInfo());
+ when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt())).thenReturn(
+ list(info1, info2));
+ ListPreference pref = mock(ListPreference.class);
+ mPreferenceController.updateState(pref);
+ ArgumentCaptor<String[]> arg = ArgumentCaptor.forClass(String[].class);
+ verify(pref).setEntries(arg.capture());
+
+
+ CharSequence[] entries = arg.getValue();
+ assertThat(entries).asList().containsExactly("Theme2");
+
+ verify(pref).setEntryValues(arg.capture());
+ CharSequence[] entryValues = arg.getValue();
+ assertThat(entryValues).asList().containsExactly("com.android.Theme2");
+
+ verify(pref).setValue(eq("com.android.Theme2"));
+ }
+
+ @Test
public void testAvailable_false() throws Exception {
+ when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(
+ new PackageInfo());
when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt()))
.thenReturn(list(new OverlayInfo("", "", "", 0, 0)));
- assertFalse(mPreferenceController.isAvailable());
+ assertThat(mPreferenceController.isAvailable()).isFalse();
}
@Test
public void testAvailable_true() throws Exception {
+ when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(
+ new PackageInfo());
when(mMockOverlayManager.getOverlayInfosForTarget(any(), anyInt()))
.thenReturn(list(new OverlayInfo("", "", "", 0, 0),
new OverlayInfo("", "", "", 0, 0)));
- assertTrue(mPreferenceController.isAvailable());
+ assertThat(mPreferenceController.isAvailable()).isTrue();
}
private ArrayList<OverlayInfo> list(OverlayInfo... infos) {