Merge "Update summary correctly in Settings dashboard." into nyc-mr1-dev
diff --git a/res/layout/fallback_home_finishing_boot.xml b/res/layout/fallback_home_finishing_boot.xml
index 4cc526f..2714409 100644
--- a/res/layout/fallback_home_finishing_boot.xml
+++ b/res/layout/fallback_home_finishing_boot.xml
@@ -26,14 +26,16 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:orientation="vertical"
-        android:layout_gravity="center">
+        android:layout_gravity="center"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="16dp">
 
         <TextView
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:textSize="20sp"
             android:textColor="?android:attr/textColorPrimary"
-            android:text="@*android:string/android_upgrading_complete"/>
+            android:text="@*android:string/android_start_title"/>
 
         <ProgressBar
             android:theme="@style/FallbackHomeProgressBar"
diff --git a/res/layout/support_offline_escalation_options.xml b/res/layout/support_offline_escalation_options.xml
index 4f6940c..6a54fba 100644
--- a/res/layout/support_offline_escalation_options.xml
+++ b/res/layout/support_offline_escalation_options.xml
@@ -64,5 +64,6 @@
         style="@style/SupportSecondaryButton"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:minHeight="48dp"/>
+        android:minHeight="48dp"
+        android:visibility="gone"/>
 </LinearLayout>
diff --git a/src/com/android/settings/ChooseLockPassword.java b/src/com/android/settings/ChooseLockPassword.java
index 5519e87..dcb4550 100644
--- a/src/com/android/settings/ChooseLockPassword.java
+++ b/src/com/android/settings/ChooseLockPassword.java
@@ -22,8 +22,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.InsetDrawable;
-import android.inputmethodservice.KeyboardView;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.text.Editable;
@@ -39,7 +40,6 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.LinearLayout;
@@ -49,8 +49,6 @@
 import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
-import com.android.internal.widget.PasswordEntryKeyboardHelper;
-import com.android.internal.widget.PasswordEntryKeyboardView;
 import com.android.internal.widget.TextViewInputDisabler;
 import com.android.settings.notification.RedactionInterstitial;
 import com.android.settings.password.PasswordRequirementAdapter;
@@ -179,6 +177,7 @@
         private int mPasswordMinSymbols = 0;
         private int mPasswordMinNumeric = 0;
         private int mPasswordMinNonLetter = 0;
