Merge "Use Car Setup Wizard Support Library theme" into pi-car-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1dd8024..2be6686 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -127,6 +127,11 @@
android:windowSoftInputMode="adjustResize">
</activity>
+ <activity android:name=".system.ThirdPartyLicensesActivity"
+ android:configChanges="orientation|keyboardHidden|screenSize"
+ android:windowSoftInputMode="adjustResize">
+ </activity>
+
<service android:name=".bluetooth.BluetoothPairingService" />
<receiver android:name=".bluetooth.BluetoothPairingRequest">
@@ -135,5 +140,17 @@
</intent-filter>
</receiver>
+ <!-- FileProvider to share a generated license html file.
+ Note that "com.android.settings.files" is set here as its authorities because a Uri
+ permission grant should be allowed to share a file with an external browser but it is
+ allowed only for Settings' authorities in ActivityManagerService. -->
+ <provider android:name="androidx.core.content.FileProvider"
+ android:authorities="com.android.settings.files"
+ android:grantUriPermissions="true"
+ android:exported="false">
+ <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/file_paths" />
+ </provider>
+
</application>
</manifest>
diff --git a/res/layout/tile.xml b/res/layout/tile.xml
index b24c34f..a94f741 100644
--- a/res/layout/tile.xml
+++ b/res/layout/tile.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- ~ Copyright (C) 2017 The Android Open Source Project
+ ~ Copyright (C) 2018 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.
@@ -46,24 +46,9 @@
android:tint="@color/toggle_icon_tint"
style="@style/ListIcon" />
</FrameLayout>
- <LinearLayout
- android:id="@+id/text_container"
+ <TextView
+ android:id="@+id/tile_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
- android:orientation="horizontal">
- <TextView
- android:id="@+id/tile_text"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:gravity="center"
- android:textAppearance="@style/TextAppearance.Car.Label1"/>
- <ImageView
- android:id="@+id/deep_dive_icon"
- android:layout_width="@dimen/car_primary_icon_size"
- android:layout_height="@dimen/car_primary_icon_size"
- android:src="@drawable/ic_arrow_drop_down"
- android:tint="@color/car_label1"
- android:layout_marginStart="@dimen/car_padding_1"
- android:visibility="gone"/>
- </LinearLayout>
-</LinearLayout>
\ No newline at end of file
+ android:textAppearance="@style/TextAppearance.Car.Label1"/>
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 578c22d..e348a7d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -280,7 +280,7 @@
<string name="terms_title">Terms and conditions</string>
<!-- Note: this may be replaced by a more-specific title of the activity that will get launched --> <skip />
<!-- About phone settings screen, setting option name to see licensing info for WebView component. [CHAR LIMIT=35] -->
- <string name="webview_license_title">System WebView License</string>
+ <string name="webview_license_title">System WebView licenses</string>
<!-- About phone settings screen, setting option name to see wallpapers attributions -->
<string name="wallpaper_attributions">Wallpapers</string>
<!-- About phone settings screen, setting option name to see wallpapers attributions values -->
diff --git a/res/xml/file_paths.xml b/res/xml/file_paths.xml
new file mode 100644
index 0000000..d0738ee
--- /dev/null
+++ b/res/xml/file_paths.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- Offer access to files under Context.getCacheDir() -->
+ <cache-path name="my_cache" />
+</paths>
diff --git a/src/com/android/car/settings/common/ListItemSettingsFragment.java b/src/com/android/car/settings/common/ListItemSettingsFragment.java
index 01867af..3c77495 100644
--- a/src/com/android/car/settings/common/ListItemSettingsFragment.java
+++ b/src/com/android/car/settings/common/ListItemSettingsFragment.java
@@ -16,11 +16,15 @@
package com.android.car.settings.common;
+import android.content.Context;
import android.os.Bundle;
+import android.view.View;
+import androidx.annotation.StringRes;
import androidx.car.widget.ListItemAdapter;
import androidx.car.widget.ListItemProvider;
import androidx.car.widget.PagedListView;
+import androidx.car.widget.TextListItem;
import com.android.car.settings.R;
import com.android.car.settings.suggestions.SuggestionListItem;
@@ -73,4 +77,14 @@
* Gets ListItemProvider that should provide items to show up in the list.
*/
public abstract ListItemProvider getItemProvider();
+
+ protected TextListItem createSimpleListItem(@StringRes int titleResId,
+ View.OnClickListener onClickListener) {
+ Context context = requireContext();
+ TextListItem item = new TextListItem(context);
+ item.setTitle(context.getString(titleResId));
+ item.setSupplementalIcon(R.drawable.ic_chevron_right, /* showDivider= */ false);
+ item.setOnClickListener(onClickListener);
+ return item;
+ }
}
diff --git a/src/com/android/car/settings/quicksettings/BluetoothTile.java b/src/com/android/car/settings/quicksettings/BluetoothTile.java
index 80daecb..fe05b8e 100644
--- a/src/com/android/car/settings/quicksettings/BluetoothTile.java
+++ b/src/com/android/car/settings/quicksettings/BluetoothTile.java
@@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.car.settings.quicksettings;
+package com.android.car.settings.quicksettings;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
@@ -28,6 +28,8 @@
import androidx.annotation.Nullable;
import com.android.car.settings.R;
+import com.android.car.settings.bluetooth.BluetoothSettingsFragment;
+import com.android.car.settings.common.BaseFragment.FragmentController;
import com.android.car.settings.common.Logger;
import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -41,6 +43,7 @@
private final StateChangedListener mStateChangedListener;
private LocalBluetoothAdapter mLocalAdapter;
private LocalBluetoothManager mLocalManager;
+ private View.OnLongClickListener mLaunchBluetoothSettings;
@DrawableRes
private int mIconRes = R.drawable.ic_settings_bluetooth;
@@ -85,7 +88,10 @@
}
};
- BluetoothTile(Context context, StateChangedListener stateChangedListener) {
+ BluetoothTile(
+ Context context,
+ StateChangedListener stateChangedListener,
+ FragmentController fragmentController) {
mStateChangedListener = stateChangedListener;
mContext = context;
IntentFilter mBtStateChangeFilter = new IntentFilter();
@@ -107,11 +113,15 @@
mIconRes = R.drawable.ic_settings_bluetooth_disabled;
mState = State.OFF;
}
+ mLaunchBluetoothSettings = v -> {
+ fragmentController.launchFragment(BluetoothSettingsFragment.getInstance());
+ return true;
+ };
}
@Nullable
- public View.OnClickListener getDeepDiveListener() {
- return null;
+ public View.OnLongClickListener getOnLongClickListener() {
+ return mLaunchBluetoothSettings;
}
@Override
@@ -146,6 +156,6 @@
if (mLocalAdapter == null) {
return;
}
- mLocalAdapter.setBluetoothEnabled(mLocalAdapter.isEnabled() ? false : true);
+ mLocalAdapter.setBluetoothEnabled(!mLocalAdapter.isEnabled());
}
}
diff --git a/src/com/android/car/settings/quicksettings/CelluarTile.java b/src/com/android/car/settings/quicksettings/CelluarTile.java
index 2ed42f1..8471e1e 100644
--- a/src/com/android/car/settings/quicksettings/CelluarTile.java
+++ b/src/com/android/car/settings/quicksettings/CelluarTile.java
@@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.car.settings.quicksettings;
+package com.android.car.settings.quicksettings;
import android.content.Context;
import android.graphics.drawable.Drawable;
@@ -57,7 +57,7 @@
}
@Nullable
- public View.OnClickListener getDeepDiveListener() {
+ public View.OnLongClickListener getOnLongClickListener() {
return null;
}
diff --git a/src/com/android/car/settings/quicksettings/DayNightTile.java b/src/com/android/car/settings/quicksettings/DayNightTile.java
index f9c6797..2fad041 100644
--- a/src/com/android/car/settings/quicksettings/DayNightTile.java
+++ b/src/com/android/car/settings/quicksettings/DayNightTile.java
@@ -13,19 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.car.settings.quicksettings;
+package com.android.car.settings.quicksettings;
import android.app.UiModeManager;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
-import android.view.View.OnClickListener;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import com.android.car.settings.R;
+import com.android.car.settings.common.BaseFragment.FragmentController;
+import com.android.car.settings.display.DisplaySettingsFragment;
/**
* Toggles auto or night mode tile on quick setting page.
@@ -34,6 +35,7 @@
private final Context mContext;
private final StateChangedListener mStateChangedListener;
private final UiModeManager mUiModeManager;
+ private final View.OnLongClickListener mLaunchDisplaySettings;
@DrawableRes
private int mIconRes = R.drawable.ic_settings_night_display;
@@ -42,7 +44,10 @@
private State mState = State.ON;
- DayNightTile(Context context, StateChangedListener stateChangedListener) {
+ DayNightTile(
+ Context context,
+ StateChangedListener stateChangedListener,
+ FragmentController fragmentController) {
mStateChangedListener = stateChangedListener;
mContext = context;
mUiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
@@ -52,11 +57,15 @@
mState = State.OFF;
}
mText = mContext.getString(R.string.night_mode_tile_label);
+ mLaunchDisplaySettings = v -> {
+ fragmentController.launchFragment(DisplaySettingsFragment.newInstance());
+ return true;
+ };
}
@Nullable
- public OnClickListener getDeepDiveListener() {
- return null;
+ public View.OnLongClickListener getOnLongClickListener() {
+ return mLaunchDisplaySettings;
}
@Override
diff --git a/src/com/android/car/settings/quicksettings/QuickSettingFragment.java b/src/com/android/car/settings/quicksettings/QuickSettingFragment.java
index aa5baef..37afd47 100644
--- a/src/com/android/car/settings/quicksettings/QuickSettingFragment.java
+++ b/src/com/android/car/settings/quicksettings/QuickSettingFragment.java
@@ -13,10 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.car.settings.quicksettings;
+import android.app.Activity;
import android.car.drivingstate.CarUxRestrictions;
import android.car.user.CarUserManagerHelper;
+import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.view.View;
@@ -24,6 +27,7 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.car.widget.PagedListView;
import com.android.car.settings.R;
@@ -37,7 +41,7 @@
* Shows a page to access frequently used settings.
*/
public class QuickSettingFragment extends BaseFragment {
- private CarUserManagerHelper mCarUserManagerHelper;
+ private CarUserManagerHelper mCarUserManagerHelper;
private UserIconProvider mUserIconProvider;
private QuickSettingGridAdapter mGridAdapter;
private PagedListView mListView;
@@ -52,7 +56,7 @@
*/
public static QuickSettingFragment newInstance() {
QuickSettingFragment quickSettingFragment = new QuickSettingFragment();
- Bundle bundle = quickSettingFragment.getBundle();
+ Bundle bundle = QuickSettingFragment.getBundle();
bundle.putInt(EXTRA_ACTION_BAR_LAYOUT, R.layout.action_bar_quick_settings);
bundle.putInt(EXTRA_LAYOUT, R.layout.quick_settings);
bundle.putInt(EXTRA_TITLE_ID, R.string.settings_label);
@@ -64,34 +68,35 @@
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mHomeFragmentLauncher = new HomeFragmentLauncher();
- getActivity().findViewById(R.id.action_bar_icon_container).setOnClickListener(
- v -> getActivity().finish());
+ Activity activity = requireActivity();
+ activity.findViewById(R.id.action_bar_icon_container).setOnClickListener(
+ v -> activity.finish());
- mOpacityDisabled = getContext().getResources().getFloat(R.dimen.opacity_disabled);
- mOpacityEnabled = getContext().getResources().getFloat(R.dimen.opacity_enabled);
- mCarUserManagerHelper = new CarUserManagerHelper(getContext());
+ mOpacityDisabled = activity.getResources().getFloat(R.dimen.opacity_disabled);
+ mOpacityEnabled = activity.getResources().getFloat(R.dimen.opacity_enabled);
+ mCarUserManagerHelper = new CarUserManagerHelper(activity);
mUserIconProvider = new UserIconProvider(mCarUserManagerHelper);
- mListView = (PagedListView) getActivity().findViewById(R.id.list);
- mGridAdapter = new QuickSettingGridAdapter(getContext());
+ mListView = activity.findViewById(R.id.list);
+ mGridAdapter = new QuickSettingGridAdapter(activity);
mListView.getRecyclerView().setLayoutManager(mGridAdapter.getGridLayoutManager());
- mFullSettingBtn = getActivity().findViewById(R.id.full_setting_btn);
+ mFullSettingBtn = activity.findViewById(R.id.full_setting_btn);
mFullSettingBtn.setOnClickListener(mHomeFragmentLauncher);
- mUserSwitcherBtn = getActivity().findViewById(R.id.user_switcher_btn);
+ mUserSwitcherBtn = activity.findViewById(R.id.user_switcher_btn);
mUserSwitcherBtn.setOnClickListener(v -> {
getFragmentController().launchFragment(UserSwitcherFragment.newInstance());
});
- setupUserButton();
+ setupUserButton(activity);
- View exitBtn = getActivity().findViewById(R.id.exit_button);
+ View exitBtn = activity.findViewById(R.id.exit_button);
exitBtn.setOnClickListener(v -> getFragmentController().goBack());
mGridAdapter
- .addTile(new WifiTile(getContext(), mGridAdapter, getFragmentController()))
- .addTile(new BluetoothTile(getContext(), mGridAdapter))
- .addTile(new DayNightTile(getContext(), mGridAdapter))
- .addTile(new CelluarTile(getContext(), mGridAdapter))
- .addSeekbarTile(new BrightnessTile(getContext()));
+ .addTile(new WifiTile(activity, mGridAdapter, getFragmentController()))
+ .addTile(new BluetoothTile(activity, mGridAdapter, getFragmentController()))
+ .addTile(new DayNightTile(activity, mGridAdapter, getFragmentController()))
+ .addTile(new CelluarTile(activity, mGridAdapter))
+ .addSeekbarTile(new BrightnessTile(activity));
mListView.setAdapter(mGridAdapter);
}
@@ -101,13 +106,13 @@
mGridAdapter.stop();
}
- private void setupUserButton() {
- ImageView userIcon = (ImageView) getActivity().findViewById(R.id.user_icon);
+ private void setupUserButton(Context context) {
+ ImageView userIcon = requireActivity().findViewById(R.id.user_icon);
UserInfo currentUserInfo = mCarUserManagerHelper.getCurrentForegroundUserInfo();
- userIcon.setImageDrawable(mUserIconProvider.getUserIcon(currentUserInfo, getContext()));
+ userIcon.setImageDrawable(mUserIconProvider.getUserIcon(currentUserInfo, context));
userIcon.clearColorFilter();
- TextView userSwitcherText = (TextView) getActivity().findViewById(R.id.user_switcher_text);
+ TextView userSwitcherText = requireActivity().findViewById(R.id.user_switcher_text);
userSwitcherText.setText(currentUserInfo.name);
}
@@ -115,12 +120,12 @@
* Quick setting fragment is distraction optimized, so is allowed at all times.
*/
@Override
- public boolean canBeShown(CarUxRestrictions carUxRestrictions) {
+ public boolean canBeShown(@NonNull CarUxRestrictions carUxRestrictions) {
return true;
}
@Override
- public void onUxRestrictionChanged(CarUxRestrictions carUxRestrictions) {
+ public void onUxRestrictionChanged(@NonNull CarUxRestrictions carUxRestrictions) {
// TODO: update tiles
applyRestriction(CarUxRestrictionsHelper.isNoSetup(carUxRestrictions));
}
diff --git a/src/com/android/car/settings/quicksettings/QuickSettingGridAdapter.java b/src/com/android/car/settings/quicksettings/QuickSettingGridAdapter.java
index e7fb053..89b0232 100644
--- a/src/com/android/car/settings/quicksettings/QuickSettingGridAdapter.java
+++ b/src/com/android/car/settings/quicksettings/QuickSettingGridAdapter.java
@@ -21,7 +21,6 @@
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SeekBar;
@@ -72,7 +71,9 @@
* A state to indicate how we want to render icon, this is independent of what to show
* in text.
*/
- enum State {OFF, ON}
+ enum State {
+ OFF, ON
+ }
/**
* Called when activity owning this tile's onStop() gets called.
@@ -92,11 +93,11 @@
boolean isAvailable();
/**
- * Returns a listener for launching a setting fragment for advanced configuration for this
- * tile, A.K.A deep dive, if available. {@code null} if deep dive is not available.
+ * Returns a listener to call when this tile is clicked and held. Returns {@code null} if
+ * no action should be performed.
*/
@Nullable
- OnClickListener getDeepDiveListener();
+ View.OnLongClickListener getOnLongClickListener();
}
interface SeekbarTile extends SeekBar.OnSeekBarChangeListener {
@@ -160,15 +161,12 @@
case TILE_VIEWTYPE:
Tile tile = mTiles.get(position - mSeekbarTiles.size());
TileViewHolder vh = (TileViewHolder) holder;
- OnClickListener deepDiveListener = tile.getDeepDiveListener();
vh.itemView.setOnClickListener(tile);
- if (deepDiveListener != null) {
- vh.mDeepDiveIcon.setVisibility(View.VISIBLE);
- vh.mTextContainer.setOnClickListener(deepDiveListener);
+ View.OnLongClickListener onLongClickListener = tile.getOnLongClickListener();
+ if (onLongClickListener != null) {
+ vh.itemView.setOnLongClickListener(onLongClickListener);
} else {
- vh.mDeepDiveIcon.setVisibility(View.GONE);
- vh.mTextContainer.setClickable(false);
- vh.mTextContainer.setOnClickListener(null);
+ vh.itemView.setOnLongClickListener(null);
}
vh.mIcon.setImageDrawable(tile.getIcon());
switch (tile.getState()) {
@@ -201,21 +199,15 @@
}
private class TileViewHolder extends RecyclerView.ViewHolder {
- private final View mIconContainer;
- private final View mTextContainer;
private final View mIconBackground;
private final ImageView mIcon;
- private final ImageView mDeepDiveIcon;
private final TextView mText;
TileViewHolder(View view) {
super(view);
- mIconContainer = view.findViewById(R.id.icon_container);
- mTextContainer = view.findViewById(R.id.text_container);
mIconBackground = view.findViewById(R.id.icon_background);
- mIcon = (ImageView) view.findViewById(R.id.tile_icon);
- mDeepDiveIcon = (ImageView) view.findViewById(R.id.deep_dive_icon);
- mText = (TextView) view.findViewById(R.id.tile_text);
+ mIcon = view.findViewById(R.id.tile_icon);
+ mText = view.findViewById(R.id.tile_text);
}
}
diff --git a/src/com/android/car/settings/quicksettings/WifiTile.java b/src/com/android/car/settings/quicksettings/WifiTile.java
index 526b39b..b2f2947 100644
--- a/src/com/android/car/settings/quicksettings/WifiTile.java
+++ b/src/com/android/car/settings/quicksettings/WifiTile.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -13,13 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License
*/
-package com.android.car.settings.quicksettings;
+package com.android.car.settings.quicksettings;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
-import android.view.View.OnClickListener;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
@@ -38,9 +37,8 @@
private final StateChangedListener mStateChangedListener;
private final CarWifiManager mCarWifiManager;
private final Context mContext;
- private final FragmentController mFragmentController;
- private final OnClickListener mSwitchSavedWifiListener;
+ private final View.OnLongClickListener mLaunchWifiSettings;
@DrawableRes
private int mIconRes = R.drawable.ic_settings_wifi;
@@ -49,12 +47,14 @@
private State mState = State.OFF;
- WifiTile(Context context, StateChangedListener stateChangedListener,
+ WifiTile(
+ Context context,
+ StateChangedListener stateChangedListener,
FragmentController fragmentController) {
mContext = context;
- mFragmentController = fragmentController;
- mSwitchSavedWifiListener = v -> {
- mFragmentController.launchFragment(WifiSettingsFragment.newInstance());
+ mLaunchWifiSettings = v -> {
+ fragmentController.launchFragment(WifiSettingsFragment.newInstance());
+ return true;
};
mCarWifiManager = new CarWifiManager(context, /* listener= */ this);
mCarWifiManager.start();
@@ -65,9 +65,8 @@
}
@Nullable
- public OnClickListener getDeepDiveListener() {
- return !mCarWifiManager.getSavedAccessPoints().isEmpty()
- ? mSwitchSavedWifiListener : null;
+ public View.OnLongClickListener getOnLongClickListener() {
+ return mLaunchWifiSettings;
}
@Override
@@ -130,6 +129,7 @@
/**
* Updates the text with access point connected, if any
+ *
* @return {@code true} if the text is updated, {@code false} other wise.
*/
private boolean updateAccessPointSsid() {
diff --git a/src/com/android/car/settings/security/ChooseLockPatternFragment.java b/src/com/android/car/settings/security/ChooseLockPatternFragment.java
index 1fa6ed4..dbf151d 100644
--- a/src/com/android/car/settings/security/ChooseLockPatternFragment.java
+++ b/src/com/android/car/settings/security/ChooseLockPatternFragment.java
@@ -48,6 +48,8 @@
private static final Logger LOG = new Logger(ChooseLockPatternFragment.class);
private static final String LOCK_OPTIONS_DIALOG_TAG = "lock_options_dialog_tag";
private static final String FRAGMENT_TAG_SAVE_PATTERN_WORKER = "save_pattern_worker";
+ private static final String STATE_UI_STAGE = "state_ui_stage";
+ private static final String STATE_CHOSEN_PATTERN = "state_chosen_pattern";
private static final int ID_EMPTY_MESSAGE = -1;
// How long we wait to clear a wrong pattern
@@ -62,6 +64,7 @@
private Button mPrimaryButton;
private ProgressBar mProgressBar;
private List<LockPatternView.Cell> mChosenPattern;
+ // Existing pattern that user previously set
private String mCurrentPattern;
private SavePatternWorker mSavePatternWorker;
@@ -75,7 +78,8 @@
*/
Introduction(
R.string.lockpattern_recording_intro_header,
- SecondaryButtonState.Cancel, PrimaryButtonState.ContinueDisabled,
+ SecondaryButtonState.Cancel,
+ PrimaryButtonState.ContinueDisabled,
/* patternEnabled= */ true),
/**
* Help screen to show how a valid pattern looks like.
@@ -83,7 +87,8 @@
*/
HelpScreen(
R.string.lockpattern_settings_help_how_to_record,
- SecondaryButtonState.Gone, PrimaryButtonState.Ok,
+ SecondaryButtonState.Gone,
+ PrimaryButtonState.Ok,
/* patternEnabled= */ false),
/**
* Invalid pattern is entered, hint message show required number of dots.
@@ -91,7 +96,8 @@
*/
ChoiceTooShort(
R.string.lockpattern_recording_incorrect_too_short,
- SecondaryButtonState.Retry, PrimaryButtonState.ContinueDisabled,
+ SecondaryButtonState.Retry,
+ PrimaryButtonState.ContinueDisabled,
/* patternEnabled= */ true),
/**
* First drawing on the pattern is valid, primary button shows Continue,
@@ -99,7 +105,8 @@
*/
FirstChoiceValid(
R.string.lockpattern_recording_intro_header,
- SecondaryButtonState.Retry, PrimaryButtonState.Continue,
+ SecondaryButtonState.Retry,
+ PrimaryButtonState.Continue,
/* patternEnabled= */ false),
/**
* Need to draw pattern again to confirm.
@@ -107,7 +114,8 @@
*/
NeedToConfirm(
R.string.lockpattern_need_to_confirm,
- SecondaryButtonState.Cancel, PrimaryButtonState.ConfirmDisabled,
+ SecondaryButtonState.Cancel,
+ PrimaryButtonState.ConfirmDisabled,
/* patternEnabled= */ true),
/**
* Confirmation of previous drawn pattern failed, didn't enter the same pattern.
@@ -115,7 +123,8 @@
*/
ConfirmWrong(
R.string.lockpattern_pattern_wrong,
- SecondaryButtonState.Cancel, PrimaryButtonState.ConfirmDisabled,
+ SecondaryButtonState.Cancel,
+ PrimaryButtonState.ConfirmDisabled,
/* patternEnabled= */ true),
/**
* Pattern is confirmed after drawing the same pattern twice.
@@ -123,7 +132,8 @@
*/
ChoiceConfirmed(
R.string.lockpattern_pattern_confirmed,
- SecondaryButtonState.Cancel, PrimaryButtonState.Confirm,
+ SecondaryButtonState.Cancel,
+ PrimaryButtonState.Confirm,
/* patternEnabled= */ false),
/**
@@ -132,7 +142,8 @@
*/
SaveFailure(
R.string.error_saving_lockpattern,
- SecondaryButtonState.Cancel, PrimaryButtonState.Retry,
+ SecondaryButtonState.Cancel,
+ PrimaryButtonState.Retry,
/* patternEnabled= */ false);
final int mMessageId;
@@ -141,12 +152,12 @@
final boolean mPatternEnabled;
/**
- * @param message The message displayed as instruction.
+ * @param messageId The message displayed as instruction.
* @param secondaryButtonState The state of the secondary button.
* @param primaryButtonState The state of the primary button.
* @param patternEnabled Whether the pattern widget is mEnabled.
*/
- Stage(int messageId,
+ Stage(@StringRes int messageId,
SecondaryButtonState secondaryButtonState,
PrimaryButtonState primaryButtonState,
boolean patternEnabled) {
@@ -172,7 +183,7 @@
* @param text The displayed mText for this mode.
* @param enabled Whether the button should be mEnabled.
*/
- PrimaryButtonState(int text, boolean enabled) {
+ PrimaryButtonState(@StringRes int text, boolean enabled) {
this.mText = text;
this.mEnabled = enabled;
}
@@ -192,10 +203,10 @@
Gone(ID_EMPTY_MESSAGE, false);
/**
- * @param text The displayed mText for this mode.
+ * @param textId The displayed mText for this mode.
* @param enabled Whether the button should be mEnabled.
*/
- SecondaryButtonState(int textId, boolean enabled) {
+ SecondaryButtonState(@StringRes int textId, boolean enabled) {
this.mTextResId = textId;
this.mEnabled = enabled;
}
@@ -229,6 +240,12 @@
mIsInSetupWizard = args.getBoolean(BaseFragment.EXTRA_RUNNING_IN_SETUP_WIZARD);
mCurrentPattern = args.getString(PasswordHelper.EXTRA_CURRENT_SCREEN_LOCK);
}
+
+ if (savedInstanceState != null) {
+ mUiStage = Stage.values()[savedInstanceState.getInt(STATE_UI_STAGE)];
+ mChosenPattern = LockPatternUtils.stringToPattern(
+ savedInstanceState.getString(STATE_CHOSEN_PATTERN));
+ }
}
@Override
@@ -290,6 +307,13 @@
}
@Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(STATE_UI_STAGE, mUiStage.ordinal());
+ outState.putString(STATE_CHOSEN_PATTERN, LockPatternUtils.patternToString(mChosenPattern));
+ }
+
+ @Override
public void onStop() {
super.onStop();
if (mSavePatternWorker != null) {
diff --git a/src/com/android/car/settings/security/ChooseLockPinPasswordFragment.java b/src/com/android/car/settings/security/ChooseLockPinPasswordFragment.java
index 295c2b0..244128c 100644
--- a/src/com/android/car/settings/security/ChooseLockPinPasswordFragment.java
+++ b/src/com/android/car/settings/security/ChooseLockPinPasswordFragment.java
@@ -52,6 +52,8 @@
private static final String LOCK_OPTIONS_DIALOG_TAG = "lock_options_dialog_tag";
private static final String FRAGMENT_TAG_SAVE_PASSWORD_WORKER = "save_password_worker";
+ private static final String STATE_UI_STAGE = "state_ui_stage";
+ private static final String STATE_FIRST_ENTRY = "state_first_entry";
private static final Logger LOG = new Logger(ChooseLockPinPasswordFragment.class);
private static final String EXTRA_IS_PIN = "extra_is_pin";
@@ -197,6 +199,11 @@
mIsAlphaMode = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == passwordQuality
|| DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == passwordQuality
|| DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == passwordQuality;
+
+ if (savedInstanceState != null) {
+ mUiStage = Stage.values()[savedInstanceState.getInt(STATE_UI_STAGE)];
+ mFirstEntry = savedInstanceState.getString(STATE_FIRST_ENTRY);
+ }
}
@Override
@@ -296,6 +303,13 @@
}
@Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(STATE_UI_STAGE, mUiStage.ordinal());
+ outState.putString(STATE_FIRST_ENTRY, mFirstEntry);
+ }
+
+ @Override
public void onStop() {
super.onStop();
if (mSavePasswordWorker != null) {
diff --git a/src/com/android/car/settings/system/LegalInformationFragment.java b/src/com/android/car/settings/system/LegalInformationFragment.java
new file mode 100644
index 0000000..f6150c7
--- /dev/null
+++ b/src/com/android/car/settings/system/LegalInformationFragment.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.car.widget.ListItem;
+import androidx.car.widget.ListItemProvider;
+import androidx.car.widget.TextListItem;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.ListItemSettingsFragment;
+
+import java.util.ArrayList;
+
+/**
+ * Fragment showing legal information.
+ */
+public class LegalInformationFragment extends ListItemSettingsFragment {
+ private static final String ACTION_WEBVIEW_LICENSE = "android.settings.WEBVIEW_LICENSE";
+
+ /**
+ * Factory method for creating the fragment.
+ */
+ public static LegalInformationFragment newInstance() {
+ LegalInformationFragment fragment = new LegalInformationFragment();
+ Bundle bundle = ListItemSettingsFragment.getBundle();
+ bundle.putInt(EXTRA_TITLE_ID, R.string.legal_information);
+ fragment.setArguments(bundle);
+ return fragment;
+ }
+
+ @Override
+ public ListItemProvider getItemProvider() {
+ return new ListItemProvider.ListProvider(getListItems());
+ }
+
+ private ArrayList<ListItem> getListItems() {
+ ArrayList<ListItem> listItems = new ArrayList<>();
+
+ listItems.add(createSystemWebviewLicensesListItem());
+ listItems.add(createThirdPartyLicensesListItem());
+
+ return listItems;
+ }
+
+ private TextListItem createSystemWebviewLicensesListItem() {
+ Context context = requireContext();
+ return createSimpleListItem(R.string.webview_license_title, v -> {
+ Intent intent = new Intent();
+ intent.setAction(ACTION_WEBVIEW_LICENSE);
+ context.startActivity(intent);
+ });
+ }
+
+ private TextListItem createThirdPartyLicensesListItem() {
+ Context context = requireContext();
+ return createSimpleListItem(R.string.settings_license_activity_title, v -> {
+ Intent intent = new Intent(context, ThirdPartyLicensesActivity.class);
+ context.startActivity(intent);
+ });
+ }
+}
diff --git a/src/com/android/car/settings/system/LicenseHtmlGeneratorFromXml.java b/src/com/android/car/settings/system/LicenseHtmlGeneratorFromXml.java
new file mode 100644
index 0000000..8002ac1
--- /dev/null
+++ b/src/com/android/car/settings/system/LicenseHtmlGeneratorFromXml.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.text.TextUtils;
+import android.util.Xml;
+
+import com.android.car.settings.common.Logger;
+
+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.
+ */
+class LicenseHtmlGeneratorFromXml {
+ private static final Logger LOG = new Logger(LicenseHtmlGeneratorFromXml.class);
+
+ 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("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("Failed to parse " + xmlFile, e);
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException ie) {
+ LOG.w("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>
+ */
+ private 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();
+ }
+
+ private 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/car/settings/system/LicenseHtmlLoader.java b/src/com/android/car/settings/system/LicenseHtmlLoader.java
new file mode 100644
index 0000000..8bdf9f7
--- /dev/null
+++ b/src/com/android/car/settings/system/LicenseHtmlLoader.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.content.Context;
+
+import com.android.car.settings.common.Logger;
+import com.android.car.settingslib.loader.AsyncLoader;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * LicenseHtmlLoader is a loader which loads a license html file from default license xml files.
+ */
+public class LicenseHtmlLoader extends AsyncLoader<File> {
+ private static final Logger LOG = new Logger(LicenseHtmlLoader.class);
+
+ 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 final Context mContext;
+
+ public LicenseHtmlLoader(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public File loadInBackground() {
+ return generateHtmlFromDefaultXmlFiles();
+ }
+
+ private File generateHtmlFromDefaultXmlFiles() {
+ final List<File> xmlFiles = getVaildXmlFiles();
+ if (xmlFiles.isEmpty()) {
+ LOG.e("No notice file exists.");
+ return null;
+ }
+
+ File cachedHtmlFile = getCachedHtmlFile();
+ if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile)
+ || generateHtmlFile(xmlFiles, cachedHtmlFile)) {
+ return cachedHtmlFile;
+ }
+
+ return null;
+ }
+
+ private 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;
+ }
+
+ private File getCachedHtmlFile() {
+ return new File(mContext.getCacheDir(), NOTICE_HTML_FILE_NAME);
+ }
+
+ private 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;
+ }
+
+ private boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) {
+ return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile);
+ }
+}
+
+
diff --git a/src/com/android/car/settings/system/SystemSettingsFragment.java b/src/com/android/car/settings/system/SystemSettingsFragment.java
index 161e907..a378c9d 100644
--- a/src/com/android/car/settings/system/SystemSettingsFragment.java
+++ b/src/com/android/car/settings/system/SystemSettingsFragment.java
@@ -46,9 +46,6 @@
private static final String ACTION_SYSTEM_UPDATE_SETTINGS =
"android.settings.SYSTEM_UPDATE_SETTINGS";
- private static final String ACTION_SETTING_VIEW_LICENSE =
- "android.settings.WEBVIEW_LICENSE";
-
private ListItemProvider mItemProvider;
public static SystemSettingsFragment getInstance() {
@@ -135,11 +132,9 @@
legalInfoItem.setPrimaryActionIcon(
R.drawable.ic_settings_about, /* useLargeIcon= */ false);
legalInfoItem.setSupplementalIcon(R.drawable.ic_chevron_right, /* showDivider= */ false);
- legalInfoItem.setOnClickListener(v -> {
- Intent intent = new Intent();
- intent.setAction(ACTION_SETTING_VIEW_LICENSE);
- context.startActivity(intent);
- });
+ legalInfoItem.setOnClickListener(v ->
+ getFragmentController().launchFragment(LegalInformationFragment.newInstance())
+ );
return legalInfoItem;
}
diff --git a/src/com/android/car/settings/system/ThirdPartyLicensesActivity.java b/src/com/android/car/settings/system/ThirdPartyLicensesActivity.java
new file mode 100644
index 0000000..571e7b7
--- /dev/null
+++ b/src/com/android/car/settings/system/ThirdPartyLicensesActivity.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018 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.car.settings.system;
+
+import android.content.ActivityNotFoundException;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import androidx.core.content.FileProvider;
+import androidx.fragment.app.FragmentActivity;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+
+import java.io.File;
+
+/**
+ * The activity that displays third-party licenses.
+ */
+public class ThirdPartyLicensesActivity extends FragmentActivity implements
+ LoaderManager.LoaderCallbacks<File> {
+ private static final Logger LOG = new Logger(ThirdPartyLicensesActivity.class);
+ private static final int LOADER_ID_LICENSE_HTML_LOADER = 0;
+ 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 String FILE_PROVIDER_AUTHORITY = "com.android.settings.files";
+ private static final String HTML_VIEWER_PACKAGE = "com.android.htmlviewer";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ 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 showSelectedFile(final String path) {
+ if (TextUtils.isEmpty(path)) {
+ LOG.e("The system property for the license file is empty");
+ showErrorAndFinish();
+ return;
+ }
+
+ final File file = new File(path);
+ if (!isFileValid(file)) {
+ LOG.e("License file " + path + " does not exist");
+ showErrorAndFinish();
+ return;
+ }
+ showHtmlFromUri(Uri.fromFile(file));
+ }
+
+ private void showErrorAndFinish() {
+ Toast.makeText(this, R.string.settings_license_activity_unavailable, Toast.LENGTH_LONG)
+ .show();
+ finish();
+ }
+
+ 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, "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(HTML_VIEWER_PACKAGE);
+
+ try {
+ startActivity(intent);
+ finish();
+ } catch (ActivityNotFoundException e) {
+ LOG.e("Failed to find viewer", e);
+ showErrorAndFinish();
+ }
+ }
+
+ private void showHtmlFromDefaultXmlFiles() {
+ LoaderManager.getInstance(this).initLoader(LOADER_ID_LICENSE_HTML_LOADER, Bundle.EMPTY,
+ this);
+ }
+
+ private void showGeneratedHtmlFile(File generatedHtmlFile) {
+ if (generatedHtmlFile != null) {
+ LOG.i("File size: " + generatedHtmlFile.length());
+ showHtmlFromUri(getUriFromGeneratedHtmlFile(generatedHtmlFile));
+ } else {
+ LOG.e("Failed to generate.");
+ showErrorAndFinish();
+ }
+ }
+
+ private Uri getUriFromGeneratedHtmlFile(File generatedHtmlFile) {
+ return FileProvider.getUriForFile(this, FILE_PROVIDER_AUTHORITY, generatedHtmlFile);
+ }
+
+ private boolean isFilePathValid(final String path) {
+ return !TextUtils.isEmpty(path) && isFileValid(new File(path));
+ }
+
+ private boolean isFileValid(final File file) {
+ return file.exists() && file.length() != 0;
+ }
+}