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;
+    }
+}