Loading src/com/android/documentsui/BaseActivity.java +20 −4 Original line number Diff line number Diff line Loading @@ -74,7 +74,6 @@ import com.android.documentsui.roots.ProvidersCache; import com.android.documentsui.sidebar.RootsFragment; import com.android.documentsui.sorting.SortController; import com.android.documentsui.sorting.SortModel; import com.android.documentsui.util.FeatureFlagUtils; import com.google.android.material.appbar.AppBarLayout; Loading Loading @@ -104,6 +103,7 @@ public abstract class BaseActivity protected NavigationViewManager mNavigator; protected SortController mSortController; protected ConfigStore mConfigStore; private final List<EventListener> mEventListeners = new ArrayList<>(); private final String mTag; Loading Loading @@ -138,6 +138,16 @@ public abstract class BaseActivity public abstract Injector<?> getInjector(); @VisibleForTesting protected void initConfigStore() { mConfigStore = DocumentsApplication.getConfigStore(this); } @VisibleForTesting public void setConfigStore(ConfigStore configStore) { mConfigStore = configStore; } @CallSuper @Override public void onCreate(Bundle savedInstanceState) { Loading @@ -159,6 +169,8 @@ public abstract class BaseActivity setContainer(); initConfigStore(); mInjector = getInjector(); mState = getState(savedInstanceState); mDrawer = DrawerController.create(this, mInjector.config); Loading Loading @@ -347,12 +359,14 @@ public abstract class BaseActivity private NavigationViewManager getNavigationViewManager(Breadcrumb breadcrumb, View profileTabsContainer) { if (FeatureFlagUtils.isPrivateSpaceEnabled()) { if (mConfigStore.isPrivateSpaceInDocsUIEnabled()) { return new NavigationViewManager(this, mDrawer, mState, this, breadcrumb, profileTabsContainer, DocumentsApplication.getUserManagerState(this)); profileTabsContainer, DocumentsApplication.getUserManagerState(this), mConfigStore); } return new NavigationViewManager(this, mDrawer, mState, this, breadcrumb, profileTabsContainer, DocumentsApplication.getUserIdManager(this)); profileTabsContainer, DocumentsApplication.getUserIdManager(this), mConfigStore); } public void onPreferenceChanged(String pref) { Loading Loading @@ -413,6 +427,7 @@ public abstract class BaseActivity mPreferencesMonitor.stop(); mSortController.destroy(); DocumentsApplication.invalidateUserManagerState(this); DocumentsApplication.invalidateConfigStore(); super.onDestroy(); } Loading @@ -438,6 +453,7 @@ public abstract class BaseActivity getApplicationContext() .getResources() .getBoolean(R.bool.show_hidden_files_by_default)); state.configStore = mConfigStore; includeState(state); Loading src/com/android/documentsui/ConfigStore.java 0 → 100644 +85 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.documentsui; import android.os.Binder; import android.provider.DeviceConfig; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.google.common.base.Supplier; public interface ConfigStore { // TODO(b/288066342): Remove and replace after new constant definition in // {@link android.provider.DeviceConfig}. String NAMESPACE_MEDIAPROVIDER = "mediaprovider"; boolean DEFAULT_PICKER_PRIVATE_SPACE_ENABLED = false; /** * @return if the Private-Space-in-DocsUI is enabled */ default boolean isPrivateSpaceInDocsUIEnabled() { return DEFAULT_PICKER_PRIVATE_SPACE_ENABLED; } /** * Implementation of the {@link ConfigStore} that reads "real" configs from * {@link android.provider.DeviceConfig}. Meant to be used by the "production" code. */ class ConfigStoreImpl implements ConfigStore { @VisibleForTesting public static final String KEY_PRIVATE_SPACE_FEATURE_ENABLED = "private_space_feature_enabled"; private static final boolean sCanReadDeviceConfig = SdkLevel.isAtLeastS(); private Boolean mIsPrivateSpaceEnabled = null; @Override public boolean isPrivateSpaceInDocsUIEnabled() { if (mIsPrivateSpaceEnabled == null) { mIsPrivateSpaceEnabled = getBooleanDeviceConfig( NAMESPACE_MEDIAPROVIDER, KEY_PRIVATE_SPACE_FEATURE_ENABLED, DEFAULT_PICKER_PRIVATE_SPACE_ENABLED); } return sCanReadDeviceConfig && mIsPrivateSpaceEnabled; } private static boolean getBooleanDeviceConfig(@NonNull String namespace, @NonNull String key, boolean defaultValue) { if (!sCanReadDeviceConfig) { return defaultValue; } return withCleanCallingIdentity( () -> DeviceConfig.getBoolean(namespace, key, defaultValue)); } private static <T> T withCleanCallingIdentity(@NonNull Supplier<T> action) { final long callingIdentity = Binder.clearCallingIdentity(); try { return action.get(); } finally { Binder.restoreCallingIdentity(callingIdentity); } } } } src/com/android/documentsui/DirectoryLoader.java +1 −2 Original line number Diff line number Diff line Loading @@ -48,7 +48,6 @@ import com.android.documentsui.base.State; import com.android.documentsui.base.UserId; import com.android.documentsui.roots.RootCursorWrapper; import com.android.documentsui.sorting.SortModel; import com.android.documentsui.util.FeatureFlagUtils; import java.util.ArrayList; import java.util.List; Loading Loading @@ -332,7 +331,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { } private List<UserId> getUserIds() { if (FeatureFlagUtils.isPrivateSpaceEnabled()) { if (mState.configStore.isPrivateSpaceInDocsUIEnabled()) { return DocumentsApplication.getUserManagerState(getContext()).getUserIds(); } return DocumentsApplication.getUserIdManager(getContext()).getUserIds(); Loading src/com/android/documentsui/DocumentsApplication.java +41 −6 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.os.RemoteException; import android.text.format.DateUtils; import android.util.Log; import androidx.annotation.Nullable; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.android.documentsui.base.Lookup; Loading @@ -41,7 +42,6 @@ import com.android.documentsui.clipping.DocumentClipper; import com.android.documentsui.queries.SearchHistoryManager; import com.android.documentsui.roots.ProvidersCache; import com.android.documentsui.theme.ThemeOverlayManager; import com.android.documentsui.util.FeatureFlagUtils; import com.android.modules.utils.build.SdkLevel; import com.google.common.collect.Lists; Loading @@ -49,6 +49,8 @@ import com.google.common.collect.Lists; import java.util.List; import java.util.Objects; import javax.annotation.concurrent.GuardedBy; public class DocumentsApplication extends Application { private static final String TAG = "DocumentsApplication"; private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS; Loading @@ -67,6 +69,10 @@ public class DocumentsApplication extends Application { Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE ); @GuardedBy("DocumentsApplication.class") @Nullable private static volatile ConfigStore sConfigStore; private ProvidersCache mProviders; private ThumbnailCache mThumbnailCache; private ClipStorage mClipStore; Loading Loading @@ -127,13 +133,33 @@ public class DocumentsApplication extends Application { } /** * Set mUserManagerState as null onDestroy of BaseActivity so that new session uses new instance * of mUserManagerState * Set {@link #mUserManagerState} as null onDestroy of BaseActivity so that new session uses new * instance of {@link #mUserManagerState} */ public static void invalidateUserManagerState(Context context) { ((DocumentsApplication) context.getApplicationContext()).mUserManagerState = null; } /** * Retrieve {@link ConfigStore} instance to access feature flags in production code. */ public static synchronized ConfigStore getConfigStore(Context context) { if (sConfigStore == null) { sConfigStore = new ConfigStore.ConfigStoreImpl(); } return sConfigStore; } /** * Set {@link #sConfigStore} as null onDestroy of BaseActivity so that new session uses new * instance of {@link #sConfigStore} */ public static void invalidateConfigStore() { synchronized (DocumentsApplication.class) { sConfigStore = null; } } private void onApplyOverlayFinish(boolean result) { Log.d(TAG, "OverlayManager.setEnabled() result: " + result); } Loading @@ -142,6 +168,11 @@ public class DocumentsApplication extends Application { @Override public void onCreate() { super.onCreate(); synchronized (DocumentsApplication.class) { if (sConfigStore == null) { sConfigStore = new ConfigStore.ConfigStoreImpl(); } } final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); final OverlayManager om = getSystemService(OverlayManager.class); Loading @@ -154,14 +185,18 @@ public class DocumentsApplication extends Application { Log.w(TAG, "Can't obtain OverlayManager from System Service!"); } if (FeatureFlagUtils.isPrivateSpaceEnabled()) { if (getConfigStore(this).isPrivateSpaceInDocsUIEnabled()) { mUserManagerState = UserManagerState.create(this); mUserIdManager = null; mProviders = new ProvidersCache(this, mUserManagerState); synchronized (DocumentsApplication.class) { mProviders = new ProvidersCache(this, mUserManagerState, sConfigStore); } } else { mUserManagerState = null; mUserIdManager = UserIdManager.create(this); mProviders = new ProvidersCache(this, mUserIdManager); synchronized (DocumentsApplication.class) { mProviders = new ProvidersCache(this, mUserIdManager, sConfigStore); } } mProviders.updateAsync(/* forceRefreshAll= */ false, /* callback= */ null); Loading src/com/android/documentsui/HorizontalBreadcrumb.java +1 −0 Original line number Diff line number Diff line Loading @@ -67,6 +67,7 @@ public final class HorizontalBreadcrumb extends RecyclerView implements Breadcru mLayoutManager = new HorizontalBreadcrumbLinearLayoutManager( getContext(), LinearLayoutManager.HORIZONTAL, false); mAdapter = new BreadcrumbAdapter(state, env, this::onKey); mAdapter.setHasStableIds(true); // Since we are using GestureDetector to detect click events, a11y services don't know which // views are clickable because we aren't using View.OnClickListener. Thus, we need to use a // custom accessibility delegate to route click events correctly. Loading Loading
src/com/android/documentsui/BaseActivity.java +20 −4 Original line number Diff line number Diff line Loading @@ -74,7 +74,6 @@ import com.android.documentsui.roots.ProvidersCache; import com.android.documentsui.sidebar.RootsFragment; import com.android.documentsui.sorting.SortController; import com.android.documentsui.sorting.SortModel; import com.android.documentsui.util.FeatureFlagUtils; import com.google.android.material.appbar.AppBarLayout; Loading Loading @@ -104,6 +103,7 @@ public abstract class BaseActivity protected NavigationViewManager mNavigator; protected SortController mSortController; protected ConfigStore mConfigStore; private final List<EventListener> mEventListeners = new ArrayList<>(); private final String mTag; Loading Loading @@ -138,6 +138,16 @@ public abstract class BaseActivity public abstract Injector<?> getInjector(); @VisibleForTesting protected void initConfigStore() { mConfigStore = DocumentsApplication.getConfigStore(this); } @VisibleForTesting public void setConfigStore(ConfigStore configStore) { mConfigStore = configStore; } @CallSuper @Override public void onCreate(Bundle savedInstanceState) { Loading @@ -159,6 +169,8 @@ public abstract class BaseActivity setContainer(); initConfigStore(); mInjector = getInjector(); mState = getState(savedInstanceState); mDrawer = DrawerController.create(this, mInjector.config); Loading Loading @@ -347,12 +359,14 @@ public abstract class BaseActivity private NavigationViewManager getNavigationViewManager(Breadcrumb breadcrumb, View profileTabsContainer) { if (FeatureFlagUtils.isPrivateSpaceEnabled()) { if (mConfigStore.isPrivateSpaceInDocsUIEnabled()) { return new NavigationViewManager(this, mDrawer, mState, this, breadcrumb, profileTabsContainer, DocumentsApplication.getUserManagerState(this)); profileTabsContainer, DocumentsApplication.getUserManagerState(this), mConfigStore); } return new NavigationViewManager(this, mDrawer, mState, this, breadcrumb, profileTabsContainer, DocumentsApplication.getUserIdManager(this)); profileTabsContainer, DocumentsApplication.getUserIdManager(this), mConfigStore); } public void onPreferenceChanged(String pref) { Loading Loading @@ -413,6 +427,7 @@ public abstract class BaseActivity mPreferencesMonitor.stop(); mSortController.destroy(); DocumentsApplication.invalidateUserManagerState(this); DocumentsApplication.invalidateConfigStore(); super.onDestroy(); } Loading @@ -438,6 +453,7 @@ public abstract class BaseActivity getApplicationContext() .getResources() .getBoolean(R.bool.show_hidden_files_by_default)); state.configStore = mConfigStore; includeState(state); Loading
src/com/android/documentsui/ConfigStore.java 0 → 100644 +85 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.documentsui; import android.os.Binder; import android.provider.DeviceConfig; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; import com.google.common.base.Supplier; public interface ConfigStore { // TODO(b/288066342): Remove and replace after new constant definition in // {@link android.provider.DeviceConfig}. String NAMESPACE_MEDIAPROVIDER = "mediaprovider"; boolean DEFAULT_PICKER_PRIVATE_SPACE_ENABLED = false; /** * @return if the Private-Space-in-DocsUI is enabled */ default boolean isPrivateSpaceInDocsUIEnabled() { return DEFAULT_PICKER_PRIVATE_SPACE_ENABLED; } /** * Implementation of the {@link ConfigStore} that reads "real" configs from * {@link android.provider.DeviceConfig}. Meant to be used by the "production" code. */ class ConfigStoreImpl implements ConfigStore { @VisibleForTesting public static final String KEY_PRIVATE_SPACE_FEATURE_ENABLED = "private_space_feature_enabled"; private static final boolean sCanReadDeviceConfig = SdkLevel.isAtLeastS(); private Boolean mIsPrivateSpaceEnabled = null; @Override public boolean isPrivateSpaceInDocsUIEnabled() { if (mIsPrivateSpaceEnabled == null) { mIsPrivateSpaceEnabled = getBooleanDeviceConfig( NAMESPACE_MEDIAPROVIDER, KEY_PRIVATE_SPACE_FEATURE_ENABLED, DEFAULT_PICKER_PRIVATE_SPACE_ENABLED); } return sCanReadDeviceConfig && mIsPrivateSpaceEnabled; } private static boolean getBooleanDeviceConfig(@NonNull String namespace, @NonNull String key, boolean defaultValue) { if (!sCanReadDeviceConfig) { return defaultValue; } return withCleanCallingIdentity( () -> DeviceConfig.getBoolean(namespace, key, defaultValue)); } private static <T> T withCleanCallingIdentity(@NonNull Supplier<T> action) { final long callingIdentity = Binder.clearCallingIdentity(); try { return action.get(); } finally { Binder.restoreCallingIdentity(callingIdentity); } } } }
src/com/android/documentsui/DirectoryLoader.java +1 −2 Original line number Diff line number Diff line Loading @@ -48,7 +48,6 @@ import com.android.documentsui.base.State; import com.android.documentsui.base.UserId; import com.android.documentsui.roots.RootCursorWrapper; import com.android.documentsui.sorting.SortModel; import com.android.documentsui.util.FeatureFlagUtils; import java.util.ArrayList; import java.util.List; Loading Loading @@ -332,7 +331,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { } private List<UserId> getUserIds() { if (FeatureFlagUtils.isPrivateSpaceEnabled()) { if (mState.configStore.isPrivateSpaceInDocsUIEnabled()) { return DocumentsApplication.getUserManagerState(getContext()).getUserIds(); } return DocumentsApplication.getUserIdManager(getContext()).getUserIds(); Loading
src/com/android/documentsui/DocumentsApplication.java +41 −6 Original line number Diff line number Diff line Loading @@ -31,6 +31,7 @@ import android.os.RemoteException; import android.text.format.DateUtils; import android.util.Log; import androidx.annotation.Nullable; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.android.documentsui.base.Lookup; Loading @@ -41,7 +42,6 @@ import com.android.documentsui.clipping.DocumentClipper; import com.android.documentsui.queries.SearchHistoryManager; import com.android.documentsui.roots.ProvidersCache; import com.android.documentsui.theme.ThemeOverlayManager; import com.android.documentsui.util.FeatureFlagUtils; import com.android.modules.utils.build.SdkLevel; import com.google.common.collect.Lists; Loading @@ -49,6 +49,8 @@ import com.google.common.collect.Lists; import java.util.List; import java.util.Objects; import javax.annotation.concurrent.GuardedBy; public class DocumentsApplication extends Application { private static final String TAG = "DocumentsApplication"; private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS; Loading @@ -67,6 +69,10 @@ public class DocumentsApplication extends Application { Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE ); @GuardedBy("DocumentsApplication.class") @Nullable private static volatile ConfigStore sConfigStore; private ProvidersCache mProviders; private ThumbnailCache mThumbnailCache; private ClipStorage mClipStore; Loading Loading @@ -127,13 +133,33 @@ public class DocumentsApplication extends Application { } /** * Set mUserManagerState as null onDestroy of BaseActivity so that new session uses new instance * of mUserManagerState * Set {@link #mUserManagerState} as null onDestroy of BaseActivity so that new session uses new * instance of {@link #mUserManagerState} */ public static void invalidateUserManagerState(Context context) { ((DocumentsApplication) context.getApplicationContext()).mUserManagerState = null; } /** * Retrieve {@link ConfigStore} instance to access feature flags in production code. */ public static synchronized ConfigStore getConfigStore(Context context) { if (sConfigStore == null) { sConfigStore = new ConfigStore.ConfigStoreImpl(); } return sConfigStore; } /** * Set {@link #sConfigStore} as null onDestroy of BaseActivity so that new session uses new * instance of {@link #sConfigStore} */ public static void invalidateConfigStore() { synchronized (DocumentsApplication.class) { sConfigStore = null; } } private void onApplyOverlayFinish(boolean result) { Log.d(TAG, "OverlayManager.setEnabled() result: " + result); } Loading @@ -142,6 +168,11 @@ public class DocumentsApplication extends Application { @Override public void onCreate() { super.onCreate(); synchronized (DocumentsApplication.class) { if (sConfigStore == null) { sConfigStore = new ConfigStore.ConfigStoreImpl(); } } final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); final OverlayManager om = getSystemService(OverlayManager.class); Loading @@ -154,14 +185,18 @@ public class DocumentsApplication extends Application { Log.w(TAG, "Can't obtain OverlayManager from System Service!"); } if (FeatureFlagUtils.isPrivateSpaceEnabled()) { if (getConfigStore(this).isPrivateSpaceInDocsUIEnabled()) { mUserManagerState = UserManagerState.create(this); mUserIdManager = null; mProviders = new ProvidersCache(this, mUserManagerState); synchronized (DocumentsApplication.class) { mProviders = new ProvidersCache(this, mUserManagerState, sConfigStore); } } else { mUserManagerState = null; mUserIdManager = UserIdManager.create(this); mProviders = new ProvidersCache(this, mUserIdManager); synchronized (DocumentsApplication.class) { mProviders = new ProvidersCache(this, mUserIdManager, sConfigStore); } } mProviders.updateAsync(/* forceRefreshAll= */ false, /* callback= */ null); Loading
src/com/android/documentsui/HorizontalBreadcrumb.java +1 −0 Original line number Diff line number Diff line Loading @@ -67,6 +67,7 @@ public final class HorizontalBreadcrumb extends RecyclerView implements Breadcru mLayoutManager = new HorizontalBreadcrumbLinearLayoutManager( getContext(), LinearLayoutManager.HORIZONTAL, false); mAdapter = new BreadcrumbAdapter(state, env, this::onKey); mAdapter.setHasStableIds(true); // Since we are using GestureDetector to detect click events, a11y services don't know which // views are clickable because we aren't using View.OnClickListener. Thus, we need to use a // custom accessibility delegate to route click events correctly. Loading