1 /*
2  * Copyright (C) 2011 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.android.tradefed.targetprep;
18 
19 import com.android.ddmlib.IDevice;
20 import com.android.ddmlib.Log;
21 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
22 import com.android.tradefed.build.IBuildInfo;
23 import com.android.tradefed.build.IDeviceBuildInfo;
24 import com.android.tradefed.config.Option;
25 import com.android.tradefed.config.OptionClass;
26 import com.android.tradefed.device.DeviceNotAvailableException;
27 import com.android.tradefed.device.ITestDevice;
28 import com.android.tradefed.util.FileUtil;
29 
30 import java.io.File;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collection;
34 
35 /**
36  * A {@link ITargetPreparer} that attempts to push any number of files from any host path to any
37  * device path.
38  *
39  * <p>Should be performed *after* a new build is flashed, and *after* DeviceSetup is run (if
40  * enabled)
41  */
42 @OptionClass(alias = "push-file")
43 public class PushFilePreparer extends BaseTargetPreparer implements ITargetCleaner {
44     private static final String LOG_TAG = "PushFilePreparer";
45     private static final String MEDIA_SCAN_INTENT =
46             "am broadcast -a android.intent.action.MEDIA_MOUNTED -d file://%s "
47                     + "--receiver-include-background";
48 
49     @Option(name="push", description=
50             "A push-spec, formatted as '/path/to/srcfile.txt->/path/to/destfile.txt' or " +
51             "'/path/to/srcfile.txt->/path/to/destdir/'. May be repeated.")
52     private Collection<String> mPushSpecs = new ArrayList<>();
53 
54     @Option(name="post-push", description=
55             "A command to run on the device (with `adb shell (yourcommand)`) after all pushes " +
56             "have been attempted.  Will not be run if a push fails with abort-on-push-failure " +
57             "enabled.  May be repeated.")
58     private Collection<String> mPostPushCommands = new ArrayList<>();
59 
60     @Option(name="abort-on-push-failure", description=
61             "If false, continue if pushes fail.  If true, abort the Invocation on any failure.")
62     private boolean mAbortOnFailure = true;
63 
64     @Option(name="trigger-media-scan", description=
65             "After pushing files, trigger a media scan of external storage on device.")
66     private boolean mTriggerMediaScan = false;
67 
68     @Option(name="cleanup", description = "Whether files pushed onto device should be cleaned up "
69             + "after test. Note that the preparer does not verify that files/directories have "
70             + "been deleted.")
71     private boolean mCleanup = false;
72 
73     @Option(name="remount-system", description="Remounts system partition to be writable "
74             + "so that files could be pushed there too")
75     private boolean mRemount = false;
76 
77     private Collection<String> mFilesPushed = null;
78 
79     /**
80      * Helper method to only throw if mAbortOnFailure is enabled.  Callers should behave as if this
81      * method may return.
82      */
fail(String message, ITestDevice device)83     private void fail(String message, ITestDevice device) throws TargetSetupError {
84         if (mAbortOnFailure) {
85             throw new TargetSetupError(message, device.getDeviceDescriptor());
86         } else {
87             // Log the error and return
88             Log.w(LOG_TAG, message);
89         }
90     }
91 
92     /**
93      * Resolve relative file path via {@link IBuildInfo} and test cases directories.
94      *
95      * @param buildInfo the build artifact information
96      * @param fileName relative file path to be resolved
97      * @return the file from the build info or test cases directories
98      */
resolveRelativeFilePath(IBuildInfo buildInfo, String fileName)99     public File resolveRelativeFilePath(IBuildInfo buildInfo, String fileName) {
100         File src = null;
101         if (buildInfo != null) {
102             src = buildInfo.getFile(fileName);
103             if (src != null && src.exists()) {
104                 return src;
105             }
106         }
107 
108         if (buildInfo instanceof IDeviceBuildInfo) {
109             IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo) buildInfo;
110             // If it exists always look first in the ANDROID_TARGET_OUT_TESTCASES
111             File targetTestCases = deviceBuild.getFile(BuildInfoFileKey.TARGET_LINKED_DIR);
112             if (targetTestCases != null) {
113                 src = FileUtil.findFile(targetTestCases, fileName);
114             }
115             if (src != null && src.exists()) {
116                 return src;
117             }
118             // Search the full tests dir if no target dir is available.
119             File testsDir = deviceBuild.getTestsDir();
120             return FileUtil.findFile(testsDir, fileName);
121         }
122         return null;
123     }
124 
125     /**
126      * {@inheritDoc}
127      */
128     @Override
setUp(ITestDevice device, IBuildInfo buildInfo)129     public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, BuildError,
130             DeviceNotAvailableException {
131         mFilesPushed = new ArrayList<>();
132         if (mRemount) {
133             device.remountSystemWritable();
134         }
135         for (String pushspec : mPushSpecs) {
136             String[] pair = pushspec.split("->");
137             if (pair.length != 2) {
138                 fail(String.format("Invalid pushspec: '%s'", Arrays.asList(pair)), device);
139                 continue;
140             }
141             Log.d(LOG_TAG, String.format("Trying to push local '%s' to remote '%s'", pair[0],
142                     pair[1]));
143 
144             File src = new File(pair[0]);
145             if (!src.isAbsolute()) {
146                 src = resolveRelativeFilePath(buildInfo, pair[0]);
147             }
148             if (src == null || !src.exists()) {
149                 fail(String.format("Local source file '%s' does not exist", pair[0]), device);
150                 continue;
151             }
152             if (src.isDirectory()) {
153                 if (!device.pushDir(src, pair[1])) {
154                     fail(String.format("Failed to push local '%s' to remote '%s'", pair[0],
155                             pair[1]), device);
156                     continue;
157                 } else {
158                     mFilesPushed.add(pair[1]);
159                 }
160             } else {
161                 if (!device.pushFile(src, pair[1])) {
162                     fail(String.format("Failed to push local '%s' to remote '%s'", pair[0],
163                             pair[1]), device);
164                     continue;
165                 } else {
166                     mFilesPushed.add(pair[1]);
167                 }
168             }
169         }
170 
171         for (String command : mPostPushCommands) {
172             device.executeShellCommand(command);
173         }
174 
175         if (mTriggerMediaScan) {
176             String mountPoint = device.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
177             device.executeShellCommand(String.format(MEDIA_SCAN_INTENT, mountPoint));
178         }
179     }
180 
181     /**
182      * {@inheritDoc}
183      */
184     @Override
tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)185     public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
186             throws DeviceNotAvailableException {
187         if (!(e instanceof DeviceNotAvailableException) && mCleanup && mFilesPushed != null) {
188             if (mRemount) {
189                 device.remountSystemWritable();
190             }
191             for (String devicePath : mFilesPushed) {
192                 device.executeShellCommand("rm -r " + devicePath);
193             }
194         }
195     }
196 }
197