+        private int mPasswordMinLengthToFulfillAllPolicies = 0;
         private int mUserId;
         private boolean mHideDrawer = false;
         /**
@@ -200,6 +199,8 @@
         private Button mCancelButton;
         private Button mNextButton;
 
+        private TextChangedHandler mTextChangedHandler;
+
         private static final int CONFIRM_EXISTING_REQUEST = 58;
         static final int RESULT_FINISHED = RESULT_FIRST_USER;
 
@@ -284,6 +285,7 @@
                 w.start(mChooseLockSettingsHelper.utils(), required,
                         false, 0, current, current, mRequestedQuality, mUserId);
             }
+            mTextChangedHandler = new TextChangedHandler();
         }
 
         @Override
@@ -397,10 +399,12 @@
                         mPasswordMinLowerCase));
             }
             if (mPasswordMinLetters > 0) {
-                passwordRequirements.add(MIN_LETTER_IN_PASSWORD);
-                requirementDescriptions.add(getResources().getQuantityString(
-                        R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
-                        mPasswordMinLetters));
+                if (mPasswordMinLetters > mPasswordMinUpperCase + mPasswordMinLowerCase) {
+                    passwordRequirements.add(MIN_LETTER_IN_PASSWORD);
+                    requirementDescriptions.add(getResources().getQuantityString(
+                            R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
+                            mPasswordMinLetters));
+                }
             }
             if (mPasswordMinNumeric > 0) {
                 passwordRequirements.add(MIN_NUMBER_IN_PASSWORD);
@@ -415,10 +419,13 @@
                         mPasswordMinSymbols));
             }
             if (mPasswordMinNonLetter > 0) {
-                passwordRequirements.add(MIN_NON_LETTER_IN_PASSWORD);
-                requirementDescriptions.add(getResources().getQuantityString(
-                        R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
-                        mPasswordMinNonLetter));
+                if (mPasswordMinNonLetter > mPasswordMinNumeric + mPasswordMinSymbols) {
+                    passwordRequirements.add(MIN_NON_LETTER_IN_PASSWORD);
+                    requirementDescriptions.add(getResources().getQuantityString(
+                            R.plurals.lockpassword_password_requires_nonletter, mPasswordMinNonLetter,
+
+                            mPasswordMinNonLetter));
+                }
             }
             // Convert list to array.
             mPasswordRequirements = passwordRequirements.stream().mapToInt(i -> i).toArray();
@@ -553,6 +560,7 @@
                     mPasswordMinSymbols = 0;
                     mPasswordMinNonLetter = 0;
             }
+            mPasswordMinLengthToFulfillAllPolicies = getMinLengthToFulfillAllPolicies();
         }
 
         /**
@@ -565,7 +573,9 @@
             int errorCode = NO_ERROR;
 
             if (password.length() < mPasswordMinLength) {
-                errorCode |= TOO_SHORT;
+                if (mPasswordMinLength > mPasswordMinLengthToFulfillAllPolicies) {
+                    errorCode |= TOO_SHORT;
+                }
             } else if (password.length() > mPasswordMaxLength) {
                 errorCode |= TOO_LONG;
             } else {
@@ -728,11 +738,6 @@
             if ((errorCode & CONTAIN_NON_DIGITS) > 0) {
                 messages.add(getString(R.string.lockpassword_pin_contains_non_digits));
             }
-            if ((errorCode & NOT_ENOUGH_LETTER) > 0) {
-                messages.add(getResources().getQuantityString(
-                        R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
-                        mPasswordMinLetters));
-            }
             if ((errorCode & NOT_ENOUGH_UPPER_CASE) > 0) {
                 messages.add(getResources().getQuantityString(
                         R.plurals.lockpassword_password_requires_uppercase, mPasswordMinUpperCase,
@@ -743,6 +748,11 @@
                         R.plurals.lockpassword_password_requires_lowercase, mPasswordMinLowerCase,
                         mPasswordMinLowerCase));
             }
+            if ((errorCode & NOT_ENOUGH_LETTER) > 0) {
+                messages.add(getResources().getQuantityString(
+                        R.plurals.lockpassword_password_requires_letters, mPasswordMinLetters,
+                        mPasswordMinLetters));
+            }
             if ((errorCode & NOT_ENOUGH_DIGITS) > 0) {
                 messages.add(getResources().getQuantityString(
                         R.plurals.lockpassword_password_requires_numeric, mPasswordMinNumeric,
@@ -778,6 +788,14 @@
             return messages.toArray(new String[0]);
         }
 
+        private int getMinLengthToFulfillAllPolicies() {
+            final int minLengthForLetters = Math.max(mPasswordMinLetters,
+                    mPasswordMinUpperCase + mPasswordMinLowerCase);
+            final int minLengthForNonLetters = Math.max(mPasswordMinNonLetter,
+                    mPasswordMinSymbols + mPasswordMinNumeric);
+            return minLengthForLetters + minLengthForNonLetters;
+        }
+
         /**
          * Update the hint based on current Stage and length of password entry
          */
@@ -818,7 +836,8 @@
             if (mUiStage == Stage.ConfirmWrong) {
                 mUiStage = Stage.NeedToConfirm;
             }
-            updateUi();
+            // Schedule the UI update.
+            mTextChangedHandler.notifyAfterTextChanged();
         }
 
         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -864,6 +883,27 @@
             }
             getActivity().finish();
         }
