1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.android.car.kitchensink.backup; 18 19 import android.annotation.Nullable; 20 import android.app.AlertDialog; 21 import android.app.backup.BackupManager; 22 import android.app.backup.RestoreObserver; 23 import android.app.backup.RestoreSession; 24 import android.app.backup.RestoreSet; 25 import android.content.pm.PackageInfo; 26 import android.content.pm.PackageManager; 27 import android.os.Bundle; 28 import android.os.UserHandle; 29 import android.util.Log; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.widget.Button; 34 35 import androidx.fragment.app.Fragment; 36 37 import com.google.android.car.kitchensink.R; 38 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.List; 42 import java.util.concurrent.CountDownLatch; 43 import java.util.concurrent.Executors; 44 import java.util.concurrent.TimeUnit; 45 46 public final class BackupAndRestoreFragment extends Fragment { 47 48 private static final String TAG = BackupAndRestoreFragment.class.getSimpleName(); 49 private static final String TRANSPORT_DIR_NAME = 50 "com.google.android.car.kitchensink.backup.KitchenSinkBackupTransport"; 51 private static final long CURRENT_SET_TOKEN = 1; 52 53 private final int mUserId = UserHandle.myUserId(); 54 55 private BackupManager mBackupManager; 56 57 private Button mBackupButton; 58 private Button mRestoreButton; 59 private Button mShowTransportButton; 60 61 @Override onCreate(Bundle savedInstanceState)62 public void onCreate(Bundle savedInstanceState) { 63 super.onCreate(savedInstanceState); 64 mBackupManager = new BackupManager(getContext()); 65 } 66 67 @Override onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)68 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 69 @Nullable Bundle savedInstanceState) { 70 return inflater.inflate(R.layout.backup_restore_fragment, container, false); 71 } 72 73 @Override onViewCreated(View view, Bundle savedInstanceState)74 public void onViewCreated(View view, Bundle savedInstanceState) { 75 mShowTransportButton = view.findViewById(R.id.show_transport); 76 mBackupButton = view.findViewById(R.id.backup); 77 mRestoreButton = view.findViewById(R.id.restore); 78 79 mShowTransportButton.setOnClickListener((v) -> showTransport()); 80 mBackupButton.setOnClickListener((v) -> backup()); 81 mRestoreButton.setOnClickListener((v) -> restore()); 82 } 83 showTransport()84 private void showTransport() { 85 boolean isEnabled = mBackupManager.isBackupEnabled(); 86 Log.v(TAG, "backup is enabled: " + isEnabled); 87 if (!isEnabled) { 88 showMessage("Backup is not enabled yet.\nEnable backup first."); 89 return; 90 } 91 String[] allTransports = mBackupManager.listAllTransports(); 92 Log.v(TAG, "All transports: " + Arrays.toString(allTransports)); 93 String currentTransport = mBackupManager.getCurrentTransport(); 94 Log.v(TAG, "Current Transport:" + currentTransport); 95 96 StringBuilder sb = new StringBuilder().append("All transports: "); 97 Arrays.stream(allTransports).forEach(t -> sb.append('\n').append(t)); 98 sb.append("\nCurrent Transport:\n").append(currentTransport); 99 showMessage(sb.toString()); 100 } 101 backup()102 private void backup() { 103 boolean isEnabled = mBackupManager.isBackupEnabled(); 104 Log.v(TAG, "backup is enabled: " + isEnabled); 105 if (!isEnabled) { 106 showMessage("Backup is not enabled yet.\nEnable backup first."); 107 return; 108 } 109 110 Executors.newSingleThreadExecutor().execute(() -> { 111 backupNow(); 112 requireActivity().runOnUiThread(() -> 113 showMessage("backup is queued, waiting for it to complete.")); 114 }); 115 } 116 backupNow()117 private void backupNow() { 118 PackageManager packageManager = getActivity().getPackageManager(); 119 List<PackageInfo> installedPackages = null; 120 try { 121 installedPackages = packageManager.getInstalledPackagesAsUser(/* flags= */0, mUserId); 122 Log.v(TAG, "installed packages: " + installedPackages); 123 } catch (Exception e) { 124 Log.e(TAG, "exception in backupNow()", e); 125 return; 126 } 127 128 if (installedPackages != null) { 129 String[] packages = installedPackages.stream().map(p -> p.packageName) 130 .toArray(String[]::new); 131 132 List<String> filteredPackages = new ArrayList<>(); 133 134 for (String p : packages) { 135 try { 136 boolean eligible = mBackupManager.isAppEligibleForBackup(p); 137 Log.v(TAG, "eligible: " + eligible + " package name: " + p); 138 if (eligible) { 139 filteredPackages.add(p); 140 Log.v(TAG, "adding package to filtered packages"); 141 } 142 } catch (Exception e) { 143 Log.e(TAG, "isAppEligibleForBackup() cannot connect: ", e); 144 } 145 } 146 // Currently, the observer is not waiting enough time for the backup to finish 147 // Will implement it later for full functionality 148 int res = mBackupManager.requestBackup(packages, /* observer= */ null); 149 Log.v(TAG, "request backup returned code: " + res); 150 if (res == 0) { 151 Log.v(TAG, "request backup res successful!"); 152 } 153 } 154 } 155 restore()156 private void restore() { 157 boolean isEnabled = mBackupManager.isBackupEnabled(); 158 Log.v(TAG, "backup is enabled: " + isEnabled); 159 if (!isEnabled) { 160 showMessage("Backup is not enabled yet.\nClick enable backup first."); 161 return; 162 } 163 164 // TODO: use Handler / HandlerThread instead 165 Executors.newSingleThreadExecutor().execute(() -> { 166 restoreNow(); 167 requireActivity().runOnUiThread(() -> showMessage("restore is complete")); 168 }); 169 } 170 restoreNow()171 private void restoreNow() { 172 RestoreObserverLocal observer = new RestoreObserverLocal(); 173 RestoreSession session = null; 174 try { 175 session = mBackupManager.beginRestoreSession(); 176 Log.v(TAG, "current restore session: " + session); 177 if (session != null) { 178 int err = session.getAvailableRestoreSets(observer); 179 if (err == 0) { 180 observer.waitForCompletion(); 181 int restoreResult = session.restoreAll(CURRENT_SET_TOKEN, observer); 182 Log.v(TAG, "restore all returned code: " + restoreResult); 183 if (restoreResult == 0) { 184 Log.i(TAG, "restore successful!!"); 185 } 186 } else { 187 Log.v(TAG, "Unable to contact server for restore" + err); 188 } 189 } else { 190 Log.i(TAG, "No restore session"); 191 } 192 } catch (Exception e) { 193 Log.e(TAG, "exception in beginRestoreSession(): ", e); 194 } finally { 195 if (session != null) { 196 try { 197 session.endRestoreSession(); 198 } catch (Exception e) { 199 Log.w(TAG, "Failed to end the restore session!", e); 200 } 201 } 202 } 203 } 204 205 private static final class RestoreObserverLocal extends RestoreObserver { 206 final CountDownLatch mLatch = new CountDownLatch(1); 207 208 @Override restoreSetsAvailable(RestoreSet[] result)209 public void restoreSetsAvailable(RestoreSet[] result) { 210 mLatch.countDown(); 211 } 212 waitForCompletion()213 public void waitForCompletion() { 214 boolean received = false; 215 try { 216 received = mLatch.await(120, TimeUnit.SECONDS); 217 } catch (InterruptedException ex) { 218 Log.e(TAG, "Current thread is stopped during restore: ", ex); 219 Thread.currentThread().interrupt(); 220 } 221 if (!received) { 222 Log.w(TAG, "Restore operation is timed out after 120 seconds."); 223 } 224 } 225 } 226 showMessage(String pattern, Object... args)227 private void showMessage(String pattern, Object... args) { 228 String message = String.format(pattern, args); 229 Log.v(TAG, "showMessage(): " + message); 230 new AlertDialog.Builder(getContext()).setMessage(message).show(); 231 } 232 } 233 234