| /* |
| * Copyright (C) 2015 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.systemupdater; |
| |
| import android.content.Context; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.os.storage.StorageEventListener; |
| import android.os.storage.StorageManager; |
| import android.os.storage.VolumeInfo; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| |
| import androidx.annotation.NonNull; |
| import androidx.fragment.app.Fragment; |
| |
| import com.android.car.ui.recyclerview.CarUiContentListItem; |
| import com.android.car.ui.recyclerview.CarUiListItem; |
| import com.android.car.ui.recyclerview.CarUiListItemAdapter; |
| import com.android.car.ui.recyclerview.CarUiRecyclerView; |
| |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Stack; |
| |
| /** |
| * Display a list of files and directories. |
| */ |
| public class DeviceListFragment extends Fragment implements UpFragment { |
| |
| private static final String TAG = "DeviceListFragment"; |
| private static final String UPDATE_FILE_SUFFIX = ".zip"; |
| private static final FileFilter UPDATE_FILE_FILTER = |
| file -> !file.isHidden() && (file.isDirectory() |
| || file.getName().toLowerCase().endsWith(UPDATE_FILE_SUFFIX)); |
| |
| |
| private final Stack<File> mFileStack = new Stack<>(); |
| private StorageManager mStorageManager; |
| private SystemUpdater mSystemUpdater; |
| private CarUiRecyclerView mFolderListView; |
| private TextView mCurrentPathView; |
| |
| private final StorageEventListener mListener = new StorageEventListener() { |
| @Override |
| public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, String.format( |
| "onVolumeMetadataChanged %d %d %s", oldState, newState, vol.toString())); |
| } |
| mFileStack.clear(); |
| showMountedVolumes(); |
| } |
| }; |
| |
| @Override |
| public void onAttach(Context context) { |
| super.onAttach(context); |
| |
| mSystemUpdater = (SystemUpdater) context; |
| } |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| Context context = getContext(); |
| |
| mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); |
| if (mStorageManager == null) { |
| if (Log.isLoggable(TAG, Log.WARN)) { |
| Log.w(TAG, "Failed to get StorageManager"); |
| } |
| Toast.makeText(context, R.string.cannot_access_storage, Toast.LENGTH_LONG).show(); |
| return; |
| } |
| } |
| |
| @Override |
| public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, |
| Bundle savedInstanceState) { |
| return inflater.inflate(R.layout.folder_list, container, false); |
| } |
| |
| @Override |
| public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { |
| mFolderListView = view.findViewById(R.id.folder_list); |
| mCurrentPathView = view.findViewById(R.id.current_path); |
| } |
| |
| @Override |
| public void onActivityCreated(Bundle savedInstanceState) { |
| super.onActivityCreated(savedInstanceState); |
| showMountedVolumes(); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| if (mStorageManager != null) { |
| mStorageManager.registerListener(mListener); |
| } |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| if (mStorageManager != null) { |
| mStorageManager.unregisterListener(mListener); |
| } |
| } |
| |
| /** Display the mounted volumes on this device. */ |
| private void showMountedVolumes() { |
| if (mStorageManager == null) { |
| return; |
| } |
| final List<VolumeInfo> vols = mStorageManager.getVolumes(); |
| ArrayList<File> volumes = new ArrayList<>(vols.size()); |
| for (VolumeInfo vol : vols) { |
| File path = vol.getPathForUser(getActivity().getUserId()); |
| if (vol.getState() == VolumeInfo.STATE_MOUNTED |
| && vol.getType() == VolumeInfo.TYPE_PUBLIC |
| && path != null) { |
| volumes.add(path); |
| } |
| } |
| |
| // Otherwise show all of the available volumes. |
| mCurrentPathView.setText(getString(R.string.volumes, volumes.size())); |
| setFileList(volumes); |
| } |
| |
| /** Set the list of files shown on the screen. */ |
| private void setFileList(List<File> files) { |
| List<CarUiListItem> fileList = new ArrayList<>(); |
| for (File file : files) { |
| CarUiContentListItem item = new CarUiContentListItem(CarUiContentListItem.Action.NONE); |
| item.setTitle(file.getName()); |
| item.setOnItemClickedListener(i -> onFileSelected(file)); |
| fileList.add(item); |
| } |
| |
| CarUiListItemAdapter adapter = new CarUiListItemAdapter(fileList); |
| adapter.setMaxItems(CarUiRecyclerView.ItemCap.UNLIMITED); |
| mFolderListView.setAdapter(adapter); |
| } |
| |
| /** Handle user selection of a file. */ |
| private void onFileSelected(File file) { |
| if (isUpdateFile(file)) { |
| mFileStack.clear(); |
| mSystemUpdater.applyUpdate(file); |
| } else if (file.isDirectory()) { |
| showFolderContent(file); |
| mFileStack.push(file); |
| } else { |
| Toast.makeText(getContext(), R.string.invalid_file_type, Toast.LENGTH_LONG).show(); |
| } |
| } |
| |
| @Override |
| public boolean goUp() { |
| if (mFileStack.empty()) { |
| return false; |
| } |
| mFileStack.pop(); |
| if (!mFileStack.empty()) { |
| // Show the list of files contained in the top of the stack. |
| showFolderContent(mFileStack.peek()); |
| } else { |
| // When the stack is empty, display the volumes and reset the title. |
| showMountedVolumes(); |
| } |
| return true; |
| } |
| |
| /** Display the content at the provided {@code location}. */ |
| private void showFolderContent(File folder) { |
| if (!folder.isDirectory()) { |
| // This should not happen. |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "Cannot show contents of a file."); |
| } |
| return; |
| } |
| |
| mCurrentPathView.setText(getString(R.string.path, folder.getAbsolutePath())); |
| |
| // Retrieve the list of files and update the displayed list. |
| new AsyncTask<File, Void, File[]>() { |
| @Override |
| protected File[] doInBackground(File... file) { |
| return file[0].listFiles(UPDATE_FILE_FILTER); |
| } |
| |
| @Override |
| protected void onPostExecute(File[] results) { |
| super.onPostExecute(results); |
| if (results == null) { |
| results = new File[0]; |
| Toast.makeText(getContext(), R.string.cannot_access_storage, |
| Toast.LENGTH_LONG).show(); |
| } |
| setFileList(Arrays.asList(results)); |
| } |
| }.execute(folder); |
| } |
| |
| /** Returns true if a file is considered to contain a system update. */ |
| private static boolean isUpdateFile(File file) { |
| return file.getName().endsWith(UPDATE_FILE_SUFFIX); |
| } |
| |
| /** Used to request installation of an update. */ |
| interface SystemUpdater { |
| /** Attempt to apply an update to the device contained in the {@code file}. */ |
| void applyUpdate(File file); |
| } |
| } |