+
+        class TextChangedHandler extends Handler {
+            private static final int ON_TEXT_CHANGED = 1;
+            private static final int DELAY_IN_MILLISECOND = 100;
+
+            /**
+             * With the introduction of delay, we batch processing the text changed event to reduce
+             * unnecessary UI updates.
+             */
+            private void notifyAfterTextChanged() {
+                removeMessages(ON_TEXT_CHANGED);
+                sendEmptyMessageDelayed(ON_TEXT_CHANGED, DELAY_IN_MILLISECOND);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == ON_TEXT_CHANGED) {
+                    updateUi();
+                }
+            }
+        }
     }
 
     private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
diff --git a/src/com/android/settings/TetherService.java b/src/com/android/settings/TetherService.java
index ad0e41f..6d359f2 100644
--- a/src/com/android/settings/TetherService.java
+++ b/src/com/android/settings/TetherService.java
@@ -20,6 +20,7 @@
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.app.Service;
+import android.app.usage.UsageStatsManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothPan;
 import android.bluetooth.BluetoothProfile;
@@ -29,6 +30,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.net.ConnectivityManager;
 import android.os.IBinder;
 import android.os.ResultReceiver;
@@ -63,6 +66,7 @@
 
     private int mCurrentTypeIndex;
     private boolean mInProvisionCheck;
+    private UsageStatsManagerWrapper mUsageManagerWrapper;
     private ArrayList<Integer> mCurrentTethers;
     private ArrayMap<Integer, List<ResultReceiver>> mPendingCallbacks;
 
@@ -87,6 +91,9 @@
         mPendingCallbacks.put(ConnectivityManager.TETHERING_USB, new ArrayList<ResultReceiver>());
         mPendingCallbacks.put(
                 ConnectivityManager.TETHERING_BLUETOOTH, new ArrayList<ResultReceiver>());
+        if (mUsageManagerWrapper == null) {
+            mUsageManagerWrapper = new UsageStatsManagerWrapper(this);
+        }
     }
 
     @Override
@@ -228,20 +235,46 @@
 
     private void startProvisioning(int index) {
         if (index < mCurrentTethers.size()) {
-            String provisionAction = getResources().getString(
-                    com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui);
-            if (DEBUG) Log.d(TAG, "Sending provisioning broadcast: " + provisionAction + " type: "
-                    + mCurrentTethers.get(index));
-            Intent intent = new Intent(provisionAction);
-            int type = mCurrentTethers.get(index);
-            intent.putExtra(TETHER_CHOICE, type);
-            intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            Intent intent = getProvisionBroadcastIntent(index);
+            setEntitlementAppActive(index);
+
+            if (DEBUG) Log.d(TAG, "Sending provisioning broadcast: " + intent.getAction()
+                    + " type: " + mCurrentTethers.get(index));
 
             sendBroadcast(intent);
             mInProvisionCheck = true;
         }
     }
 
+    private Intent getProvisionBroadcastIntent(int index) {
+        String provisionAction = getResources().getString(
+                com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui);
+        Intent intent = new Intent(provisionAction);
+        int type = mCurrentTethers.get(index);
+        intent.putExtra(TETHER_CHOICE, type);
+        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+        return intent;
+    }
+
+    private void setEntitlementAppActive(int index) {
+        final PackageManager packageManager = getPackageManager();
+        Intent intent = getProvisionBroadcastIntent(index);
+        List<ResolveInfo> resolvers =
+                packageManager.queryBroadcastReceivers(intent, PackageManager.MATCH_ALL);
+        if (resolvers.isEmpty()) {
+            Log.e(TAG, "No found BroadcastReceivers for provision intent.");
+            return;
+        }
+
+        for (ResolveInfo resolver : resolvers) {
+            if (resolver.activityInfo.applicationInfo.isSystemApp()) {
+                String packageName = resolver.activityInfo.packageName;
+                mUsageManagerWrapper.setAppInactive(packageName, false);
+            }
+        }
+    }
+
     private void scheduleAlarm() {
         Intent intent = new Intent(this, TetherService.class);
         intent.putExtra(ConnectivityManager.EXTRA_RUN_PROVISION, true);
@@ -335,4 +368,26 @@
         }
     };
 
