diff --git a/res/drawable/work_apps_toggle_background.xml b/res/drawable/work_apps_toggle_background.xml
deleted file mode 100644
index a04d269875..0000000000
--- a/res/drawable/work_apps_toggle_background.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
- -
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
diff --git a/res/layout/work_mode_fab.xml b/res/layout/work_mode_fab.xml
deleted file mode 100644
index 21f269fed6..0000000000
--- a/res/layout/work_mode_fab.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/res/layout/work_mode_switch.xml b/res/layout/work_mode_switch.xml
index 538a180992..31953c72aa 100644
--- a/res/layout/work_mode_switch.xml
+++ b/res/layout/work_mode_switch.xml
@@ -1,4 +1,5 @@
-
-
+ android:paddingTop="@dimen/work_profile_footer_padding"
+/>
diff --git a/res/layout/work_profile_edu.xml b/res/layout/work_profile_edu.xml
new file mode 100644
index 0000000000..c3c7010f7e
--- /dev/null
+++ b/res/layout/work_profile_edu.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index caeb0b4eb1..d06561112e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -115,10 +115,6 @@
8dp
-
- 48dp
- 24dp
- 18dp
20dp
16sp
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 42f69e0f92..c851cf8273 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -389,24 +389,27 @@
Got it
- Work apps are off
+ Work profile is paused
- Your work apps can’t send you notifications, use your battery, or access your location
+ Work apps can’t send you notifications, use your battery, or access your location
- Work apps are off. Your work apps can’t send you notifications, use your battery, or access your location
+ Work profile is paused. Work apps can’t send you notifications, use your battery, or access your location
Work apps are badged and visible to your IT admin
Got it
- Turn off work apps
+ Pause work apps
- Turn on work apps
+ Turn on
Filter
+
+ Pause work apps and notifications
+
Failed: %1$s
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 801a6c3300..cb20fec47a 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -228,7 +228,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
}
private void resetWorkProfile() {
- mWorkModeSwitch.updateCurrentState(!mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED));
+ mWorkModeSwitch.update(!mAllAppsStore.hasModelFlag(FLAG_QUIET_MODE_ENABLED));
mAH[AdapterHolder.WORK].setupOverlay();
mAH[AdapterHolder.WORK].applyPadding();
}
@@ -482,7 +482,7 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo
private void setupWorkToggle() {
if (Utilities.ATLEAST_P) {
mWorkModeSwitch = (WorkModeSwitch) mLauncher.getLayoutInflater().inflate(
- R.layout.work_mode_fab, this, false);
+ R.layout.work_mode_switch, this, false);
this.addView(mWorkModeSwitch);
mWorkModeSwitch.setInsets(mInsets);
mWorkModeSwitch.post(this::resetWorkProfile);
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index 1eb726ce0d..16ae2508ec 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -25,6 +25,7 @@ import android.view.MotionEvent;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.views.WorkEduView;
/**
* AllAppsContainerView with launcher specific callbacks
@@ -87,6 +88,13 @@ public class LauncherAllAppsContainerView extends AllAppsContainerView {
@Override
public void onActivePageChanged(int currentActivePage) {
super.onActivePageChanged(currentActivePage);
+ if (mUsingTabs) {
+ if (currentActivePage == AdapterHolder.WORK) {
+ WorkEduView.showWorkEduIfNeeded(mLauncher);
+ } else {
+ mWorkTabListener = WorkEduView.showEduFlowIfNeeded(mLauncher, mWorkTabListener);
+ }
+ }
}
@Override
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index c742909161..4567ee620f 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -15,57 +15,108 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
import android.content.Context;
+import android.content.SharedPreferences;
import android.graphics.Rect;
+import android.os.AsyncTask;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.widget.Switch;
import com.android.launcher3.Insettable;
import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.views.ArrowTipView;
+
+import java.lang.ref.WeakReference;
/**
* Work profile toggle switch shown at the bottom of AllApps work tab
*/
-public class WorkModeSwitch extends Button implements Insettable, View.OnClickListener {
+public class WorkModeSwitch extends Switch implements Insettable {
+
+ private static final int WORK_TIP_THRESHOLD = 2;
+ public static final String KEY_WORK_TIP_COUNTER = "worked_tip_counter";
private Rect mInsets = new Rect();
- private boolean mWorkEnabled;
+
+ private final float[] mTouch = new float[2];
+ private int mTouchSlop;
public WorkModeSwitch(Context context) {
- this(context, null, 0);
+ super(context);
+ init();
}
public WorkModeSwitch(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
+ super(context, attrs);
+ init();
}
public WorkModeSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
+ mTouchSlop = viewConfiguration.getScaledTouchSlop();
}
@Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- setOnClickListener(this);
+ public void setChecked(boolean checked) { }
+
+ @Override
+ public void toggle() {
+ // don't show tip if user uses toggle
+ Utilities.getPrefs(getContext()).edit().putInt(KEY_WORK_TIP_COUNTER, -1).apply();
+ trySetQuietModeEnabledToAllProfilesAsync(isChecked());
+ }
+
+ /**
+ * Sets the enabled or disabled state of the button
+ * @param isChecked
+ */
+ public void update(boolean isChecked) {
+ super.setChecked(isChecked);
+ setCompoundDrawablesRelativeWithIntrinsicBounds(
+ isChecked ? R.drawable.ic_corp : R.drawable.ic_corp_off, 0, 0, 0);
+ setEnabled(true);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mTouch[0] = ev.getX();
+ mTouch[1] = ev.getY();
+ } else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
+ if (Math.abs(mTouch[0] - ev.getX()) > mTouchSlop
+ || Math.abs(mTouch[1] - ev.getY()) > mTouchSlop) {
+ int action = ev.getAction();
+ ev.setAction(MotionEvent.ACTION_CANCEL);
+ super.onTouchEvent(ev);
+ ev.setAction(action);
+ return false;
+ }
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ private void trySetQuietModeEnabledToAllProfilesAsync(boolean enabled) {
+ new SetQuietModeEnabledAsyncTask(enabled, new WeakReference<>(this)).execute();
}
@Override
public void setInsets(Rect insets) {
int bottomInset = insets.bottom - mInsets.bottom;
mInsets.set(insets);
- ViewGroup.MarginLayoutParams marginLayoutParams =
- (ViewGroup.MarginLayoutParams) getLayoutParams();
- if (marginLayoutParams != null) {
- marginLayoutParams.bottomMargin = bottomInset + marginLayoutParams.bottomMargin;
- }
+ setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
+ getPaddingBottom() + bottomInset);
}
/**
@@ -74,41 +125,78 @@ public class WorkModeSwitch extends Button implements Insettable, View.OnClickLi
public void setWorkTabVisible(boolean workTabVisible) {
clearAnimation();
if (workTabVisible) {
- setEnabled(true);
setVisibility(VISIBLE);
setAlpha(0);
animate().alpha(1).start();
+ showTipIfNeeded();
} else {
animate().alpha(0).withEndAction(() -> this.setVisibility(GONE)).start();
}
}
- @Override
- public void onClick(View view) {
- setEnabled(false);
- UI_HELPER_EXECUTOR.post(() -> setToState(!mWorkEnabled));
+ private static final class SetQuietModeEnabledAsyncTask
+ extends AsyncTask {
+
+ private final boolean enabled;
+ private final WeakReference switchWeakReference;
+
+ SetQuietModeEnabledAsyncTask(boolean enabled,
+ WeakReference switchWeakReference) {
+ this.enabled = enabled;
+ this.switchWeakReference = switchWeakReference;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ WorkModeSwitch workModeSwitch = switchWeakReference.get();
+ if (workModeSwitch != null) {
+ workModeSwitch.setEnabled(false);
+ }
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... voids) {
+ WorkModeSwitch workModeSwitch = switchWeakReference.get();
+ if (workModeSwitch == null || !Utilities.ATLEAST_P) {
+ return false;
+ }
+
+ Context context = workModeSwitch.getContext();
+ UserManager userManager = context.getSystemService(UserManager.class);
+ boolean showConfirm = false;
+ for (UserHandle userProfile : UserCache.INSTANCE.get(context).getUserProfiles()) {
+ if (Process.myUserHandle().equals(userProfile)) {
+ continue;
+ }
+ showConfirm |= !userManager.requestQuietModeEnabled(enabled, userProfile);
+ }
+ return showConfirm;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean showConfirm) {
+ if (showConfirm) {
+ WorkModeSwitch workModeSwitch = switchWeakReference.get();
+ if (workModeSwitch != null) {
+ workModeSwitch.setEnabled(true);
+ }
+ }
+ }
}
/**
- * Sets the enabled or disabled state of the button
+ * Shows a work tip on the Nth work tab open
*/
- public void updateCurrentState(boolean active) {
- mWorkEnabled = active;
- setEnabled(true);
- setCompoundDrawablesRelativeWithIntrinsicBounds(
- active ? R.drawable.ic_corp_off : R.drawable.ic_corp, 0, 0, 0);
- setText(active ? R.string.work_apps_pause_btn_text : R.string.work_apps_enable_btn_text);
- }
-
- protected Boolean setToState(boolean toState) {
- UserManager userManager = getContext().getSystemService(UserManager.class);
- boolean showConfirm = false;
- for (UserHandle userProfile : UserCache.INSTANCE.get(getContext()).getUserProfiles()) {
- if (Process.myUserHandle().equals(userProfile)) {
- continue;
- }
- showConfirm |= !userManager.requestQuietModeEnabled(!toState, userProfile);
+ public void showTipIfNeeded() {
+ Context context = getContext();
+ SharedPreferences prefs = Utilities.getPrefs(context);
+ int tipCounter = prefs.getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD);
+ if (tipCounter < 0) return;
+ if (tipCounter == 0) {
+ new ArrowTipView(context)
+ .show(context.getString(R.string.work_switch_tip), getTop());
}
- return showConfirm;
+ prefs.edit().putInt(KEY_WORK_TIP_COUNTER, tipCounter - 1).apply();
}
}
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
new file mode 100644
index 0000000000..6be0c23577
--- /dev/null
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2020 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.launcher3.views;
+
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsPagedView;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.statemanager.StateManager.StateListener;
+
+/**
+ * On boarding flow for users right after setting up work profile
+ */
+public class WorkEduView extends AbstractSlideInView
+ implements Insettable, StateListener {
+
+ private static final int DEFAULT_CLOSE_DURATION = 200;
+ public static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
+ public static final String KEY_LEGACY_WORK_EDU_SEEN = "showed_bottom_user_education";
+
+ private static final int WORK_EDU_NOT_STARTED = 0;
+ private static final int WORK_EDU_PERSONAL_APPS = 1;
+ private static final int WORK_EDU_WORK_APPS = 2;
+
+ protected static final int FINAL_SCRIM_BG_COLOR = 0x88000000;
+
+
+ private Rect mInsets = new Rect();
+ private View mViewWrapper;
+ private Button mProceedButton;
+ private TextView mContentText;
+
+ private int mNextWorkEduStep = WORK_EDU_PERSONAL_APPS;
+
+
+ public WorkEduView(Context context, AttributeSet attr) {
+ this(context, attr, 0);
+ }
+
+ public WorkEduView(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mContent = this;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ mActivityContext.getSharedPrefs().edit()
+ .putInt(KEY_WORK_EDU_STEP, mNextWorkEduStep).apply();
+ handleClose(true, DEFAULT_CLOSE_DURATION);
+ }
+
+ @Override
+ protected void onCloseComplete() {
+ super.onCloseComplete();
+ mActivityContext.getStateManager().removeStateListener(this);
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_ON_BOARD_POPUP) != 0;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mViewWrapper = findViewById(R.id.view_wrapper);
+ mProceedButton = findViewById(R.id.proceed);
+ mContentText = findViewById(R.id.content_text);
+
+ // make sure layout does not shrink when we change the text
+ mContentText.post(() -> mContentText.setMinLines(mContentText.getLineCount()));
+
+ mProceedButton.setOnClickListener(view -> {
+ if (getAllAppsPagedView() != null) {
+ getAllAppsPagedView().snapToPage(AllAppsContainerView.AdapterHolder.WORK);
+ }
+ goToWorkTab(true);
+ });
+ }
+
+ private void goToWorkTab(boolean animate) {
+ mProceedButton.setText(R.string.work_profile_edu_accept);
+ if (animate) {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(mContentText, ALPHA, 0);
+ animator.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mContentText.setText(
+ mActivityContext.getString(R.string.work_profile_edu_work_apps));
+ ObjectAnimator.ofFloat(mContentText, ALPHA, 1).start();
+ }
+ });
+ animator.start();
+ } else {
+ mContentText.setText(mActivityContext.getString(R.string.work_profile_edu_work_apps));
+ }
+ mNextWorkEduStep = WORK_EDU_WORK_APPS;
+ mProceedButton.setOnClickListener(v -> handleClose(true));
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ int leftInset = insets.left - mInsets.left;
+ int rightInset = insets.right - mInsets.right;
+ int bottomInset = insets.bottom - mInsets.bottom;
+ mInsets.set(insets);
+ setPadding(leftInset, getPaddingTop(), rightInset, 0);
+ mViewWrapper.setPaddingRelative(mViewWrapper.getPaddingStart(),
+ mViewWrapper.getPaddingTop(), mViewWrapper.getPaddingEnd(), bottomInset);
+ }
+
+ private void show() {
+ attachToContainer();
+ animateOpen();
+ mActivityContext.getStateManager().addStateListener(this);
+ }
+
+ @Override
+ protected int getScrimColor(Context context) {
+ return FINAL_SCRIM_BG_COLOR;
+ }
+
+ private void goToFirstPage() {
+ if (getAllAppsPagedView() != null) {
+ getAllAppsPagedView().snapToPageImmediately(AllAppsContainerView.AdapterHolder.MAIN);
+ }
+ }
+
+ private void animateOpen() {
+ if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ return;
+ }
+ mIsOpen = true;
+ mOpenCloseAnimator.setValues(
+ PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+ mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mOpenCloseAnimator.start();
+ }
+
+ private AllAppsPagedView getAllAppsPagedView() {
+ View v = mActivityContext.getAppsView().getContentView();
+ return (v instanceof AllAppsPagedView) ? (AllAppsPagedView) v : null;
+ }
+
+ /**
+ * Checks if user has not seen onboarding UI yet and shows it when user navigates to all apps
+ */
+ public static StateListener showEduFlowIfNeeded(Launcher launcher,
+ @Nullable StateListener oldListener) {
+ if (oldListener != null) {
+ launcher.getStateManager().removeStateListener(oldListener);
+ }
+ if (hasSeenLegacyEdu(launcher) || launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP,
+ WORK_EDU_NOT_STARTED) != WORK_EDU_NOT_STARTED) {
+ return null;
+ }
+
+ StateListener listener = new StateListener() {
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ if (finalState != LauncherState.ALL_APPS) return;
+ LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+ WorkEduView v = (WorkEduView) layoutInflater.inflate(
+ R.layout.work_profile_edu, launcher.getDragLayer(),
+ false);
+ v.show();
+ v.goToFirstPage();
+ launcher.getStateManager().removeStateListener(this);
+ }
+ };
+ launcher.getStateManager().addStateListener(listener);
+ return listener;
+ }
+
+ /**
+ * Shows work apps edu if user had dismissed full edu flow
+ */
+ public static void showWorkEduIfNeeded(Launcher launcher) {
+ if (hasSeenLegacyEdu(launcher) || launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP,
+ WORK_EDU_NOT_STARTED) != WORK_EDU_PERSONAL_APPS) {
+ return;
+ }
+ LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+ WorkEduView v = (WorkEduView) layoutInflater.inflate(
+ R.layout.work_profile_edu, launcher.getDragLayer(), false);
+ v.show();
+ v.goToWorkTab(false);
+ }
+
+ private static boolean hasSeenLegacyEdu(Launcher launcher) {
+ return launcher.getSharedPrefs().getBoolean(KEY_LEGACY_WORK_EDU_SEEN, false);
+ }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ close(false);
+ }
+}