aboutsummaryrefslogtreecommitdiffstats
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkDeltaVisitor.java
blob: 5d6793a927a13297cbe619bc75185f09fd818c48 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
 *
 * 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.ide.eclipse.adt.build;

import com.android.ide.eclipse.adt.build.BaseBuilder.BaseDeltaVisitor;
import com.android.ide.eclipse.common.AndroidConstants;
import com.android.sdklib.SdkConstants;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;

import java.util.ArrayList;

/**
 * Delta resource visitor looking for changes that will trigger a new packaging of an Android
 * application.
 * <p/>
 * This looks for the following changes:
 * <ul>
 * <li>Any change to the AndroidManifest.xml file</li>
 * <li>Any change inside the assets/ folder</li>
 * <li>Any file change inside the res/ folder</li>
 * <li>Any .class file change inside the output folder</li>
 * <li>Any change to the classes.dex inside the output folder</li>
 * <li>Any change to the packaged resources file inside the output folder</li>
 * <li>Any change to a non java/aidl file inside the source folders</li>
 * <li>Any change to .so file inside the lib (native library) folder</li>
 * </ul>
 */
public class ApkDeltaVisitor extends BaseDeltaVisitor
        implements IResourceDeltaVisitor {

    /**
     * compile flag. This is set to true if one of the changed/added/removed
     * file is a .class file. Upon visiting all the delta resources, if this
     * flag is true, then we know we'll have to make the "classes.dex" file.
     */
    private boolean mConvertToDex = false;

    /**
     * compile flag. This is set to true if one of the changed/added/removed
     * file is a resource file. Upon visiting all the delta resources, if
     * this flag is true, then we know we'll have to make the intermediate
     * apk file.
     */
    private boolean mPackageResources = false;
    
    /**
     * Final package flag. This is set to true if one of the changed/added/removed
     * file is a non java file (or aidl) in the resource folder. Upon visiting all the
     * delta resources, if this flag is true, then we know we'll have to make the final
     * package.
     */
    private boolean mMakeFinalPackage = false;

    /** List of source folders. */
    private ArrayList<IPath> mSourceFolders;

    private IPath mOutputPath;

    private IPath mAssetPath;

    private IPath mResPath;

    private IPath mLibFolder;

    /**
     * Builds the object with a specified output folder.
     * @param builder the xml builder using this object to visit the
     *  resource delta.
     * @param sourceFolders the list of source folders for the project, relative to the workspace.
     * @param outputfolder the output folder of the project.
     */
    public ApkDeltaVisitor(BaseBuilder builder, ArrayList<IPath> sourceFolders,
            IFolder outputfolder) {
        super(builder);
        mSourceFolders = sourceFolders;
        
        if (outputfolder != null) {
            mOutputPath = outputfolder.getFullPath();
        }
        
        IResource assetFolder = builder.getProject().findMember(SdkConstants.FD_ASSETS);
        if (assetFolder != null) {
            mAssetPath = assetFolder.getFullPath();
        }

        IResource resFolder = builder.getProject().findMember(SdkConstants.FD_RESOURCES);
        if (resFolder != null) {
            mResPath = resFolder.getFullPath();
        }
        
        IResource libFolder = builder.getProject().findMember(SdkConstants.FD_NATIVE_LIBS);
        if (libFolder != null) {
            mLibFolder = libFolder.getFullPath();
        }
    }

    public boolean getConvertToDex() {
        return mConvertToDex;
    }

    public boolean getPackageResources() {
        return mPackageResources;
    }
    
    public boolean getMakeFinalPackage() {
        return mMakeFinalPackage;
    }

    /**
     * {@inheritDoc}
     * @throws CoreException 
     *
     * @see org.eclipse.core.resources.IResourceDeltaVisitor
     *      #visit(org.eclipse.core.resources.IResourceDelta)
     */
    public boolean visit(IResourceDelta delta) throws CoreException {
        // if all flags are true, we can stop going through the resource delta.
        if (mConvertToDex && mPackageResources && mMakeFinalPackage) {
            return false;
        }

        // we are only going to look for changes in res/, src/ and in
        // AndroidManifest.xml since the delta visitor goes through the main
        // folder before its childre we can check when the path segment
        // count is 2 (format will be /$Project/folder) and make sure we are
        // processing res/, src/ or AndroidManifest.xml
        IResource resource = delta.getResource();
        IPath path = resource.getFullPath();
        String[] pathSegments = path.segments();
        int type = resource.getType();

        // since the delta visitor also visits the root we return true if
        // segments.length = 1
        if (pathSegments.length == 1) {
            return true;
        }

        // check the manifest.
        if (pathSegments.length == 2 &&
                AndroidConstants.FN_ANDROID_MANIFEST.equalsIgnoreCase(pathSegments[1])) {
            // if the manifest changed we have to repackage the
            // resources.
            mPackageResources = true;
            mMakeFinalPackage = true;

            // we don't want to go to the children, not like they are
            // any for this resource anyway.
            return false;
        }
        
        // check the other folders.
        if (mOutputPath != null && mOutputPath.isPrefixOf(path)) {
            // a resource changed inside the output folder.
            if (type == IResource.FILE) {
                // just check this is a .class file. Any modification will
                // trigger a change in the classes.dex file
                String ext = resource.getFileExtension();
                if (AndroidConstants.EXT_CLASS.equalsIgnoreCase(ext)) {
                    mConvertToDex = true;
                    mMakeFinalPackage = true;
    
                    // no need to check the children, as we are in a package
                    // and there can only be subpackage children containing
                    // only .class files
                    return false;
                }

                // check for a few files directly in the output folder and force
                // rebuild if they have been deleted.
                if (delta.getKind() == IResourceDelta.REMOVED) {
                    IPath parentPath = path.removeLastSegments(1);
                    if (mOutputPath.equals(parentPath)) {
                        String resourceName = resource.getName();
                        // check if classes.dex was removed
                        if (resourceName.equalsIgnoreCase(AndroidConstants.FN_CLASSES_DEX)) {
                            mConvertToDex = true;
                            mMakeFinalPackage = true;
                        } else if (resourceName.equalsIgnoreCase(
                                AndroidConstants.FN_RESOURCES_AP_) ||
                                AndroidConstants.PATTERN_RESOURCES_S_AP_.matcher(
                                        resourceName).matches()) {
                            // or if the default resources.ap_ or a configured version
                            // (resources-###.ap_) was removed.
                            mPackageResources = true;
                            mMakeFinalPackage = true;
                        }
                    }
                }
            }

            // if this is a folder, we only go visit it if we don't already know
            // that we need to convert to dex already.
            return mConvertToDex == false;
        } else if (mResPath != null && mResPath.isPrefixOf(path)) {
            // in the res folder we are looking for any file modification
            // (we don't care about folder being added/removed, only content
            // is important)
            if (type == IResource.FILE) {
                mPackageResources = true;
                mMakeFinalPackage = true;
                return false;
            }

            // for folders, return true only if we don't already know we have to
            // package the resources.
            return mPackageResources == false;
        } else if (mAssetPath != null && mAssetPath.isPrefixOf(path)) {
            // this is the assets folder that was modified.
            // we don't care what content was changed. All we care
            // about is that something changed inside. No need to visit
            // the children even.
            mPackageResources = true;
            mMakeFinalPackage = true;
            return false;
        } else if (mLibFolder != null && mLibFolder.isPrefixOf(path)) {
            // inside the native library folder. Test if the changed resource is a .so file.
            if (type == IResource.FILE &&
                    path.getFileExtension().equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) {
                mMakeFinalPackage = true;
                return false; // return false for file.
            }

            // for folders, return true only if we don't already know we have to make the
            // final package.
            return mMakeFinalPackage == false;
        } else {
            // we are in a folder that is neither the resource folders, nor the output.
            // check against all the source folders, unless we already know we need to do
            // the final package.
            // This could be a source folder or a folder leading to a source folder.
            // However we only check this if we don't already know that we need to build the
            // package anyway
            if (mMakeFinalPackage == false) {
                for (IPath sourcePath : mSourceFolders) {
                    if (sourcePath.isPrefixOf(path)) {
                        // In the source folders, we are looking for any kind of
                        // modification related to file that are not java files.
                        // Also excluded are aidl files, and package.html files
                        if (type == IResource.FOLDER) {
                            // always visit the subfolders, unless the folder is not to be included
                            return ApkBuilder.checkFolderForPackaging((IFolder)resource);
                        } else if (type == IResource.FILE) {
                            if (ApkBuilder.checkFileForPackaging((IFile)resource)) {
                                mMakeFinalPackage = true;
                            }

                            return false;
                        }
                        
                    }
                }
            }
        }
        
        // if the folder is not inside one of the folders we are interested in (res, assets, output,
        // source folders), it could be a folder leading to them, so we return true.
        return true;
    }
}