+    @VisibleForTesting
+    void setUsageStatsManagerWrapper(UsageStatsManagerWrapper wrapper) {
+        mUsageManagerWrapper = wrapper;
+    }
+
+    /**
+     * A static helper class used for tests. UsageStatsManager cannot be mocked out becasue
+     * it's marked final. This class can be mocked out instead.
+     */
+    @VisibleForTesting
+    public static class UsageStatsManagerWrapper {
+        private final UsageStatsManager mUsageStatsManager;
+
+        UsageStatsManagerWrapper(Context context) {
+            mUsageStatsManager = (UsageStatsManager)
+                    context.getSystemService(Context.USAGE_STATS_SERVICE);
+        }
+
+        void setAppInactive(String packageName, boolean isInactive) {
+            mUsageStatsManager.setAppInactive(packageName, isInactive);
+        }
+    }
 }
diff --git a/src/com/android/settings/password/PasswordRequirementAdapter.java b/src/com/android/settings/password/PasswordRequirementAdapter.java
index dd4d10a..b05d8b8 100644
--- a/src/com/android/settings/password/PasswordRequirementAdapter.java
+++ b/src/com/android/settings/password/PasswordRequirementAdapter.java
@@ -28,7 +28,7 @@
         .PasswordRequirementViewHolder;
 
 /**
- * Used in {@link com.android.settings.ConfirmLockPassword} to show password requirements.
+ * Used in {@link com.android.settings.ChooseLockPassword} to show password requirements.
  */
 public class PasswordRequirementAdapter extends
         RecyclerView.Adapter<PasswordRequirementViewHolder> {
diff --git a/tests/unit/src/com/android/settings/TetherServiceTest.java b/tests/unit/src/com/android/settings/TetherServiceTest.java
index 09c6119..e2bb5f8 100644
--- a/tests/unit/src/com/android/settings/TetherServiceTest.java
+++ b/tests/unit/src/com/android/settings/TetherServiceTest.java
@@ -35,11 +35,16 @@
 import android.app.Activity;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
+import android.app.usage.UsageStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
 import android.content.res.Resources;
@@ -61,10 +66,16 @@
 import org.mockito.MockitoAnnotations;
 
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 public class TetherServiceTest extends ServiceTestCase<TetherService> {
 
     private static final String TAG = "TetherServiceTest";
+    private static final String FAKE_PACKAGE_NAME = "com.some.package.name";
+    private static final String ENTITLEMENT_PACKAGE_NAME = "com.some.entitlement.name";
     private static final String TEST_RESPONSE_ACTION = "testProvisioningResponseAction";
     private static final String TEST_NO_UI_ACTION = "testNoUiProvisioningRequestAction";
     private static final int BOGUS_RECEIVER_RESULT = -5;
@@ -75,6 +86,7 @@
 
     private TetherService mService;
     private MockResources mResources;
+    private FakeUsageStatsManagerWrapper mUsageStatsManagerWrapper;
     int mLastReceiverResultCode = BOGUS_RECEIVER_RESULT;
     private int mLastTetherRequestType = TETHERING_INVALID;
     private int mProvisionResponse = BOGUS_RECEIVER_RESULT;
@@ -83,6 +95,7 @@
 
     @Mock private AlarmManager mAlarmManager;
     @Mock private ConnectivityManager mConnectivityManager;
+    @Mock private PackageManager mPackageManager;
     @Mock private WifiManager mWifiManager;
     @Mock private SharedPreferences mPrefs;
     @Mock private Editor mPrefEditor;
@@ -115,6 +128,27 @@
         when(mPrefs.edit()).thenReturn(mPrefEditor);
         when(mPrefEditor.putString(eq(CURRENT_TYPES), mStoredTypes.capture())).thenReturn(
                 mPrefEditor);
+        mUsageStatsManagerWrapper = new FakeUsageStatsManagerWrapper(mContext);
+
+        ResolveInfo systemAppResolveInfo = new ResolveInfo();
+        ActivityInfo systemActivityInfo = new ActivityInfo();
+        systemActivityInfo.packageName = ENTITLEMENT_PACKAGE_NAME;
+        ApplicationInfo systemAppInfo = new ApplicationInfo();
+        systemAppInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        systemActivityInfo.applicationInfo = systemAppInfo;
+        systemAppResolveInfo.activityInfo = systemActivityInfo;
+
+        ResolveInfo nonSystemResolveInfo = new ResolveInfo();
+        ActivityInfo nonSystemActivityInfo = new ActivityInfo();
+        nonSystemActivityInfo.packageName = FAKE_PACKAGE_NAME;
+        nonSystemActivityInfo.applicationInfo = new ApplicationInfo();
+        nonSystemResolveInfo.activityInfo = nonSystemActivityInfo;
+
+        List<ResolveInfo> resolvers = new ArrayList();
+        resolvers.add(nonSystemResolveInfo);
+        resolvers.add(systemAppResolveInfo);
+        when(mPackageManager.queryBroadcastReceivers(
+                any(Intent.class), eq(PackageManager.MATCH_ALL))).thenReturn(resolvers);
     }
 
     @Override
@@ -139,6 +173,19 @@
         assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
     }
 
+    public void testStartKeepsProvisionAppActive() {
+        setupService();
+        getService().setUsageStatsManagerWrapper(mUsageStatsManagerWrapper);
+
+        runProvisioningForType(TETHERING_WIFI);
+
+        assertTrue(waitForProvisionRequest(TETHERING_WIFI));
+        assertTrue(waitForProvisionResponse(TETHER_ERROR_NO_ERROR));
+        assertFalse(mUsageStatsManagerWrapper.isAppInactive(ENTITLEMENT_PACKAGE_NAME));
+        // Non-system handler of the intent action should stay idle.
+        assertTrue(mUsageStatsManagerWrapper.isAppInactive(FAKE_PACKAGE_NAME));
+    }
+
     public void testScheduleRechecks() {
         Intent intent = new Intent();
         intent.putExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_WIFI);
@@ -229,6 +276,19 @@
         startService(intent);
     }
 
+    private boolean waitForAppInactive(UsageStatsManager usageStatsManager, String packageName) {
+        long startTime = SystemClock.uptimeMillis();
+        while (true) {
+            if (usageStatsManager.isAppInactive(packageName)) {
+                return true;
+            }
+            if ((SystemClock.uptimeMillis() - startTime) > PROVISION_TIMEOUT) {
+                return false;
+            }
+            SystemClock.sleep(SHORT_TIMEOUT);
+        }
+    }
+
     private boolean waitForProvisionRequest(int expectedType) {
         long startTime = SystemClock.uptimeMillis();
         while (true) {
@@ -308,6 +368,11 @@
         }
 
         @Override
+        public PackageManager getPackageManager() {
+            return mPackageManager;
+        }
+
+        @Override
         public Object getSystemService(String name) {
             if (ALARM_SERVICE.equals(name)) {
                 return mAlarmManager;
@@ -355,4 +420,27 @@
                     responseIntent, android.Manifest.permission.CONNECTIVITY_INTERNAL);
         }
     }
+
+    private static class FakeUsageStatsManagerWrapper
+            extends TetherService.UsageStatsManagerWrapper {
+        private final Set<String> mActivePackages;
+
+        FakeUsageStatsManagerWrapper(Context context) {
+            super(context);
+            mActivePackages = new HashSet<>();
+        }
+
+        @Override
+        void setAppInactive(String packageName, boolean isInactive) {
+            if (!isInactive) {
+                mActivePackages.add(packageName);
+            } else {
+                mActivePackages.remove(packageName);
+            }
+        }
+
+        boolean isAppInactive(String packageName) {
+            return !mActivePackages.contains(packageName);
+        }
+    }
 }